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

Posted

技术标签:

【中文标题】使用 Linq Select 将实体映射到 DTO 的最简洁方法?【英文标题】:Cleanest Way To Map Entity To DTO With Linq Select? 【发布时间】:2015-06-19 06:18:01 【问题描述】:

我一直在尝试提出一种干净且可重用的方法来将实体映射到它们的 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: automapper.org 我以前使用过 AutoMapper,但我假设它必须在映射之前执行查询。在进一步查看文档后,它看起来可能与我正在寻找的内容接近 HERE 您的查询将在执行映射时执行,但如果实体中有您不感兴趣的字段,请使用 NHibernate 和 EntityFramework 都可用的Project().To&lt;&gt;。它将有效地对映射配置中指定的字段执行select 是的,Project().To&lt;&gt; 绝对是要走的路。此外,AutoMapper 可以处理嵌套集合,但对展平的支持有限。 【参考方案1】:

只需使用AutoMapper。

例子:

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

您的查询将在执行映射时执行,但如果实体中有您不感兴趣的字段,请使用 NHibernate 和 EntityFramework 都可用的Project().To&lt;&gt;。它将有效地对映射配置中指定的字段进行选择。

【讨论】:

可能会增加一些价值:rogeralsing.com/2013/12/01/…【参考方案2】:

您可以使用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 * FROM Persons 然后映射,或者这实际上会将输出 SQL 修改为仅 SELECT 扩展中包含的那些属性。【参考方案3】:

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

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

【讨论】:

一个快速评论,在最后一个例子中你不需要箭头函数var personsDTO = persons.Select(ToPersonDTOMap).ToList();【参考方案4】:

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());

【讨论】:

【参考方案5】:

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

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;

【讨论】:

以上是关于使用 Linq Select 将实体映射到 DTO 的最简洁方法?的主要内容,如果未能解决你的问题,请参考以下文章

NestJS > TypeORM 将复杂实体映射到复杂 DTO

如何将DTO映射到多个实体?

将 DTO 映射到后端实体

将验证属性从域实体映射到 DTO

如何使用 AutoMapper 将 json 请求 dto 中的 OData 枚举字符串映射到实体枚举属性

将 IQueryable where 子句从 DTO 映射到实体