Update on inheriting base type mappings with AutoMapper

Several months ago I wrote this post which shows an extension method for inheriting the mappings on a base type for a child type in AutoMapper.  Since then I have had a few comments on the post which led me to make a small update. One of the assumptions I made in my implementation was that when inheriting base type mappings both of your mapping types will have their base types in the parent mapping. For example:

Mapper.CreateMap<SourceBaseClass, DestBaseClass>()
    .ForMember(x => x.BasePropTwo, m => m.MapFrom(x => x.BaseProp2));
 
Mapper.CreateMap<SourceChildClass, DestChildClass>()
    .ForMember(x => x.ChildPropTwo, m => m.MapFrom(x => x.ChildProp2))
    .InheritMappingFromBaseType();

However, there are scenarios where you may have one type that is mapping to both a parent and child type and would still want to inherit the parent mapping for the child mapping. If you try this with my previous code it would look like this but not work.

Mapper.CreateMap<SourceClass, DestBaseClass>()
    .ForMember(x => x.BasePropTwo, m => m.MapFrom(x => x.BaseProp2));
 
Mapper.CreateMap<SourceClass, DestChildClass>()
    .ForMember(x => x.ChildPropTwo, m => m.MapFrom(x => x.ChildProp2))
    .InheritMappingFromBaseType();

In order to support this scenario I updated the extension method to take an optional enum which specifies if you want to inherit a mapping that has a base type for the source type, destination type or both.

public enum WithBaseFor
{
    Source,
    Destination,
    Both
}
    
public static class Inheritance
{
    public static void InheritMappingFromBaseType<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mappingExpression,
                                                                         WithBaseFor baseFor = WithBaseFor.Both)
    {
        var sourceType = typeof (TSource);
        var destinationType = typeof (TDestination);
        var sourceParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Source
                                   ? sourceType.BaseType
                                   : sourceType;

        var destinationParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Destination
                                        ? destinationType.BaseType
                                        : destinationType;


        mappingExpression
            .BeforeMap((x, y) => Mapper.Map(x, y, sourceParentType, destinationParentType))
            .ForAllMembers(x => x.Condition(r => NotAlreadyMapped(sourceParentType, destinationParentType, r)));
    }


    private static bool NotAlreadyMapped(Type sourceType, Type desitnationType, ResolutionContext r)
    {
        return !r.IsSourceValueNull &&
               Mapper.FindTypeMapFor(sourceType, desitnationType).GetPropertyMaps().Where(
                   m => m.DestinationProperty.Name.Equals(r.MemberName)).Select(y => !y.IsMapped()).All(b => b);
    }
}

Using this new code the above mapping becomes

Mapper.CreateMap<SourceClass, DestBaseClass>()
    .ForMember(x => x.BasePropTwo, m => m.MapFrom(x => x.BaseProp2));
 
Mapper.CreateMap<SourceClass, DestChildClass>()
    .ForMember(x => x.ChildPropTwo, m => m.MapFrom(x => x.ChildProp2))
    .InheritMappingFromBaseType(WithBaseFor.Destination);

There is one caveat to using my InheritMappingFromBaseType extension method that was discovered since my original post … it is not compatible with the Mapper.AssertConfigurationIsValid() call. It will cause this function to fail even though your mapping will work fine. I haven’t had a chance to figure out if there is a way around this but if anyone has an idea I would love to hear it.

I published the updated code and a sample project showing its use to the InheritedAutoMapper GitHub page. Feel free to grab the code and fork it to your liking.

  • http://kennydust.com kennydust

    one easiest way to get around that automapper assert test is to ignore all the members, and explicitly set them if needed (in aftermap or such).

    if you add this somewhere in your InheritMappingFromBaseType extension method ->

    mappingExpression.ForAllMembers(x => x.Ignore());

    the test will pass.

    • Marcin D

      This solution is like disabling this validation for specified mapping.