Kasaku
Kasaku

Reputation: 2192

AutoMapper - Mapping derived classes without abstract class on source side

I am trying to map the following scenario:

// SourceA + SourceB to not derive from a shared base class.
class SourceCollection
{
    IList<SourceA> ACollection;
    IList<SourceB> BCollection;

    // ... other properties ...
}

class DestCollection
{
    IList<DestBase> Collection;

    // ... other properties ...
}

abstract class DestBase { }

// In their destination form, DestA + DestB share DestBase as a base class.
class DestA : DestBase { }
class DestB : DestBase { }

When I map SourceCollection > DestCollection I want each of the collections to be gathered into a single collection. Ideally I would like to do this without a custom resolver, since AutoMapper understands how to map SourceA > DestA and SourceB > DestB fine. I just need it to realise that SourceA can map to DestBase as DestA, as DestA derives from DestBase.

As there is no SourceBase I can't just map that to DestBase and use .Include.

I'm guessing this might require a custom resolver since it is two collections merging into one (I would just expect the result to be concatonated), but it would be nice if it didn't require it. Or at least I could write a single custom resolver that was generic enough to not require to be changed if SourceC + DestC came along.

Update:

This is a ValueResolver it can be achieved with, however it does require accounting for each type to resolve manually:

public class BaseCollectionResolver : ValueResolver< SourceCollection, IList<DestBase> >
{
    protected override IList<DestBase> ResolveCore(SourceCollection source)
    {
        var items = new List<DestBase>();

        foreach (var sourceA in source.ACollection)
            items.Add( Mapper.Map<SourceA, DestA>(sourceA) );

        foreach (var sourceB in source.BCollection)
            items.Add( Mapper.Map<SourceB, DestB>(sourceB) );

        return items;
    }
}

And then mapped accordingly:

Mapper.CreateMap<SourceCollection, DestCollection>()
      .ForMember(dest => dest.Collection, m => m.ResolveUsing<BaseCollectionResolver>());

Upvotes: 1

Views: 2277

Answers (1)

djdd87
djdd87

Reputation: 68506

I can't think of a nice way to do it without using an ITypeConverter:

public class SourceCollectionToDestCollection 
    : ITypeConverter<SourceCollection, DestCollection>
{
    public DestCollection Convert(ResolutionContext context)
    {
        SourceCollection source = context.SourceValue as SourceCollection;

        DestCollection destination = context.DestinationValue as DestCollection 
            ?? new DestCollection();

        foreach (var sourceA in source.ACollection)
        {
            DestA dest = Mapper.Map<SourceA, DestA>(sourceA);
            destination.Collection.Add(dest);
        }

        foreach (var sourceB in source.BCollection)
        {
            DestB dest = Mapper.Map<SourceB, DestB>(sourceB);
            destination.Collection.Add(dest);
        }

        return destination;
    }
}

And add it to the mapper:

Mapper.CreateMap<SourceCollection, DestCollection>()
    .ConvertUsing<SourceCollectionToDestCollection>();

Upvotes: 1

Related Questions