Am I using Automapper 2.0's Include functionality correctly?

3.3k views Asked by At

Either I'm not, or it isn't working... I have a single Source class that I want to map to multiple views that inherit from each other.

Basically the base class is the Detail, and the child class is Edit or Update which use all the same data as Detail, plus a couple other fields to manage their own lists or whatever.

Here are the maps I'm using:

Mapper.CreateMap<Ticket, Detail>()
                .Include<Ticket, Update>()
                .Include<Ticket, Edit>()
                .ForMember(dest => dest.Priority, opt => opt.MapFrom(src => src.Priority.Code))
                .ForMember(dest => dest.TicketID, opt => opt.MapFrom(src => src.ID))
                .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusCode))
                .ForMember(dest => dest.Category, opt => opt.MapFrom(src => src.ProblemCategoryCode))
                .ForMember(dest => dest.crmBusCode, opt => opt.MapFrom(src => src.Company.crmBusCode))
                .ForMember(dest => dest.TeamMembers, opt => opt.MapFrom(src => src.Schedules.Where(s => s.CompleteTime == null)));

            Mapper.CreateMap<Ticket, Update>()
                .ForMember(m => m.Schedules, opt => opt.MapFrom(t => t.Schedules.Where(s => s.EmployeeID == Util.CurrentUserID() && s.CompleteTime == null)));

            Mapper.CreateMap<Ticket, Edit>();

Then if I Mapper.Map(ticket) any of the properties that use MapFrom don't get evaluated, they just end up with the values they'd have had if there was no set mapping.

So what's wrong here?

2

There are 2 answers

2
nemesv On BEST ANSWER

As an alternative solution if you don't want to call Mapper.Map two times. You can move the common mappings of Detail into an extension method:

public static class MappingExtensions
{
    public static IMappingExpression<Ticket, TDest> MapDetailProperties<TDest>(
         this IMappingExpression<Ticket, TDest> mapBase) where TDest : Detail
    {
        return mapBase
            .ForMember(dest => dest.Priority, 
                opt => opt.MapFrom(src => src.Priority.Code))
             ///....
            .ForMember(dest => dest.TeamMembers, 
               opt => opt.MapFrom(src => src
                   .Schedules.Where(s => s.CompleteTime == null)));
    }
}

And then use that extension method when registering the Ticket -> Update and Ticket -> Edit mappers:

Mapper.CreateMap<Ticket, Update>()
    .MapDetailProperties()
    .ForMember(m => m.Schedules, opt => opt.MapFrom(t => t.Schedules
        .Where(s => s.EmployeeID == Util.CurrentUserID() && 
            s.CompleteTime == null)));

Mapper.CreateMap<Ticket, Edit>()
    .MapDetailProperties();

Then you can use Map normally:

Ticket ticket = new Ticket();    
var edit = Mapper.Map<Ticket, Edit>(ticket);
var update = Mapper.Map<Ticket, Update>(ticket); 
0
Andrew Whitaker On

Am I using Automapper 2.0's Include functionality correctly?

No--When you use .Include, AutoMapper expects that the destination classes are in a similar hierarchy as the source classes (This is discussed further here). In other words, if you were mapping to different subclasses of Ticket to Detail, Update and Edit, Include would be appropriate.

This doesn't seem helpful in your case. I would recommend using the overload of .Map that takes an existing object and modifies it. That way, you only have to define a mapping for the base type:

Ticket ticket = new Ticket();
Edit edit = new Edit();

Mapper.Map<Ticket, Detail>(ticket, edit); 
// Edit has now been automapped using the base mapping.

Mapper.Map<Ticket, Edit>(ticket, edit); 
// The properties unique to Edit have now been mapped.