Самый чистый способ создания карты для 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);
    }
}