Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Am I using Automapper 2.0's Include functionality correctly?

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?

like image 629
CodeRedick Avatar asked Jan 14 '12 17:01

CodeRedick


2 Answers

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); 
like image 75
nemesv Avatar answered Sep 21 '22 16:09

nemesv


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.
like image 31
Andrew Whitaker Avatar answered Sep 22 '22 16:09

Andrew Whitaker