将DTO类与Expression 映射到另一个Expression

问题描述 投票:4回答:3

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

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

在DTO类中,我创建了一个Selector,它从我的实体映射到我的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
3个回答
6
投票

因此,这里我们将需要几种帮助器方法,但是一旦有了它们,事情就应该很简单。

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

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

接下来,我们将编写一个composite扩展方法。这将需要一个计算中间结果的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);
}

现在我们将创建一个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,
            });
}

0
投票

哇,这是@Servy的绝佳实现。这对我来说是一对一的关系。对我来说,目前还不清楚如何将其用于一对多关系。是否可以提供一个示例呢?


-3
投票

首先,我将仅使用单个类来执行此操作。不需要具有相同功能的DTO类。

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

© www.soinside.com 2019 - 2024. All rights reserved.