Использование AutoMapper для раскрепощения DTO
Я пытался использовать AutoMapper, чтобы сэкономить время, переходя от моих DTO к моим доменным объектам, но у меня возникают проблемы с настройкой карты, чтобы она работала, и я начинаю задумываться, может ли AutoMapper быть неподходящим инструментом для работа.
Рассмотрим этот пример объектов домена (одна сущность и одно значение):
public class Person
{
public string Name { get; set; }
public StreetAddress Address { get; set; }
}
public class StreetAddress
{
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Мой DTO (из объекта Linq-to-SQL) выглядит примерно так:
public class PersonDTO
{
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Я хотел бы иметь возможность сделать это в моем хранилище:
return Mapper.Map<PersonDTO, Person>(result);
Я пытался настроить AutoMapper всеми возможными способами, но я получаю общую конфигурацию карты пропущенных типов или ошибкунеподдерживаемого отображения, без подробных сведений о том, где я терплю неудачу.
Я пробовал несколько различных конфигураций, но вот несколько:
Mapper.CreateMap<PersonDTO, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom(Mapper.Map<Person, Domain.StreetAddress>));
а также
Mapper.CreateMap<Person, Domain.Person>()
.ForMember(dest => dest.Address.Address1, opt => opt.MapFrom(src => src.Address))
.ForMember(dest => dest.Address.City, opt => opt.MapFrom(src => src.City))
.ForMember(dest => dest.Address.State, opt => opt.MapFrom(src => src.State));
Я читал, что сглаживать объекты с помощью AutoMapper легко, но разглаживать их нелегко... или даже невозможно. Может кто-нибудь сказать мне, пытаюсь ли я сделать невозможное, и если нет, то, что я делаю неправильно?
Обратите внимание, что мои реальные объекты немного сложнее, так что возможно я опускаю информацию, которая является ключом к ошибке... если то, что я делаю, выглядит правильно, я могу предоставить больше информации или начать упрощать свои объекты для тестирования,
7 ответов
Используйте https://github.com/omuleanu/ValueInjecter, он выравнивает и разглаживает, и все, что вам нужно, в загрузке есть пример приложения asp.net mvc, где демонстрируются все функции (также модульные тесты)
Это также, кажется, работает для меня:
Mapper.CreateMap<PersonDto, Address>();
Mapper.CreateMap<PersonDto, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));
По сути, создайте отображение из dto в оба объекта, а затем используйте его в качестве источника для дочернего объекта.
В дополнение к ответу Сиднея и, согласно комментарию Тревора де Коеккока, таким образом возможно двустороннее картирование.
public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
}
public class PersonViewModel
{
public string Name { get; set; }
public string AddressStreet { get; set; }
public string AddressCity { get; set; }
public string AddressState { get; set; }
}
Сопоставления Automapper
Mapper.Initialize(cfg => cfg.RecognizePrefixes("Address"));
Mapper.CreateMap<Person, PersonViewModel>();
Mapper.CreateMap<PersonViewModel, Address>();
Mapper.CreateMap<PersonViewModel, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));
Если вы реализуете класс NameOf, вы можете избавиться от префикса волшебной строки
Mapper.Initialize(cfg => cfg.RecognizePrefixes(Nameof<Person>.Property(x => x.Address)));
РЕДАКТИРОВАТЬ: В C# 6
Mapper.Initialize(cfg => cfg.RecognizePrefixes(nameof(Person.Address)));
Не могу опубликовать комментарий, поэтому отправляю ответ. Я предполагаю, что в реализации AutoMapper произошли некоторые изменения, поэтому ответ /questions/23957331/ispolzovanie-automapper-dlya-raskreposcheniya-dto/23957350#23957350 предложенный HansoS, больше не компилируется. Хотя есть другой метод, который можно использовать в таких сценариях - ResolveUsing
:
Mapper.CreateMap<Person, Domain.Person>()
.ForMember(dest => dest.Address, opt => opt.ResolveUsing( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
Это может быть поздно, но вы можете решить эту проблему, используя лямбда-выражения для создания объекта следующим образом:
Mapper.CreateMap<Person, Domain.Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
У меня есть другое решение. Основная идея заключается в том, что AutoMapper знает, как сглаживать вложенные объекты, когда вы правильно называете свойства в сглаженном объекте: добавляя имя свойства вложенного объекта в качестве префикса. Для вашего случая адрес является префиксом:
public class PersonDTO
{
public string Name { get; set; }
public string AddressCity { get; set; }
public string AddressState { get; set; }
...
}
Таким образом, создание привычного сопоставления из вложенного в плоское и затем использование метода ReverseMap позволяет AutomMapper понять, как отсоединить вложенный объект.
CreateMap<Person, PersonDTO>()
.ReverseMap();
Это все!
Я использую это
public static void Unflatten<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
var prefix = opt.DestinationMember.Name;
var memberProps = typeof(TMember).GetProperties();
var props = typeof(TSource).GetProperties().Where(p => p.Name.StartsWith(prefix))
.Select(sourceProp => new
{
SourceProp = sourceProp,
MemberProp = memberProps.FirstOrDefault(memberProp => prefix + memberProp.Name == sourceProp.Name)
})
.Where(x => x.MemberProp != null);
var parameter = Expression.Parameter(typeof(TSource));
var bindings = props.Select(prop => Expression.Bind(prop.MemberProp, Expression.Property(parameter, prop.SourceProp)));
var resolver = Expression.Lambda<Func<TSource, TMember>>(
Expression.MemberInit(Expression.New(typeof(TMember)), bindings),
parameter);
opt.ResolveUsing(resolver.Compile());
}
конфигурация
new MapperConfiguration(cfg =>
{
cfg.CreateMap<Person, PersonDTO>();
cfg.CreateMap<PersonDTO, Person>().ForMember(x => x.HomeAddress, opt => opt.Unflatten());
});
модели
public class Person
{
public string Name { get; set; }
public Address HomeAddress { get; set; }
}
public class Address
{
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
Следуя правилам автоматического выравнивания AutoMapper
public class PersonDTO
{
public string Name { get; set; }
public string HomeAddressLine1 { get; set; }
public string HomeAddressLine2 { get; set; }
public string HomeAddressCity { get; set; }
public string HomeAddressState { get; set; }
public string HomeAddressZipCode { get; set; }
}
Наверное, нужно много улучшений, но это работает...