Самый чистый способ создания карты для DTO с помощью Linq Select?
Я пытался придумать чистый и многоразовый способ сопоставления объектов с их DTO. Вот пример того, что я придумал, и где я застрял.
Объекты
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
// Other properties not included in DTO
}
public class Address
{
public int ID { get; set; }
public string City { get; set; }
// Other properties not included in DTO
}
DTOS
public class PersonDTO
{
public int ID { get; set; }
public string Name { get; set; }
public AddressDTO Address { get; set; }
}
public class AddressDTO
{
public int ID { get; set; }
public string City { get; set; }
}
Выражения
Вот как я начал обрабатывать отображение. Мне нужно решение, которое не будет выполнять запрос перед сопоставлением. Мне сказали, что если вы передадите Func<in, out>
вместо Expression<Func<in, out>>
, он выполнит запрос перед сопоставлением.
public static Expressions
{
public static Expression<Func<Person, PersonDTO>> = (person) => new PersonDTO()
{
ID = person.ID,
Name = person.Name,
Address = new AddressDTO()
{
ID = person.Address.ID,
City = person.Address.City
}
}
}
Одна из проблем заключается в том, что у меня уже есть выражение, которое отображает Address
в AddressDTO
, поэтому у меня есть дублированный код. Это также будет нарушено, если person.Address
равно null. Это очень неприятно, особенно если я хочу отображать другие объекты, связанные с человеком в этом же DTO. Он становится гнездом птиц вложенных отображений.
Я пробовал следующее, но Linq не знает, как его обрабатывать.
public static Expressions
{
public static Expression<Func<Person, PersonDTO>> = (person) => new PersonDTO()
{
ID = person.ID,
Name = person.Name,
Address = Convert(person.Address)
}
public static AddressDTO Convert(Address source)
{
if (source == null) return null;
return new AddressDTO()
{
ID = source.ID,
City = source.City
}
}
}
Есть ли какие-то элегантные решения, которые мне не хватает?
Ответы
Ответ 1
Просто используйте AutoMapper.
Пример:
Mapper.CreateMap<Address, AddressDTO>();
Mapper.CreateMap<Person, PersonDTO>();
Ваш запрос будет выполняться при выполнении сопоставления, но если в объекте, который вам неинтересен, есть поля Project().To<>
, которые доступны как для NHibernate, так и для EntityFramework. Он эффективно выполнит выбор в полях, указанных в конфигурациях сопоставления.
Ответ 2
Если вы хотите вручную создавать сопоставления, вы можете использовать Select в коллекции следующим образом:
Некоторые тестовые данные:
var persons = new List<Person>
{
new Person() {ID = 1, Name = "name1", Address = new Address() {ID = 1, City = "city1"}},
new Person() {ID = 2, Name = "name2", Address = new Address() {ID = 2, City = "city2"}},
new Person() {ID = 3, Name = "name3", Address = new Address() {ID = 1, City = "city1"}}
};
Методы сопоставления:
public static PersonDTO ToPersonDTOMap(Person person)
{
return new PersonDTO()
{
ID = person.ID,
Name = person.Name,
Address = ToAddressDTOMap(person.Address)
};
}
public static AddressDTO ToAddressDTOMap(Address address)
{
return new AddressDTO()
{
ID = address.ID,
City = address.City
};
}
Фактическое использование:
var personsDTO = persons.Select(x => ToPersonDTOMap(x)).ToList();
Имейте в виду, что если это был реальный запрос, он не будет выполнен, если он был IQueryable, он будет выполнен после его материализации (например, с помощью ToList()).
Однако я бы подумал об использовании некоторой структуры, которая могла бы сделать это (сопоставления) для вас автоматически (если ваше сопоставление так же просто, как приведенный пример (.
Ответ 3
Вы можете использовать AutoMapper или написать такие методы расширения, как эти:
public static class PersonMapper
{
public static PersonDTO ConvertToDTO(this Person person)
{
return new PersonDTO { ID = person.ID, Name = person.Name, Address = person.Address.ConvertToDTO() };
}
public static IEnumerable<PersonDTO> ConvertToDTO(this IEnumerable<Person> people)
{
return people.Select(person => person.ConvertToDTO());
}
}
public static class AddressMapper
{
public static AddressDTO ConvertToDTO(this Address address)
{
return new AddressDTO { ID = address.ID, City = address.City };
}
public static IEnumerable<AddressDTO> ConvertToDTO(this IEnumerable<Address> addresses)
{
return addresses.Select(address => address.ConvertToDTO());
}
}
Затем вы можете сопоставить объект Person
с объектом PersonDTO
следующим образом:
public class Program
{
static void Main(string[] args)
{
Person person = new Person { ID = 1, Name = "John", Address = new Address { ID = 1, City = "New Jersey" } };
PersonDTO personDTO = person.ConvertToDTO();
Console.WriteLine(personDTO.Name);
}
}