AutoMapper - отображение наследования не работает, тот же источник, несколько адресов
Могу ли я использовать сопоставление наследования в AutoMapper (v2.2) для карт с одинаковым типом источника, но с разными типами назначения?
У меня есть эта базовая ситуация (у реальных классов есть еще много свойств):
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
и ViewModelB
- разные представления одного и того же класса Entity:
public class Entity
{
public int Property1 { get; set; }
public int Property2 { get; set; }
public int Property3 { get; set; }
}
Я хочу повторно использовать одно и то же отображение для BaseViewModel
для каждого ViewModel, например:
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));
Но, к сожалению, это, похоже, не работает. Вызывается следующим образом:
var model = Mapper.Map<Entity, ViewModelA>(entity);
приведет к model
, имеющему PropertyA
, но не CommonProperty
. Я верю, что правильно следую примерам https://github.com/AutoMapper/AutoMapper/wiki/Mapping-inheritance, но я боюсь, что несколько карт, созданных с одинаковым типом источника, отключают AutoMapper вверх.
Любые идеи? Мне нравится идея группировки сопоставлений базового класса вместе, но это, похоже, не работает.
Ответы
Ответ 1
К сожалению, в этом случае AutoMapper, похоже, регистрирует только одно дочернее классное сопоставление для типа источника, последнее (ViewModelB
). Вероятно, это было предназначено для работы с параллельными иерархиями, а не с одним типом источника.
Чтобы обойти это, вы можете инкапсулировать общие сопоставления в методе расширения:
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));
}
И используйте его в отдельных сопоставлениях подклассов:
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));
Ответ 2
Возможно, здесь можно сделать
CreateMap<Entity, ViewModelA>()
.InheritMapping(x =>
{
x.IncludeDestinationBase<BaseViewModel>();
});
Существует код расширения
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)));
}
}
}