Can I use inheritance mapping in AutoMapper (v2.2) for maps with the same Source type but different Destination types?
I have this basic situation (the real classes have many more properties):
public abstract class BaseViewModel
{
public int CommonProperty { get; set;}
}
public class ViewModelA : BaseViewModel
{
public int PropertyA { get; set; }
}
public class ViewModelB : BaseViewModel
{
public int PropertyB { get; set; }
}
ViewModelA
and ViewModelB
are different representations of the same Entity class:
public class Entity
{
public int Property1 { get; set; }
public int Property2 { get; set; }
public int Property3 { get; set; }
}
I want to reuse the same mapping for BaseViewModel
for each ViewModel, such as:
Mapper.CreateMap<Entity, BaseViewModel>()
.Include<Entity, ViewModelA>()
.Include<Entity, ViewModelB>()
.ForMember(x => x.CommonProperty, y => y.MapFrom(z => z.Property1));
Mapper.CreateMap<Entity, ViewModelA>()
.ForMember(x => x.PropertyA, y => y.MapFrom(z => z.Property2));
Mapper.CreateMap<Entity, ViewModelB>()
.ForMember(x => x.PropertyB, y => y.MapFrom(z => z.Property3));
But unfortunately, this doesn't seem to work. Calls like these:
var model = Mapper.Map<Entity, ViewModelA>(entity);
result in model
having PropertyA
mapped, but not CommonProperty
. I believe I'm following the examples in https://github.com/AutoMapper/AutoMapper/wiki/Mapping-inheritance properly, but I'm afraid having multiple maps created with the same Source type is tripping AutoMapper up.
Any insights? I love the idea of grouping Base class mappings together, but this doesn't seem to work.
Unfortunately in this case, AutoMapper seems to be registering only one child class mapping per source type, the last one (ViewModelB
). This was probably designed to work with parallel hierarchies, not with a single source type.
To work around this, you can encapsulate the common mappings in an extension method:
public static IMappingExpression<Entity, TDestination> MapBaseViewModel<TDestination>(this IMappingExpression<Entity, TDestination> map)
where TDestination : BaseViewModel {
return map.ForMember(x => x.CommonProperty, y => y.MapFrom(z => z.Property1));
}
And use it in the individual subclass mappings:
Mapper.CreateMap<Entity, ViewModelA>()
.MapBaseViewModel<ViewModelA>()
.ForMember(x => x.PropertyA, y => y.MapFrom(z => z.Property2));
Mapper.CreateMap<Entity, ViewModelB>()
.MapBaseViewModel<ViewModelB>()
.ForMember(x => x.PropertyB, y => y.MapFrom(z => z.Property3));
Yo can do like here
CreateMap<Entity, ViewModelA>()
.InheritMapping(x =>
{
x.IncludeDestinationBase<BaseViewModel>();
});
There is code of extension
public static class MapExtensions
{
public static void InheritMapping<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression,
Action<InheritMappingExpresssion<TSource, TDestination>> action)
{
InheritMappingExpresssion<TSource, TDestination> x =
new InheritMappingExpresssion<TSource, TDestination>(mappingExpression);
action(x);
x.ConditionsForAll();
}
private static bool NotAlreadyMapped(Type sourceType, Type desitnationType, ResolutionContext r, Type typeSourceCurrent, Type typeDestCurrent)
{
var result = !r.IsSourceValueNull &&
Mapper.FindTypeMapFor(sourceType, desitnationType).GetPropertyMaps().Where(
m => m.DestinationProperty.Name.Equals(r.MemberName)).Select(y => !y.IsMapped()
).All(b => b);
return result;
}
public class InheritMappingExpresssion<TSource, TDestination>
{
private readonly IMappingExpression<TSource, TDestination> _sourcExpression;
public InheritMappingExpresssion(IMappingExpression<TSource, TDestination> sourcExpression)
{
_sourcExpression = sourcExpression;
}
public void IncludeSourceBase<TSourceBase>(
bool ovverideExist = false)
{
Type sourceType = typeof (TSourceBase);
Type destinationType = typeof (TDestination);
if (!sourceType.IsAssignableFrom(typeof (TSource))) throw new NotSupportedException();
Result(sourceType, destinationType);
}
public void IncludeDestinationBase<TDestinationBase>()
{
Type sourceType = typeof (TSource);
Type destinationType = typeof (TDestinationBase);
if (!destinationType.IsAssignableFrom(typeof (TDestination))) throw new NotSupportedException();
Result(sourceType, destinationType);
}
public void IncludeBothBases<TSourceBase, TDestinatioBase>()
{
Type sourceType = typeof (TSourceBase);
Type destinationType = typeof (TDestinatioBase);
if (!sourceType.IsAssignableFrom(typeof (TSource))) throw new NotSupportedException();
if (!destinationType.IsAssignableFrom(typeof (TDestination))) throw new NotSupportedException();
Result(sourceType, destinationType);
}
internal void ConditionsForAll()
{
_sourcExpression.ForAllMembers(x => x.Condition(r => _conditions.All(c => c(r))));//указываем что все кондишены истинны
}
private List<Func<ResolutionContext, bool>> _conditions = new List<Func<ResolutionContext, bool>>();
private void Result(Type typeSource, Type typeDest)
{
_sourcExpression.BeforeMap((x, y) =>
{
Mapper.Map(x, y, typeSource, typeDest);
});
_conditions.Add((r) => NotAlreadyMapped(typeSource, typeDest, r, typeof (TSource), typeof (TDestination)));
}
}
}