我正在使用Entity Framework Code First并尝试从我的实体类映射到我的DTO类.但我很难搞清楚如何编写Selector.
在这个小例子中,我创建了一个Person类和一个Address类.
在DTO类中,我创建了一个Selector,它从我的Entity映射到我的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> 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 > 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.保持代码清洁并在类之间分离责任.
所以,我们在这里需要几个辅助方法,但是一旦我们拥有它们,事情应该相当简单.
我们将从这个类开始,它可以用一个表达式替换另一个表达式的所有实例:
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, }); }