使用函数中的表达式编译 LinQ 查询
我想创建一个使用可重用 where 谓词的编译查询。举一个例子来说明这一点:
ObjectContext.Employees.Where(EmployeePredicates.CustomerPredicate)
EmployeePredicates 是一个静态类,其属性 CustomerPredicate 如下所示:
public static Expression<Func<Employee, bool>> CustomerPredicate
{
get
{
return t => t.CustomerId == 1;
}
}
这按预期工作。 然而,在大多数情况下,您希望将参数传递给表达式。为了实现这一点,我必须将该属性更改为静态函数:
public static Expression<Func<Employee, bool>> CustomerPredicate(int id)
{
return t => t.CustomerId == id;
}
我可以这样使用它:
ObjectContext.Employees.Where(EmployeePredicates.CustomerPredicate(id))
这可行,但现在是棘手的部分。我想编译这个查询... Visual Studio 不会给我任何编译错误,但是当我运行这个示例时,运行时会抛出以下异常:
Internal .NET Framework Data Provider error 1025
所以我们在同一页面上,这里是完整的代码给了我一个异常:
var _compiledQuery = CompiledQuery.Compile<AdventureWorksEntities, int, IQueryable<Employee>>(
(ctx, id) =>
(ctx.Employee.Where(EmployeePredicates.CustomerPredicate(id))
));
有谁知道为什么抛出这个异常?我采用了这种工作方式 http://www.albahari.com/nutshell/linqkit .aspx。任何帮助将不胜感激。
谢谢乔恩,
您所描述的方法的问题在于,将谓词链接在一起将变得非常困难。如果我需要将此谓词与另一个过滤具有特定名称的员工的谓词结合起来怎么办?然后你最终会得到很多选择来传递参数。
(ctx, id, name) =>
(ctx.Employee.Select(emp => new {emp, id})
.Where(EmployeePredicates.CustomerPredicate(id))
.Select(emp => new {emp, name})
.Where(EmployeePredicates.NamePredicate(name))
当您处理连接表时,情况会变得更糟。
(ctx, id, name) =>
(ctx.Employee
.Join(ctx.Contact, e=> e.ContactId, c => c.Id), (emp, cont) => new Container<Employee, Customer> {Employee = emp, Contact = cont})
.Where(EmployeePredicates.CustomerPredicate(id))
.Where(EmployeePredicates.NamePredicate(name))
.Select(t => new EmployeeDTO {Name = t.cont.Name, Customer = e.emp.Customer})
因为每个Where()都对T类型的东西进行操作并返回T类型的东西,所以上面代码中的WherePredicates必须对Container类型起作用。这使得谓词的重用变得非常困难。重用是这种方法的最初目标......
I would like to create a compiled query which uses reusable where predicates. An example to make this clear:
ObjectContext.Employees.Where(EmployeePredicates.CustomerPredicate)
EmployeePredicates is a static class with a property CustomerPredicate that looks like this:
public static Expression<Func<Employee, bool>> CustomerPredicate
{
get
{
return t => t.CustomerId == 1;
}
}
This works as expected.
In most cases however, you would like to pass a parameter to Expression. To achieve this I have to change the property to a static function:
public static Expression<Func<Employee, bool>> CustomerPredicate(int id)
{
return t => t.CustomerId == id;
}
And I can use this like this:
ObjectContext.Employees.Where(EmployeePredicates.CustomerPredicate(id))
This works, but now comes the tricky part. I would like to compile this query... Visual studio doesn't give me any compile errors, but when I run this example the following exception is thrown at runtime:
Internal .NET Framework Data Provider error 1025
Just so we're on the same page here is the full code that gives me the exception:
var _compiledQuery = CompiledQuery.Compile<AdventureWorksEntities, int, IQueryable<Employee>>(
(ctx, id) =>
(ctx.Employee.Where(EmployeePredicates.CustomerPredicate(id))
));
Does anyone have a clue why this exception is being thrown? I've taken this way of working from http://www.albahari.com/nutshell/linqkit.aspx. Any help would be much appreciated.
Thanks Jon,
The problem with the approach you describe, is that chaining predicates together will become very hard. What if I need to combine this predicate with another predicate that filters the employees with a certain name? Then you end up with a lot of selects to pass in the parameters.
(ctx, id, name) =>
(ctx.Employee.Select(emp => new {emp, id})
.Where(EmployeePredicates.CustomerPredicate(id))
.Select(emp => new {emp, name})
.Where(EmployeePredicates.NamePredicate(name))
It gets even worse when you're working on joined tables.
(ctx, id, name) =>
(ctx.Employee
.Join(ctx.Contact, e=> e.ContactId, c => c.Id), (emp, cont) => new Container<Employee, Customer> {Employee = emp, Contact = cont})
.Where(EmployeePredicates.CustomerPredicate(id))
.Where(EmployeePredicates.NamePredicate(name))
.Select(t => new EmployeeDTO {Name = t.cont.Name, Customer = e.emp.Customer})
Because each Where() operates on something of type T and returns something of type T, the WherePredicates in the code above must work on the type Container. This makes it very hard to reuse the Predicates. And reuse was the initial goal of this approach...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
问题是实体框架试图检查由 表示的表达式树,
但它不能这样做,因为它不知道 EmployeePredicates.CustomerPredicate 的作用。
至于最好的解决办法......我不确定。基本上,它必须知道在查询编译时完整的查询是什么样的,只需使用参数的占位符即可。
我怀疑最好的解决方案将涉及这样的事情:
...因为这将抽象提高了一个级别;它为您提供了一个使用
id
作为ParameterExpression
的表达式树,这是您构建适当的表达式树以调用CompileQuery
所需要的东西。代码>.不幸的是,这有点难以思考:(The problem is that the Entity Framework is trying to examine the expression tree represented by
It can't do that, because it doesn't know what
EmployeePredicates.CustomerPredicate
does.As for the best fix... I'm not sure. Basically it's got to know at query compile time what the full query looks like, just with the placeholders for parameters.
I suspect the best solution will involve something like this:
... as that raises the abstraction by one level; it gives you an expression tree which uses
id
as aParameterExpression
, which is something you'll need in order to build the appropriate expression tree to callCompileQuery
. It gets a little hard to think about, unfortunately :(