为什么一个查询有效而另一个查询无效?

7

我正在使用帮助方法根据用户访问权限预先过滤所有查询。

假设方法签名为:

public IQueryable<Client> GetAllClients()

为什么使用LINQ时这个代码有效:

IQueryable<Client> allItems = GetAllClients();
return (from item in allItems
where item.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)
select item).FirstOrDefault();

但不是这样的:
return (from item in GetAllClients() 
    where item.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)
    select item).FirstOrDefault();

我可以处理第一个,但由于已经离开LINQ几年了,理解这个问题的原因会很有帮助。
“不起作用”是指选项2会引发以下异常:
System.NotSupportedException类型的first chance exception在EntityFramework.SqlServer.dll中发生。
更多信息:LINQ to Entities不识别System.Linq.IQueryable`1[typename] GetAllClients()方法,并且该方法不能被转换为存储表达式。
Clients是存储在数据库中的数据类型。我正在为实体框架数据模型创建常用查询的方法,并且由于多租户设计具有由数据类型定义的安全访问权限,因此我希望在数据访问级别上进行过滤。

6
如果您能 a) 显示完整的 return 语句;b) 显示错误信息;c) 显示您正在尝试编写的方法的返回类型,那将会很有帮助。 - Jon Skeet
1
“不工作” 是指什么? - Jeroen Vannevel
2
请编辑您的问题,使其完整和完整,而不是在评论中包含此内容。另外,“Clients”是什么? - Eric J.
3
编辑您的问题,确保所有相关信息都已包含在内。说实话,我不清楚您为什么要在这里使用查询表达式,但我强烈怀疑您正在进行除所示更改以外的其他更改。请注意不改变原意,使内容更加通俗易懂。 - Jon Skeet
1
很明显问题在于它正在尝试使用Entity Framework调用“GetAllClients”...但是我可以通过仅使用那里的信息证明问题不正确(请查看https://dotnetfiddle.net/GPwJ0g),因此您应该更清楚地定义您的问题(或者至少标记它为“Entity Framework”,并说明您正在使用“DbContext”)。 - Jcl
显示剩余2条评论
2个回答

7
在这里的问题是,可查询层试图将方法调用 GetAllClients 转换为查询语句,而不是使用 GetAllClients 的返回值作为查询的源。从语法上看,这很容易混淆,但这完全是可以预料的。
这是因为 IQueryable 对象,与 IEnumerable 不同,实际上呈现了可以用于将代码(在本例中)翻译成 SQL 的元代码。由于大多数 C# 方法没有 SQL 等效项,并且编译后的方法无法像扫描元代码那样进行扫描,因此当它们遇到无法翻译的内容时,这些框架会简单地报错。
请注意,避免使用 Linq 语法,而改用方法调用是避免大部分此类问题的一种方式,这样会稍微少点误导。
return GetAllClients()
    .Where(item => item.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)
    .FirstOrDefault();

非常感谢您提供如此简洁明了的答案。我从未考虑过使用直接方法调用来解决这个问题。 - Martin Noreke
3
您所说的通常适用于WHERE或SELECT子句,但我从未见过它影响FROM。我认为这个问题还有更多细节需要考虑。 - recursive
@recursive 当我第一次想到时,linq语法经常对调用层次结构产生奇怪的影响。请参见下面另一个程序员的答案,因为他展示了结果表达式树,证实了我在答案中所做的声明。 - David

2
你的筛选方法返回了一个对象。C#编译器会将你在返回语句中的lambda表达式自动转换成表达式树的形式,这个自动转换是IQueryable整个延迟执行特性的实现方式之一。
在第一个案例中,"return item from allItems",你将得到如下表达式树:
Call: Queryable.Select(Constant: allItems, LambdaExpression: predicate)
而在第二个案例中,"return item from GetAllClients()",你将得到如下表达式树:
Call: Queryable.Select(Call: GetAllClients, LambdaExpression: predicate)
请注意,Queryable.Select的第一个参数不同!在第二个案例中,编译器通过将其存储在表达式树中来延迟调用GetAllClients方法。当该表达式树最终传递给EF的SQL翻译器时,EF不知道如何将对C#的GetAllClients函数的调用转换为有效的SQL。

非常有趣能够看到实际的构建方法调用,但请注意编译器将转换为 Expression<Func<...>>IQueryables 推迟翻译的方式,而不是 Linq 推迟执行的全部方式。 - David

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接