Automapper сглаживает несколько сложных объектов, используя пользовательское отображение
Поэтому у меня есть кое-что помимо обычного DTO для бизнес-картографа, и я пытаюсь сопоставить их с минимальным количеством кода преобразования.
Настроить
public class Target {
public string propA { get; set; }
public string propB { get; set; }
public string propC { get; set; }
public string propD { get; set; }
public string propE { get; set; }
public List<KeyValuePair> Tokens { get; set; }
}
public class Source {
public SomeClass SomeClass { get; set; }
public AnotherClass AnotherClass { get; set; }
}
public class SomeClass {
public string propA { get; set; }
public string propB { get; set; }
public string propDifferent { get; set; }
public List<KeyValuePair> Tokens { get; set; }
}
public class AnotherClass {
public string propC { get; set; }
public string propD { get; set; }
public List<KeyValuePair> Tokens { get; set; }
}
Mapper Config
Mapper.CreateMap<SomeClass, Target>()
.ForMember(dest => dest.propE, opt => opt.MapFrom(src => src.propDifferent));
Mapper.CreateMap<AnotherClass, Target>();
Mapper.CreateMap<Source, Target>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.SomeClass))
.ForMember(dest => dest, opt => opt.MapFrom(src => src.AnotherClass));
Делать это бросает
Ошибка: AutoMapper.AutoMapperConfigurationException: пользовательская конфигурация для элементов поддерживается только для отдельных элементов верхнего уровня в типе.
И мне тоже нужно взять AnotherClass.Tokens
, SomeClass.Tokens
и добавить его в Target.Tokens
,
Я знаю, что могу использовать .ConvertUsing
но затем я должен определить сопоставление для каждого свойства, и я теряю преимущество сопоставления на основе соглашения для сопоставления свойств.
Есть ли другой способ достижения этого (кроме .ConvertUsing
или сопоставление каждого свойства вручную)?
Если это не через Automapper, возможно ли это через EmitMapper? Я думаю, что добавление в список токенов возможно с помощью EmitMapper's PostProcessing
,
Обновить
После небольшого взлома я нашел способ:
public static IMappingExpression<TSource, TDestination> FlattenNested<TSource, TNestedSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TNestedSource);
var destinationType = typeof(TDestination);
var sourceProperties = sourceType.GetProperties().ToDictionary(x => x.Name.ToLowerInvariant());
var childPropName = typeof (TSource).GetProperties().First(x => x.PropertyType == sourceType).Name;
var mappableProperties = destinationType.GetProperties()
.Where(p => sourceProperties.ContainsKey(p.Name.ToLowerInvariant()) &&
sourceProperties[p.Name.ToLowerInvariant()].PropertyType ==
p.PropertyType)
.Select(p => new {DestProperty = p.Name, SrcProperty = sourceProperties[p.Name.ToLowerInvariant()].Name});
foreach (var property in mappableProperties)
{
expression.ForMember(property.DestProperty,
opt => opt.MapFrom(src => src.GetPropertyValue(childPropName).GetPropertyValue(property.SrcProperty)));
}
return expression;
}
Примечание: я делаю Name.ToLowerInvariant()
быть в состоянии соответствовать AccountID
-> AccountId
и тому подобное.
использование
AutoMapper.Mapper.CreateMap<Source, Target>()
.FlattenNested<Source, SomeClass, Target>()
.FlattenNested<Source, AnotherClass, Target>()
.ForMember(dest => dest.propE, opt => opt.MapFrom(src => src.propDifferent));
Я заметил некоторые другие свойства в IMappingExpression
что я могу быть в состоянии использовать и очистить многое из этого. Буду обновлять по мере их нахождения.
2 ответа
Вот так я решил похожую проблему:
public static IMappingExpression<TSource, TDestination> FlattenNested<TSource, TNestedSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression,
Expression<Func<TSource, TNestedSource>> nestedSelector,
IMappingExpression<TNestedSource, TDestination> nestedMappingExpression)
{
var dstProperties = typeof(TDestination).GetProperties().Select(p => p.Name);
var flattenedMappings = nestedMappingExpression.TypeMap.GetPropertyMaps()
.Where(pm => pm.IsMapped() && !pm.IsIgnored())
.ToDictionary(pm => pm.DestinationProperty.Name,
pm => Expression.Lambda(
Expression.MakeMemberAccess(nestedSelector.Body, pm.SourceMember),
nestedSelector.Parameters[0]));
foreach (var property in dstProperties)
{
if (!flattenedMappings.ContainsKey(property))
continue;
expression.ForMember(property, opt => opt.MapFrom((dynamic)flattenedMappings[property]));
}
return expression;
}
использование
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
public string Street { get; set; }
}
public class CustomerDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string Street { get; set; }
}
public class CustomerProfile : Profile
{
protected override void Configure()
{
var nestedMap = CreateMap<Address, CustomerDto>()
.IgnoreAllNonExisting();
CreateMap<Customer, CustomerDto>()
.FlattenNested(s => s.Address, nestedMap);
}
}
[TestFixture]
public class CustomerProfileTests
{
[Test]
public void Test()
{
Mapper.Initialize(c => c.AddProfile<CustomerProfile>());
Mapper.AssertConfigurationIsValid();
}
}
IgnoreAllNonExisting()
нашел здесь.
Хотя это не универсальное решение, этого должно быть достаточно для простых случаев.
Преимущества:
- Вы используете AutoMapper для создания вложенной карты, поэтому вы полагаетесь на надежный код, а также вы можете использовать такие вещи, как
RecognizePrefixes
и так далее. - Поскольку вам необходимо указать вложенный селектор свойств, вы избегаете возможной неоднозначности, когда у вас есть несколько вложенных свойств одного типа.
Вы хотите использовать BeforeMap для создания экземпляра объекта:
ОБНОВИТЬ:
Mapper.CreateMap<Source, Target>()
.BeforeMap(( Source, Target) => {
Source.SomeClass = new SomeClass();
Source.AnotherClass = new AnotherClass();
})
.AfterMap(( Source, Target) => {
Target.SomeClass = Mapper.Map<AnotherClass, Target>(Target);
Target.AnotherClass = Mapper.Map<SomeClass, Target>(Target);
})
Это позволит вам сопоставить родительский элемент перед сопоставлением свойств отдельных объектов.
Я думаю, что я теряюсь в именах вашего базового класса, но вы можете вызвать свойства mapper.Map для сопоставления объектов.
ОБНОВЛЕНИЕ 2:
На основании этого кода:
Mapper.CreateMap<Source, Target>()
.ForMember(dest => **dest**, opt => opt.MapFrom(src => src.SomeClass))
.ForMember(dest => **dest**, opt => opt.MapFrom(src => src.AnotherClass));
Дест там пытается разрешить объект. Если вы хотите разрешить только свойства этих объектов, я бы предложил вам указать их.
Mapper.CreateMap<Source, Target>()
.ForMember(dest => dest.propA, opt => opt.MapFrom(src => src.SomeClass.propA
.ForMember(dest => dest.propB, opt => opt.MapFrom(src => src.SomeClass.propB
.ForMember(dest => dest.propC, opt => opt.MapFrom(src => src.AnotherClass.propC
.ForMember(dest => dest.propD, opt => opt.MapFrom(src => src.AnotherClass.propD