【发布时间】:2020-04-28 09:04:20
【问题描述】:
考虑以下片段:
IQueryable<Person> pQ = from p in db.People select p;
IEnumerable<Person> pE = pQ;
Console.WriteLine(pQ.Count());
Console.WriteLine(pE.Count());
两者都给出相同的结果,但如果您跟踪生成的 SQL,IQueryable 版本使用COUNT 发出查询,而IEnumerable 版本只发出SELECT,拉入所有行并执行在内存中计数,这可能是非常低效的。这同样适用于其他方法,例如 Sum() 和 Average(),并且在 EF6 和 EF Core 3 中都会出现。
原因是Count() 是一个扩展方法,因此绑定到变量的静态类型(第一种情况下为IQueryable,第二种情况下为IEnumerable)而不是动态类型(两者都相同)情况,因为它是同一个对象)。
这是一个非常讨厌的问题,这意味着在这种情况下通常最好避免使用IEnumerable。但是,有一个解决方法:
Console.WriteLine(pE.AsQueryable().Count());
这意味着SQL COUNT 已发出。
(注意:转换为IQueryable 也可以在这里工作,但不是一般情况下 - 如果IEnumerable 纯粹是指内存中的数据,那么转换会失败,但AsQueryable() 会简单地将对象包围在一个中性的包装器。)
我的问题是:既然这个问题很容易被忽视并且会导致这种低效的行为,为什么对AsQueryable() 的调用不简单地嵌入到扩展方法的IEnumerable 实现中?
【问题讨论】:
-
IEnumerable扩展方法的功能与IQueryable版本不同。这在Count中并不明显,但请考虑Where。在IEnumerable中,这需要一个谓词(返回布尔值)lambda 并使用它来过滤流。在IQueryable中,这需要一个表示 lambda 的Expression树并将其转换为 SQL。到方法执行时,参数已经是错误的类型,无法转换。本质上,您的代码所做的是将IQueryable转换为IEnumerable,这与调用执行查询的AsEnumerable相同。
标签: entity-framework linq-to-entities extension-methods