【问题标题】:Cleanest Way To Map Entity To DTO With Linq Select?使用 Linq Select 将实体映射到 DTO 的最简洁方法?
【发布时间】: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 可以处理嵌套集合,但对展平的支持有限。

标签: c# linq entity-framework linq-to-sql linq-to-entities


【解决方案1】:

只需使用AutoMapper

例子:

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

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

【讨论】:

【解决方案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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-17
      • 1970-01-01
      • 2018-09-13
      • 2021-07-24
      • 2021-12-31
      • 1970-01-01
      相关资源
      最近更新 更多