使用 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<>
。它将有效地对映射配置中指定的字段执行select
。
是的,Project().To<>
绝对是要走的路。此外,AutoMapper 可以处理嵌套集合,但对展平的支持有限。
【参考方案1】:
只需使用AutoMapper。
例子:
Mapper.CreateMap<Address, AddressDTO>();
Mapper.CreateMap<Person, PersonDTO>();
您的查询将在执行映射时执行,但如果实体中有您不感兴趣的字段,请使用 NHibernate 和 EntityFramework 都可用的
Project().To<>
。它将有效地对映射配置中指定的字段进行选择。
【讨论】:
可能会增加一些价值: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