所以我正在与一些LINQ(实体框架)lambda进行斗争,我最终通过将结果存储在变量中来解决这个问题。类似的东西:
IQueryable<object> DoStuff() { return /* some linq query */; }
void Main()
{
var result = DoStuff();
// (_database is DbContext) == true
// Works fine
_database.Table.Where(x => result.Contains(x.Id));
// Throws "LINQ to Entities does not recognize the method '[...] DoStuff()' method, and this method cannot be translated into a store expression."
_database.Table.Where(x => DoStuff().Contains(x.Id));
}
我能够通过将查询存储在单独的变量中来解决我的问题(偶然),但我很好奇为什么有效?这是延迟执行的事吗?在此之前,我一直认为两个可交换的方法(方法与中间变量用法)用于引用类型,但显然它们不是。
答案 0 :(得分:9)
在LINQ to SQL,EF等中,Where()
方法中的lambda在执行之前会转换为SQL查询。如果使用自己的方法,则此转换将失败,因为SQL中不知道方法DoStuff()
。如果你在LINQ lambda“外部”得到结果,那么SQL查询是用静态值创建的,因此有效......
答案 1 :(得分:6)
当然 - 这是了解lambda表达式会发生什么的问题。
对于LINQ to SQL,EF等,它被转换为expression tree - 基本上代表lambda表达式中代码的数据。然后,LINQ提供程序必须将该数据转换为SQL。如何将DoStuff()
的调用转换为SQL?你不能,因为DoStuff()
在数据库中不存在,并且提供者不知道该方法在SQL本身中尝试模拟它的方法。 做工作的非常有限的一组方法调用实际上是硬编码到LINQ提供程序中,以及转换为SQL。
在理论中,一个特定的智能LINQ提供程序可以发现对DoQuery
的调用,分析其中的IL以确定它的作用,并检查它是否理解其中的所有内容,并将其转换为SQL。但是,我并不知道任何能够做到这一点的LINQ提供商,并且我很惊讶地看到一个能够可靠地完成它的人,包括DoStuff
内的进一步方法调用。这将是一项庞大的工作量,你无论如何都不希望它在执行时发生。 (当然,将它视为概念证明真是太棒了......)
现在,即使使用LINQ to Objects,您的两段代码之间也会有不同的行为。在工作代码中,DoStuff()
执行一次,然后结果用于与序列中的每个元素进行比较。在失败的代码中,您需要在序列中按每个元素调用DoStuff()
。它仍然可以工作,但你可能会不必要地多次执行查询。请注意,此处lambda表达式的转换是不同的 - 编译器将使用委托转换而不是表达式树转换。
在您的特定情况下,可能会有更多事情发生 - 因为您的方法返回IQueryable<object>
,很可能从该方法返回的内容甚至没有作为查询执行 - 但假设它是针对具有相同提供程序的同一数据库的LINQ查询,LINQ提供程序将&#34;理解&#34; DoStuff()
返回的查询,并计算出一个SQL查询,包括DoStufF()
中的查询部分和Main
中的查询部分。查看正在执行的SQL的日志以验证这一点。