Advanced mapping with Automapper

Setting a manual mapping configuration

We can instruct Automapper to map a destination object’s member to a specific source object’s member. This is useful when the default mapping (which search for a member with the same name) doesn’t produce the correct result.

Mapper.CreateMap<MyRequestEntity, MyRequestModel>()
    .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.MyRequestId))

Using a custom resolver for complex mappings

Let’s consider this scenario: our MyRequestEntity object has an InterviewRequest collection which contains all together different kinds of interview requests, all associated with the main request. Each request has a InterviewTypeId field, which tells the type of the interview request. Furthermore a comment related to each interview request type is included in the main object.

On the other hand our MyRequestModel object has multiple objects, one for each interview request type, which contain the collection of the related interview requests together with the comment. So the destination object has a very different data structure compared with the source object, how can we perform a similar mapping?

We can use a custom resolver to generate on the fly an exchange object from the source object, which exposes data with the same structure as the destination object. Then Automapper can perform its mapping from this exchange object and the destination object.

Mapper.CreateMap<MyRequestEntity, MyRequestModel>()
    .ForMember(dest => dest.FaceToFaceInterviews, opts => opts.ResolveUsing<FaceToFaceInterviewResolver>())

Here we are telling Automapper that for our FaceToFaceInterviews destination property (which contains the collection of interview request of type “face-to-face”, together with the comment) we want to use a custom resolver of class FaceToFaceInterviewResolver. Here is the implementation of the resolver:

public class FaceToFaceInterviewResolver : ValueResolver<MyRequestEntity, InterviewRequestModel>
{
    protected override InterviewRequestModel ResolveCore(MyRequestEntity source)
    {
        var obj = new InterviewRequestModel()
        {
            Items = source.InterviewRequest.Where(
                i => i.InterviewTypeId == (int)RequestConstants.MyRequestInterviewType.FaceToFaceInterview),
            Comment = source.FaceToFaceInterviewNotes
        };
        return obj.Items.Any() ? obj : null;
    }
}

We are creating a InterviewRequestModel object (our exchange object) from the source object. Automapper will use this object as the source object when mapping the FaceToFaceInterviews member of the destination object. In other words we are manipulating the source object’s data to fit the destination data structure, in order to be able to perform the mapping. At this point we just need to define a mapping between the exchange object and the destination object:

Mapper.CreateMap<InterviewRequestModel, FaceToFaceInterviewsModel>();

The mapping is very simple because we defined our exchange object to have the same structure as the destination object, so the mapping can be performed just using the default behavior. Of course we could have an exchange object that is different in respect with the destination object. In that case we have to instruct Automapper with the appropriate mapping rules as always.


Executing other actions after the mapping

We can execute other operations on the destination object after the mapping has been performed (for example if we need the child objects to refer the parent). We can do this using the AfterMap() method in this way:

Mapper.CreateMap<MyRequestModel, MyRequestEntity>()
    .ForMember(dest => dest.MyRequestId, opts => opts.MapFrom(src => src.Id))
    ...
    .AfterMap((src, dest) =>
    {
        ...
    });

Automapper

With Automapper we can automatically perform deep copies between different object through reflection. The default mapping attempt to copy properties with the same name from the source type to the destination type. Is very useful when we want to generate to copy a model object to a view model object or to a subclassed object.

To perform a copy we have to map each involved source/destination type for our copy. In this case we are creating a mapping between the GlobalData and the SpecificData types to get a new SpecificData instance from a GlobalData instance.

Mapper.CreateMap<GlobalData, SpecificData>();
SpecificData dst = Mapper.Map<GlobalData, SpecificData>(src);

Using a custom Constructor

We can define as well more complex mappings. In this case we are instructing Automapper to use a particular constructor to create our MyFeedItem instances. As we can see from this example we can map a collection just by mapping its inner element’s type.

Mapper.CreateMap<OuterFeed, MyFeedItem>()
    .ConstructUsing((OuterFeed src) => new MyFeedItem(GlobalData));
var Feeds = Mapper.Map<IEnumerable<OuterFeed>, IEnumerable<MyFeedItem>>(feeds);

Mapping through Subclasses

If we need to map a type which includes some property of abstract/interface type we have to tell Automapper how to map each of its derived/concrete types. We can have the same situation even when we are working with subclassed object.

In this example we want to map a ItemModel type to a ItemViewModel type. The ItemModel type includes a property of type RelatedItemModel, that is an abstract class, so within an instance we’ll find one of its derived types: RelatedItemModelCountry, RelatedItemModelGender or RelatedItemModelGeneric.

On the other side, the ItemViewModel type includes a correspondent property of type RelatedItemViewModel, that is an abstract class as well, so within an instance we’ll find one of its derived types: RelatedItemViewModelCountry, RelatedItemViewModelGender or RelatedItemViewModelGeneric.

AutoMapper.Mapper.CreateMap<ItemModel, ItemViewModel>();
AutoMapper.Mapper.CreateMap<RelatedItemModel, RelatedItemViewModel>()
    .Include<RelatedItemModelCountry, RelatedItemViewModelCountry>()
    .Include<RelatedItemModelGender, RelatedItemViewModelGender>()
    .Include<RelatedItemModelGeneric, RelatedItemViewModelGeneric>();
AutoMapper.Mapper.CreateMap<RelatedItemModelCountry, RelatedItemViewModelCountry>();
AutoMapper.Mapper.CreateMap<RelatedItemModelGender, RelatedItemViewModelGender>();
AutoMapper.Mapper.CreateMap<RelatedItemModelGeneric, RelatedItemViewModelGeneric>();
var itemViewModel= AutoMapper.Mapper.Map<ItemModel, ItemViewModel>(item);