如何从较小的、可重复使用的查询组成实体框架查询?
我的应用程序中有一些(相当多余的)查询,如下所示:
var last30Days = DateTime.Today.AddDays(-30);
from b in Building
let issueSeverity = (from u in Users
where u.Building == b
from i in u.Issues
where i.Date > last30Days
select i.Severity).Max()
select new
{
Building = b,
IssueSeverity = issueSeverity
}
并且:
var last30Days = DateTime.Today.AddDays(-30);
from c in Countries
let issueSeverity = (from u in Users
where u.Building.Country == c
from i in u.Issues
where i.Date > last30Days
select i.Severity).Max()
select new
{
Country = c,
IssueSeverity = issueSeverity
}
当然,这是一个简化的示例。然而,要点是我需要捕获一个日期并过滤它的子查询。我还需要根据父对象以不同的方式过滤子查询。
我尝试(本质上)创建以下函数:
public IQueryable<int?> FindSeverity(Expression<Func<User, bool>> predicate)
{
var last30Days = DateTime.Today.AddDays(-30);
return from u in Users.Where(predicate)
from i in u.Issues
where i.Date > last30Days
select i.Severity;
}
按如下方式使用它:
from c in Countries
let issueSeverity = FindSeverity(u => u.Building.Country == c).Max()
select new
{
Country = c,
IssueSeverity = issueSeverity
}
这可以编译,但在运行时不起作用。实体框架抱怨 FindSeverity 函数未知。
我尝试过几种不同的表达体操方法,但都没有效果。
我需要做什么来编写可重用的实体框架查询?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我已经解决了你的问题,但没有最终令人满意的结果。我只列出我能找到并理解的几点。
1)
我重写了你的最后一个代码片段(以简化的形式,没有投影到匿名类型)...
...然后在扩展方法语法中:
现在我们更好地看到
FindSeverity( u => u.Building.Country == c).Max()
是Expression>
的主体 (在本例中,T
是int
)。 (我不确定“body”是否是正确的技术终点,但你知道我的意思:Lambda 箭头右侧的部分=>)。当整个查询被转换为表达式树时,该主体被转换为对函数FindSeverity
的方法调用。 (当您观察query
的Expression
属性时,您可以在调试器中看到这一点:FindSeverity
直接是表达式树中的一个节点,而不是此方法的主体。)执行失败,因为 LINQ to Entities 不识别此方法。在此类 lambda 表达式的主体中,您只能使用已知函数,例如静态 System.Data.Objects.EntityFunctions 类中的规范函数。2)
构建查询的可重用部分的一种可能的通用方法是编写
IQueryable
的自定义扩展方法,例如:然后您可以编写如下查询
:您可以看到,您被迫使用扩展方法语法编写查询。我没有看到在查询语法中使用此类自定义查询扩展方法的方法。
上面的示例只是创建可重用查询片段的通用模式,但它对于您问题中的特定查询并没有真正的帮助。至少我不知道如何重新编写您的 FindSeverity 方法以使其适合这种模式。
3)
我相信您的原始查询无法在 LINQ to Entities 中运行。像这样的查询...
...属于类别 “引用 a LINQ to Entities 不支持查询内的非标量变量”。 (在 LINQ to Objects 中它可以工作。)上面查询中的非标量变量是
Users
。如果Building
表不为空,则会出现异常:“无法创建 EntityType 类型的常量值。仅支持原始类型(“例如 Int32、String 和 Guid”)在这种情况下。”看起来数据库中的
User
和Building
之间存在一对多关系,但这种关联并未完全建模在您的实体中:User
具有导航属性Building
,但Building
没有Users
的集合。在这种情况下,我希望查询中出现Join
,如下所示:这不会创建提到的引用非标量变量的异常。但也许我误解了你的模型。
I've played around a bit with your problem but without a final satisfying result. I will only list the few points I could find and understand.
1)
I rewrite your last code snippet (in simplified form without the projection to an anonymous type)...
...and then in extension method syntax:
Now we see better that
FindSeverity(u => u.Building.Country == c).Max()
is the body of anExpression<Func<Country, T>>
(T
isint
in this case). (I'm not sure if "body" is the correct terminus technicus, but you know what I mean: the part right from the Lambda arrow =>). When the whole query is translated into an expression tree this body is translated as a method call to the functionFindSeverity
. (You can see this in the debugger when you watch theExpression
property ofquery
:FindSeverity
is directly a node in the expression tree, and not the body of this method.) This fails on execution because LINQ to Entities doesn't know this method. In the body of such a lambda expression you can only use known functions, for instance the canonical functions from the staticSystem.Data.Objects.EntityFunctions
class.2)
A possible general way to build reusable parts of a query is to write custom extension methods of
IQueryable<T>
, for example:Then you can write queries like:
As you can see, you are forced to write your queries in extension method syntax. I don't see a way to use such custom query extension methods in query syntax.
The example above is only a general pattern to create reusable query fragments but it doesn't really help for the specific queries in your question. At least I don't know how to reformulate your
FindSeverity
method so that it fits into this pattern.3)
I believe that your original queries cannot work in LINQ to Entities. A query like this...
...falls under the category "Referencing a non-scalar variable" inside of a query which is not supported in LINQ to Entities. (In LINQ to Objects it works.) The non-scalar variable in the query above is
Users
. If theBuilding
table is not empty an exception is expected: "Unable to create a constant value of type EntityType. Only primitive types ('such as Int32, String, and Guid') are supported in this context."It looks that you have a one-to-many relationship between
User
andBuilding
in the database but this association isn't completely modelled in your Entities:User
has a navigation propertyBuilding
butBuilding
doesn't have a collection ofUsers
. In this case I would expect aJoin
in the query, something like:This wouldn't create the mentioned exception of referencing a non-scalar variable. But perhaps I misunderstood your model.