我希望能够重用 LINQ to Entities 查询的“选择”部分。例如,我可以采取以下...
projectQuery.Select(p => new ProjectModel
ProjectName = p.ProjectName,
ProjectNumber = p.ProjectNumber);
并用表达式替换它...
projectQuery.Select(ProjectModel.FullSelector);
FullSelector 看起来像这样:
public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
ProjectName = p.ProjectName,
ProjectNumber = p.ProjectNumber
};
这很好用,发送到数据库的查询只选择 FullSelector 使用的字段。另外,每次我需要查询项目实体时,我都可以重用 FullSelector。
现在是棘手的部分。在执行包含导航属性的查询时,嵌套的选择器表达式不起作用。
public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
ProjectName = p.ProjectName,
ProjectNumber = p.ProjectNumber
Addresses = p.Addresses.Select(AddressModel.FullSelector);
};
这是行不通的。内部 Select 给出编译时错误“无法从用法中推断出类型参数。尝试明确指定类型参数。”
以下示例编译但在执行查询时崩溃,提示“Internal .NET Framework Data Provider error 1025。”:
public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
ProjectName = p.ProjectName,
ProjectNumber = p.ProjectNumber
Addresses = p.Addresses.Select(AddressModel.FullSelector.Compile());
};
下一个示例编译但抛出运行时错误“LINQ to Entities 无法识别方法‘EPIC.WebAPI.Models.AddressModel Invoke(EPIC.Domain.Entities.Address)’方法,并且此方法无法转换为存储表达式。”
public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
ProjectName = p.ProjectName,
ProjectNumber = p.ProjectNumber
Addresses = p.Addresses.Select(a => AddressModel.PartialSelector.Compile().Invoke(a));
};
有谁知道如何让内部选择工作?我明白为什么最后一个示例不起作用,但前两个是否接近工作?
谢谢!
最佳答案
那么,首先,为什么您的代码不起作用。第一段:
p.Addresses.Select(AddressModel.FullSelector);
这不起作用,因为导航属性没有实现 IQueryable
,它们实现了 ICollection
。 ICollection
当然没有接受 Expression
参数的 Select
方法。
第二个片段:
p.Addresses.Select(AddressModel.FullSelector.Compile());
这不起作用,因为正在编译 FullSelector
。由于正在编译,查询提供程序无法查看方法的主体并将代码转换为 SQL 代码。
第三个片段与第二个片段有完全相同的问题。将其包装在 lambda 中并不会改变这一事实。
那么,既然我们知道了为什么您的代码不起作用,现在该怎么办?
这有点令人难以置信,我不是这种方法设计的忠实粉丝,但我们开始吧。我们将编写一个方法,该方法接受表示带有一个参数的函数的表达式,然后它会接受另一个接受一些不相关类型的表达式,然后一个与我们第一个参数中的委托(delegate)具有相同类型的函数 , 然后返回一个不相关的类型。
这个方法的实现可以简单地用我们拥有的表达式替换使用的委托(delegate)参数的所有实例,然后将其全部包装在一个新的 lambda 中:
public static Expression<Func<T1, T2>> Use<T1, T2, T3, T4>(
this Expression<Func<T3, T4>> expression,
Expression<Func<T1, Func<T3, T4>, T2>> other)
{
return Expression.Lambda<Func<T1, T2>>(
other.Body.Replace(other.Parameters[1], expression),
other.Parameters[0]);
}
//another overload if there are two selectors
public static Expression<Func<T1, T2>> Use<T1, T2, T3, T4, T5, T6>(
this Expression<Func<T3, T4>> firstExpression,
Expression<Func<T5, T6>> secondExpression,
Expression<Func<T1, Func<T3, T4>, Func<T5, T6>, T2>> other)
{
return Expression.Lambda<Func<T1, T2>>(
other.Body.Replace(other.Parameters[1], firstExpression)
.Replace(other.Parameters[2], secondExpression),
other.Parameters[0]);
}
这个想法有点令人难以置信,但代码实际上很短。它依赖于此方法将一个表达式的所有实例替换为另一个表达式:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
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);
}
}
现在要调用它,我们可以在我们的地址选择器上调用 Use
,然后编写一个方法来接受我们的普通参数以及地址选择器的委托(delegate):
public static Expression<Func<Project, ProjectModel>> FullSelector =
AddressModel.FullSelector.Use((Project project,
Func<Address, AddressModel> selector) => new ProjectModel
{
ProjectName = project.ProjectName,
ProjectNumber = project.ProjectNumber,
Addresses = project.Addresses.Select(selector),
});
现在这将完全按照预期工作。
关于c# - 在 LINQ to entities 查询的 "Select"部分使用表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23658031/