【问题标题】:Mapping DTO classes with Expression<Func> inside another Expression<Func>在另一个 Expression<Func> 中使用 Expression<Func> 映射 DTO 类
【发布时间】:2013-12-05 20:22:04
【问题描述】:

我正在使用 Entity Framework Code First 并尝试从我的实体类映射到我的 DTO 类。但是我很难弄清楚如何编写选择器。

在这个小例子中,我创建了一个 Person 类和一个 Address 类。

在 DTO 类中,我创建了一个选择器,它从我的实体映射到我的 DTO,但是不能在 PersonDto.Selector 中使用 AddressDto.Selector 吗?

public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Address Address { get; set; }
    }

    public class Address
    {
        public int Id { get; set; }
        public string Street { get; set; }
    }

现在我正在尝试将其映射到 DTO 类。

    public class PersonDto
        {
            public static Expression<Func<Person, PersonDto>> Selector = 
            entity => new PersonDto
                {
                     Id = entity.Id,
                     Name = entity.Name,
                     Address = ??? AddressDTO.Selector
                };

            public int Id { get; set; }
            public string Name { get; set; }
            public AddressDto Address { get; set; }
        }

        public class AddressDto
        {
            public static Expression<Func<Address, AddressDto>> Selector = 
            entity => new AddressDto
                 {
                     Id = entity.Id,
                     Street = entity.Street
                 };

            public int Id { get; set; }
            public string Street { get; set; }
        }

我知道我可以在 PersonDto.Selector 中写这个

Address = new AddressDto
                     {
                         Id = entity.Address.Id,
                         Street = entity.Address.Street
                     };

但我正在寻找一种方法来重用 AddressDto 类中的 Selector。保持代码整洁并在类之间分离职责。

【问题讨论】:

    标签: c# .net entity-framework dto


    【解决方案1】:

    所以,我们在这里需要几个辅助方法,但是一旦有了它们,事情就应该相当简单了。

    我们将从这个类开始,它可以用另一个表达式替换一个表达式的所有实例:

    internal class ReplaceVisitor : ExpressionVisitor
    {
        private readonly Expression from, to;
        public ReplaceVisitor(Expression from, Expression to)
        {
            this.from = from;
            this.to = to;
        }
        public override Expression Visit(Expression node)
        {
            return node == from ? to : base.Visit(node);
        }
    }
    

    然后是一个扩展方法,让调用更简单:

    public static Expression Replace(this Expression expression,
        Expression searchEx, Expression replaceEx)
    {
        return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
    }
    

    接下来我们将编写一个 compose 扩展方法。这将采用一个计算中间结果的 lambda,然后是另一个基于中间结果计算最终结果的 lambda,并返回一个新的 lambda,它采用初始 lambda 返回的内容并返回最终 lambda 的输出。实际上,它调用一个函数,然后根据第一个函数的结果调用另一个函数,但使用的是表达式,而不是方法。

    public static Expression<Func<TFirstParam, TResult>>
        Compose<TFirstParam, TIntermediate, TResult>(
        this Expression<Func<TFirstParam, TIntermediate>> first,
        Expression<Func<TIntermediate, TResult>> second)
    {
        var param = Expression.Parameter(typeof(TFirstParam), "param");
    
        var newFirst = first.Body.Replace(first.Parameters[0], param);
        var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
    
        return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
    }
    

    现在我们将创建一个Combine 方法。这将是相似的,但略有不同。它需要一个计算中间结果的 lambda,以及一个同时使用初始输入和中间输入的函数来计算最终结果。和Compose方法基本一样,但是第二个函数也知道第一个参数:

    public static Expression<Func<TFirstParam, TResult>>
        Combine<TFirstParam, TIntermediate, TResult>(
        this Expression<Func<TFirstParam, TIntermediate>> first,
        Expression<Func<TFirstParam, TIntermediate, TResult>> second)
    {
        var param = Expression.Parameter(typeof(TFirstParam), "param");
    
        var newFirst = first.Body.Replace(first.Parameters[0], param);
        var newSecond = second.Body.Replace(second.Parameters[0], param)
            .Replace(second.Parameters[1], newFirst);
    
        return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
    }
    

    好的,既然我们拥有了所有这些,我们就可以使用它了。我们要做的第一件事是创建一个静态构造函数;我们将无法将我们必须做的所有事情内联到字段初始化程序中。 (另一种选择是创建一个静态方法来计算它,并让初始化程序调用它。)

    之后,我们将创建一个表达式,接收一个人并返回其地址。这是您所拥有的表达式中缺少的拼图之一。使用它,我们将使用AddressDto 选择器组合该地址选择器,然后在其上使用Combine。使用它我们有一个 lambda,它接受一个 Person 和一个 AddressDTO 并返回一个 PersonDTO。所以在那里我们基本上有你想要的,但是有一个address参数给我们分配给地址:

    static PersonDto()
    {
        Expression<Func<Person, Address>> addressSelector =
            person => person.Address;
    
        Selector = addressSelector.Compose(AddressDto.Selector)
                .Combine((entity, address) => new PersonDto
                {
                    Id = entity.Id,
                    Name = entity.Name,
                    Address = address,
                });
    }
    

    【讨论】:

    • Microsoft 的官方文档(尽管由一个人编写)在此处列出了用于将结果转换为 DTO 的 Expression> 方法:asp.net/web-api/overview/web-api-routing-and-actions/…。我不明白为什么微软还没有一种内置方式来制作复杂的 DTO,我们必须制作这些额外的方法(re: hack)来做到这一点。当然,这不可能是一个独特的用户案例。
    • @nzondlo 好吧,任何类型的“自动”解决方案都可能需要破解。它需要对其认为正在发生的事情进行猜测和判断。通过手动操作,您可以确定它是正确的,因此它不那么hacky。一旦您建立了一些用于操作表达式的基本工具,这也不是那么难。 (我确实希望其中的一些可以在基类库中;我几乎从不回答Expression 相关的问题,而不附加我自己对Replace 的定义。)这些辅助方法是有意编写的可重复使用。
    • stackoverflow.com/questions/20253205/… 也许我想更多的是参考您对我的问题给出的非常相似的答案。我看不出该功能如何无法内置到基于 Linq-To-Entity 方法的 API 中
    【解决方案2】:

    首先,我将只使用一个类来执行此操作。不需要做同样事情的 DTO 类。

    如果您坚持使用辅助 DTO 类,那么我会简单地为它们创建扩展方法。例如:PersonDTO personDto = myPerson.ToDTO()

    【讨论】:

      猜你喜欢
      • 2019-01-11
      • 1970-01-01
      • 1970-01-01
      • 2012-05-29
      • 1970-01-01
      • 1970-01-01
      • 2021-08-25
      • 2019-09-04
      • 1970-01-01
      相关资源
      最近更新 更多