C# 使用表达式将DTO映射到类<;Func>;在另一个表达式中<;Func>;

C# 使用表达式将DTO映射到类<;Func>;在另一个表达式中<;Func>;,c#,.net,entity-framework,dto,C#,.net,Entity Framework,Dto,我首先使用实体框架代码,并尝试从实体类映射到DTO类。但是我很难弄清楚如何编写选择器 在这个小示例中,我创建了一个Person类和一个Address类 在DTO类中,我创建了一个选择器,它从我的实体映射到我的DTO,但是不能在PersonDto.Selector中使用AddressDto.Selector吗 public class Person { public int Id { get; set; } public string Name { get;

我首先使用实体框架代码,并尝试从实体类映射到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; }
        }

但是我正在寻找一种方法来重用AddressDto类中的选择器。为了保持代码干净,并在类之间划分责任。

首先,我只使用一个类来完成这项工作。不需要DTO类来做同样的事情


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

因此,我们需要几个助手方法,但是一旦我们有了它们,事情应该相当简单

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

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返回的值并返回最终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);
}
好了,现在我们有了所有这些,我们可以使用它了。我们要做的第一件事是创建一个静态构造函数;我们将无法将所有必须执行的操作内联到字段初始值设定项中。(另一个选项是创建一个静态方法来计算它,并让初始值设定项调用它。)

之后,我们将创建一个表达式,该表达式接受一个人并返回其地址。它是你所拥有的表达式中缺失的拼图块之一。使用它,我们将把地址选择器和
AddressDto
选择器组合起来,然后在上面使用
Combine
。使用它,我们有一个lambda,它接受一个
Person
和一个
AddressDTO
,并返回一个
PersonDTO
。因此,在这里,我们基本上得到了您想要的,但是通过给我们一个
地址
参数来分配给地址:

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,
            });
}
static PersonDto()
{
表达式地址选择器=
person=>person.Address;
选择器=addressSelector.Compose(AddressDto.Selector)
.Combine((实体、地址)=>新联系人
{
Id=entity.Id,
Name=entity.Name,
地址,
});
}
Microsoft的官方文档(尽管是由一个人编写的)在此处列出了将结果转换为DTO的表达式方法:。我不明白为什么微软还没有一种内置复杂DTO的方法,我们必须用这些额外的方法(re:hack)来实现这一点。当然,这不可能是一个独特的用户案例。@nzondlo好吧,任何一种“自动”解决方案都可能是一个黑客。它需要对它认为正在发生的事情进行猜测和判断。通过手动操作,您可以确保它是正确的,因此它的黑客较少。一旦你建立了一些操纵表达式的基本工具,这也就不难了。(我确实希望其中一些方法可以在基类库中;我几乎从不回答与
表达式
相关的问题,除非在我自己对
Replace
的定义中附加内容)这些帮助器方法是有意编写的,以便可重用。也许我想的更多的是关于你对我的问题的回答,这个答案非常相似。我看不出该功能如何不能构建到基于Linq到实体方法的API中
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);
}
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,
            });
}