Search code examples
c#mappingautomapper

AutoMapper - how to set up a single mapping for two distinct use cases?


I have a situation there where I get some data about trips from an API. I'm using AutoMapper to map this "API model" to an EF Core entity for storing in my SQL Server database.

Most of the mappings are straightforward, but I'm struggling with one situation: the TripModel has a property called ReasonForTravel which is an int? value - if it has a value, and that value is > 0, then I want to use a custom ValueConverter to convert that int to a string that I need - but if either the ReasonForTravel has no value at all, or it's <= 0, then I want to instead take the value from CustomTravelReason on the TripModel.

How do I express this in an AutoMapper CreateMap so that I can map my TripModel to Trip entity in one step?

Here's my code - the model, the entity, and the AutoMapper Profile:

public class TripModel
{
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public int? ReasonForTravel { get; set; }
    public string CustomTravelReason { get; set; }
    // more stuff, but irrelevant here
}

public class Trip
{
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public string ReasonForTrip { get; set; }
}

public class TripProfile : Profile
{
    public TripProfile()
    {
        CreateMap<TripModel, Trip>
            // I know these two aren't strictly necessary - just for illustration of what I'm doing
            .ForMember(dest => dest.Firstname, opt => opt.MapFrom(src => src.Firstname))
            .ForMember(dest => dest.Lastname, opt => opt.MapFrom(src => src.Lastname))
            
            // but how do I specify this now?? This is for the case of ReasonForTravel > 0
            .ForMember(dest => dest.ReasonForTrip, opt => opt.ConvertUsing(new TravelReasonConverter(), src => src.Lastname))

            // and this is for the case of ReasonForTravel <= 0 or ReasonForTravel = null
            .ForMember(dest => dest.ReasonForTrip, opt => opt.MapFrom(src => src.CustomTravelReason));
    }
}

Solution

  • There's a MapFrom overload that allows to pass a Func into which you can specify a if/else check. Unfortunately that can't be combined with ConvertUsing, but you can just call that converter yourself.

    Alternatively you choose to use a regular function to do the conversion instead of using a converter.

    CreateMap<TripModel, Trip>()
        // Other mappings
        // ...
        .ForMember(dest => dest.ReasonForTrip,
            opt => opt.MapFrom((src, dest, member, resolutionContext) =>
                src.ReasonForTravel > 0 
                    ? new TravelReasonConverter().Convert(src.ReasonForTravel, resolutionContext) 
                      // or call a function returning a string. 
                    : src.CustomTravelReason
            ));
            
    public class TravelReasonConverter : IValueConverter<int?, string>
    {
        public string Convert(int? sourceMember, ResolutionContext context)
            => "some converted value";    
    }