AutoMapper is a useful library that makes it easy to map properties between two types. You can declaratively define how you want properties mapped or just let it map based on simple conventions (like matching property names). While working with this library I found out that if you have one set of mapping between base types and then another set of mapping between their respective child types then the child type mappings won’t inherit the base type mappings.
For example, given the follow types:
public class SourceBaseClass { public string BaseProp1 { get; set; } public string BaseProp2 { get; set; } } public class SourceChildClass : SourceBaseClass { public string ChildProp1 { get; set; } public string ChildProp2 { get; set; } } public class DestBaseClass { public string BaseProp1 { get; set; } public string BasePropTwo { get; set; } } public class DestChildClass : DestBaseClass { public string ChildProp1 { get; set; } public string ChildPropTwo { get; set; } }
I created one mapping for the base types so that SourceBaseClass::BaseProp2 would map to DestBaseClass::BasePropTwo and created another mapping for the child types so that SourceChildClass::ChildProp2 would map to DestChildClass::ChildPropTwo.
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));
You may notice I don’t have a mapping for the other properties, but since the names of BaseProp1 and ChildProp1 are the same for both source and destination types the convention base mapping will take care of those.
With these mappings in place if you try to map an instance of SourceChildClass to DestChildClass you will see that BasePropTwo does not get mapped.
[Fact] public void Will_map_base_properties_on_child_types_when_child_types_used() { var source = new SourceChildClass { ChildProp1 = "child1", ChildProp2 = "child2", BaseProp1 = "base1", BaseProp2 = "base2" }; var res = Mapper.Map<SourceChildClass, DestChildClass>(source); Assert.Equal(source.ChildProp1, res.ChildProp1); Assert.Equal(source.ChildProp2, res.ChildPropTwo); Assert.Equal(source.BaseProp1, res.BaseProp1); // Passes, because it matches convention Assert.Equal(source.BaseProp2, res.BasePropTwo); // Fails!, since it does not inherit this mapping from the BaseTypes mappings }
A simple solution to this problem could be to duplicate the base type mapping in the child type mapping definitions. But this is repetitious, especially if you have large types with many mappings on them. To avoid doing that I created an extension method called InheritMappingFromBaseType that will let you declare in a child type mapping that you want to inherit the mappings of your base type.
public static class MapperExtensions { public static void InheritMappingFromBaseType<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mappingExpression) { var sourceType = typeof(TSource); var desctinationType = typeof(TDestination); var sourceParentType = sourceType.BaseType; var destinationParentType = desctinationType.BaseType; 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 the InheritMappingFromBaseType method changes the mapping slightly:
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();
The InheritMappingFromBaseType method will attempt to first map using the base types and then exclude those mapped properties from the child type mapping. This method has saved me from duplicating a large set of mappings since I had several child types which all needed the same base type mappings.
Thats a cool little Extension Method… just saved my bacon!!!
Thanks
Hi there. I was looking exactly for something like this, so thanks for putting it together, but I must be missing something as it doesn’t seem to make any difference. I have:
Mapper.CreateMap()
.ForMember(dest => dest.SupportingImageFilename, opt => opt.Ignore());
Mapper.CreateMap()
.ForMember(dest => dest.EditorialType, opt => opt.MapFrom(src => EditorialType.ToString(src.EditorialType)))
.InheritMappingFromBaseType();
And PressReleaseViewExtended inherits from PressReleaseView, but Automapper still complains that SupportingImageFilename isn’t mapped on PressReleaseViewExtended.
Although that mapping is an ignore, I’ve also tried it in another class with “MapFrom” and it doesn’t carry those mappings over to the child classes either.
Sorry, types got wiped. Should be (curly brackets swapped for lt and gt):
Mapper.CreateMap{PressRelease, PressReleaseView}()
.ForMember(dest => dest.SupportingImageFilename, opt => opt.Ignore());
Mapper.CreateMap{PressRelease, PressReleaseViewExtended}()
.ForMember(dest => dest.EditorialType, opt => opt.MapFrom(src => EditorialType.ToString(src.EditorialType)))
.ForMember(dest => dest.EditToken, opt => opt.Ignore())
.ForMember(dest => dest.SupportingImageFilename, opt => opt.Ignore())
.InheritMappingFromBaseType();
What version of automapper are you using? I will re-test with that version to see if there is a problem.
The latest, 1.1.0.188. Thanks!
I re-tested the code I posted above and it does still work. The issue I think you are having is that the mapping your are inheriting from has only one of its type’s as a base type of the new mapping. My implementation of the inheritance here assumes both type you are mapping between will have their basetypes in the inherited mapping. You can extend my code and add ability to specify which types you expect to have basetypes.
For example maybe something like this:
public enum WithBaseFor
{
Left,
Right,
Both
}
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.Left
? sourceType.BaseType
: sourceType;
var destinationParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Right
? destinationType.BaseType
: destinationType;
mappingExpression
.BeforeMap((x, y) => Mapper.Map(x, y, sourceParentType, destinationParentType))
.ForAllMembers(x => x.Condition(r => NotAlreadyMapped(sourceParentType, destinationParentType, r)));
}
Using this you can specify which type(s) you will be mapping the base type of.
Let me know if this helps.
-Matt
Thanks again for looking into this, and what you’ve written makes perfect sense. I’ve tried your new code too and I’m getting the same problem, that:
AutoMapper.AutoMapperConfigurationException : The following 1 properties on Paladin3.WebUI.Models.Entities.PressReleaseViewExtended are not mapped:
SupportingImageFilename
My automapper config for those two types is:
Mapper.CreateMap{PressRelease, PressReleaseView}()
.ForMember(dest => dest.SupportingImageFilename, opt => opt.Ignore());
Mapper.CreateMap{PressRelease, PressReleaseViewExtended}()
.ForMember(dest => dest.EditorialType, opt => opt.MapFrom(src => EditorialType.ToString(src.EditorialType)))
.ForMember(dest => dest.EditToken, opt => opt.Ignore())
.InheritMappingFromBaseType(MapperExtensions.WithBaseFor.Right);
PressReleaseViewExtended inherits from PressReleaseView. Both are contracted to IPressReleaseLinkable, but if I step through the code your helper function is definitely recognising that the base type is PressReleaseView when mapping PressReleaseViewExtended.
I wondered if it was because I had mapping to “Ignore” and perhaps automapper does something usual for that case, but a separate test with actual mapping from one property to another is getting the same error.
I don’t know the ins-and-outs of automapper at all, but I have to say I’m baffled! It’s like it’s just ignoring the ForAllMembers -> NotAlreadyMapped, but I don’t know how to test that or have a look at its decision tree.
Oh! I’ve just run some more tests and it *is* correctly mapping those inherited classes, but it’s Mapper.AssertConfigurationIsValid();
which is failing with that error (which is a test I’d *really* like it to pass). Any idea how we can get around that?
I am glad to hear that the code is working. I wasn’t aware it was causing the AssertConfigurationIsValid to fail. I briefly looked into it but couldn’t figure out why AutoMapper doesn’t like it. I posted a new blog showing the updated code and I also posted the code on GitHub.
This is solved in AutoMapper 2.0:
https://github.com/AutoMapper/AutoMapper/wiki/Mapping-inheritance
BTW in this example only one level of inheritance is supported