我正在使用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<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。保持代码清洁并在类之间分离责任。
答案 0 :(得分: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);
}
接下来我们将编写一个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,
});
}
答案 1 :(得分:-3)
首先,我只会使用一个类来执行此操作。不需要做同样事情的DTO类。
如果您坚持使用辅助DTO课程,那么我只需为它们创建扩展方法。例如:PersonDTO personDto = myPerson.ToDTO()