IQueryable OrderBy with Func :发生了什么
使用此问题给出的代码, 在传递选择器函数时,OrderBy不会转换为SQL
Func f = x => x.Name; var t = db.Table1.OrderBy(f).ToList();
翻译的SQL是:
SELECT [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name] FROM [dbo].[Table1] AS [Extent1]
好。
我可以理解代码编译: IQueryable
inheritance自IEnumerable
,它有一个OrderBy方法,以Func
作为参数。
我可以理解,ORDER BY子句不是在SQL中生成的,因为我们没有将Expression<Func>
作为OrderBy参数传递(IQueryable的参数)
但是场景背后会发生什么? “错误的”OrderBy方法会发生什么? 没有 ? 我无法看到如何以及为什么……我夜晚的任何光明?
因为f
是委托而不是表达式,所以编译器选择IEnumerable
OrderBy
扩展方法而不是IQueryable
方法。
这意味着所有结果都是从数据库中获取的,因为然后在内存中完成排序,就像它是Linq to Objects一样。 也就是说,在内存中,只能通过获取所有记录来完成排序。
当然,实际上直到你开始枚举结果时才会发生这种情况 – 在你的情况下,你直接做了因为你在调用ToList()
急切加载结果。
更新以回应您的评论
从引入歧义的角度来看,你的问题似乎与IQueryable
/ IEnumerable
二元性一样“危险”。 它真的不是:
t.OrderBy(r => r.Field)
C#首先将lambda视为Expression<>
,因此如果t
是IQueryable
则选择IQueryable
扩展方法。 它与传递给带有string
和object
重载的重载方法的string
变量相同 – 将使用string
版本,因为它是最佳表示 。
正如Jeppe指出的那样,实际上是因为在inheritance接口之前使用了直接接口
t.AsEnumerable().OrderBy(r => r.Field)
C#不能再看到IQueryable
,所以将lambda视为Func
,因为这是它的下一个最佳表示。 (相当于之前我的string
/ object
类比中只有一个object
方法可用。
然后最后你的例子:
Func f = r => r.Field; t.OrderBy(f);
编写此代码的开发人员不可能期望将其视为较低级别组件转换为SQL的表达式,除非开发人员从根本上不理解委托与表达式之间的区别 。 如果是这种情况,那么一点点阅读就能解决问题。
我认为在开始使用新技术之前要求开发人员进行一些阅读是不合理的。 尤其是在MSDN的辩护中,这个特定的主题得到了很好的报道。
我现在意识到通过添加这个编辑,我现在已经使@IanNewson的注释无效 – 但我希望它提供一个有意义的引人注目的论点:)
但是场景背后会发生什么?
假设db.Table1
返回Table
,编译器将:
- 检查
Table
是否具有OrderBy
方法 – nope - 检查它实现的任何基类或接口是否具有
OrderBy
方法 – nope - 开始查看扩展方法
它将查找Queryable.OrderBy
和Enumerable.OrderBy
作为与目标类型匹配的扩展方法,但Queryable.OrderBy
方法不适用,因此它使用Enumerable.OrderBy
。
因此,您可以将其视为编译器已将代码重写为:
List t = Enumerable.ToList(Enumerable.OrderBy(db.Table1, f));
现在在执行时, Enumerable.OrderBy
将遍历其源( db.Table1
)并基于密钥提取function执行适当的排序。 (严格来说,它会立即返回一个IEnumerable
,当它被要求提供第一个结果时会迭代它。)
queryable返回所有记录(因此SQL语句中没有WHERE子句),然后通过Enumerable.OrderBy
将Func应用于客户端内存中的对象。 更具体地说,OrderBy调用解析为Enumerable.OrderBy,因为参数是Func。 因此,您可以使用静态方法调用语法重写语句,以使其更清楚地发生了什么:
Func f = x => x.Name; var t = Enumerable.OrderBy(db.Table1, f).ToList();
最终结果是OrderBy指定的排序由客户端进程而不是数据库服务器完成。
这个答案是对Andras Zoltan的答案的一种评论(但是这个评论格式太长了)。
Zoltan的答案很有趣,而且大多数都是正确的,除了短语C#将lambda视为Expression<>
首先[…] 。
C#将lambda(和任何匿名函数)视为与委托和同一委托的Expression<>
(表达式树)同等“接近”。 根据C#规范,两者都不是“更好的转换目标” 。
所以请考虑以下代码:
class C { public void Overloaded(Expression> e) { Console.WriteLine("expression tree"); } public void Overloaded(Func d) { Console.WriteLine("delegate"); } }
然后:
var c = new C(); c.Overloaded(i => i + 1); // will not compile! "The call is ambiguous ..."
所以它与IQueryable<>
一起使用的原因是另外的。 直接接口类型定义的方法优于基接口中定义的方法。
为了说明,将上面的代码更改为:
interface IBase { void Overloaded(Expression> e); } interface IDerived : IBase { void Overloaded(Func d); } class C : IDerived { public void Overloaded(Expression> e) { Console.WriteLine("expression tree"); } public void Overloaded(Func d) { Console.WriteLine("delegate"); } }
然后:
IDerived x = new C(); x.Overloaded(i => i + 1); // compiles! At runtime, writes "delegate" to the console
如您所见,选择IDerived
定义的成员,而不是IBase
定义的成员。 请注意,我改变了这种情况(与IQueryable<>
相比),因此在我的示例中,委托重载在最派生的接口中定义,因此优先于表达式树重载。
注意:在IQueryable<>
情况下,有问题的OrderBy
方法不是普通的实例方法。 相反,一个是派生接口的扩展方法,另一个是基接口的扩展方法。 但解释是类似的。