Преобразование AutoMapper из нескольких источников
Скажем, у меня есть два класса моделей:
public class People {
public string FirstName {get;set;}
public string LastName {get;set;}
}
Также есть класс Телефон:
public class Phone {
public string Number {get;set;}
}
И я хочу преобразовать в PeoplePhoneDto следующим образом:
public class PeoplePhoneDto {
public string FirstName {get;set;}
public string LastName {get;set;}
public string PhoneNumber {get;set;}
}
Скажем, в моем контроллере я:
var people = repository.GetPeople(1);
var phone = repository.GetPhone(4);
// normally, without automapper I would made
return new PeoplePhoneDto(people, phone) ;
Я не могу найти пример для этого сценария. Возможно ли это?
Примечание. Пример не является реальным, только для этого вопроса.
Ответы
Ответ 1
Вы не можете напрямую сопоставить многие источники с одним пунктом назначения - вам следует применять карты по одному, как описано в Andrew Whitaker. Итак, вы должны определить все сопоставления:
Mapper.CreateMap<People, PeoplePhoneDto>();
Mapper.CreateMap<Phone, PeoplePhoneDto>()
.ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));
Затем создайте целевой объект любым из этих сопоставлений и примените другие сопоставления к созданному объекту. И этот шаг можно упростить с помощью очень простого метода расширения:
public static TDestination Map<TSource, TDestination>(
this TDestination destination, TSource source)
{
return Mapper.Map(source, destination);
}
Использование очень просто:
var dto = Mapper.Map<PeoplePhoneDto>(people)
.Map(phone);
Ответ 2
Для этого можно использовать Tuple
:
Mapper.CreateMap<Tuple<People, Phone>, PeoplePhoneDto>()
.ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.Item1.FirstName))
.ForMember(d => d.LastName, opt => opt.MapFrom(s => s.Item1.LastName))
.ForMember(d => d.Number, opt => opt.MapFrom(s => s.Item2.Number ));
Если у вас будет больше исходных моделей, вы можете использовать другое представление (List, Dictionary или что-то еще), которое соберет все эти модели вместе как источник.
Вышеприведенный код следует поместить в некоторый файл AutoMapperConfiguration, установить один раз и глобально, а затем использовать, когда это применимо.
AutoMapper по умолчанию поддерживает только один источник данных. Таким образом, нет возможности устанавливать сразу несколько источников (не обертывая их в коллекцию), потому что тогда как мы узнаем, что, если, например, у двух исходных моделей есть свойства с одинаковыми именами?
Существует хоть какое-то обходное решение для этого:
public static class EntityMapper
{
public static T Map<T>(params object[] sources) where T : class
{
if (!sources.Any())
{
return default(T);
}
var initialSource = sources[0];
var mappingResult = Map<T>(initialSource);
// Now map the remaining source objects
if (sources.Count() > 1)
{
Map(mappingResult, sources.Skip(1).ToArray());
}
return mappingResult;
}
private static void Map(object destination, params object[] sources)
{
if (!sources.Any())
{
return;
}
var destinationType = destination.GetType();
foreach (var source in sources)
{
var sourceType = source.GetType();
Mapper.Map(source, destination, sourceType, destinationType);
}
}
private static T Map<T>(object source) where T : class
{
var destinationType = typeof(T);
var sourceType = source.GetType();
var mappingResult = Mapper.Map(source, sourceType, destinationType);
return mappingResult as T;
}
}
И затем:
var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>(people, phone);
Но, честно говоря, хотя я использую AutoMapper уже несколько лет, мне никогда не приходилось использовать сопоставление из нескольких источников.
В случаях, когда, например, мне понадобилось несколько бизнес-моделей в моей модели с одним представлением, я просто встроил эти модели в класс модели представления.
Итак, в вашем случае это будет выглядеть так:
public class PeoplePhoneDto {
public People People { get; set; }
public Phone Phone { get; set; }
}