使用 Linq Select 将实体映射到 DTO 的最简洁方法?

Cleanest Way To Map Entity To DTO With 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
}

DTO

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 为空,这也会中断。这很快就会变得混乱,特别是如果我想在同一个 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
        }
    }
}

有没有我遗漏的优雅解决方案?

只需使用AutoMapper

示例:

Mapper.CreateMap<Address, AddressDTO>();
Mapper.CreateMap<Person, PersonDTO>();

Your query will execute when the mapping is performed but if there are fields in the entity that you're not interested use Project().To<> which is available both for NHibernate and EntityFramework. It will effectively do a select on the fields specified in the mapping configurations.

您可以使用 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);
    }
}

如果您想手动创建映射,则可以按以下方式在集合上使用 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())。

但是,我会考虑使用一些可以自动为您完成(映射)的框架(如果您的映射像提供的示例一样简单(。

Automapper 是最好的方法。

对我来说,我只将它用于简单的对象,但我不推荐它

  public static class ObjectMapper
{
    public static T Map<T>(object objfrom, T objto)
    {
        var ToProperties = objto.GetType().GetProperties();
        var FromProperties = objfrom.GetType().GetProperties();

        ToProperties.ToList().ForEach(o =>
            {
                var fromp = FromProperties.FirstOrDefault(x => x.Name == o.Name && x.PropertyType == o.PropertyType);
                if (fromp != null)
                {
                    o.SetValue(objto, fromp.GetValue(objfrom));
                }
            });

        return objto;
    }
}

我想怎么叫就怎么叫

   var myDTO= ObjectMapper.Map(MyObject, new MyObjectDTO());

我明白你想要的方式了。我可以向您推荐这个解决方案:

public class PersonDTO
{
    public int ID { get; set; }
    public string Name { get; set; }
    public AddressDTO Address { get; set; }

    public static Expression<Func<Entities.Person, PersonDTO>> PersonSelector
    {
        get
        {
            return person => new PersonDTO()
            {
                ID = x.Id,
                Name = x.Name,
                Address = x.Address
                  .Select(AddressDTO.AddressSelector)
            };
        }
    }
}



public async Task<PersonDTO> GetPerson(int id)
{
    var person = await _personRepository.Get(id, PersonDTO.PersonSelector);
    return person;
}

public async Task<TResult> Get<TResult>(int id, Expression<Func<Person, TResult>> selector)
{
    var result = await _context.Persons
        .Where(x => x.Id == id)
        .Select(selector)
        .SingleOrDefaultAsync();

    return result;
}