我应该何时使用 IEnumerable 以及何时使用 IQueryable?
Posted
技术标签:
【中文标题】我应该何时使用 IEnumerable 以及何时使用 IQueryable?【英文标题】:When should I use IEnumerable and when IQueryable? 【发布时间】:2020-12-21 20:52:55 【问题描述】:我曾多次尝试寻找不同之处,但有多种答案。
一个常见的区别是IEnumerable
从内存中过滤数据,而IQueryable
在服务器端过滤。
这到底是什么意思?
对内存数据进行过滤是否仍然是服务器端的事情?
我试图在多个地方寻找它们的用途,但我无法用简单的话找到它。
谢谢。
【问题讨论】:
【参考方案1】:IEnumerable<T>
代表产生一系列结果的东西。但是,它不会公开有关如何生成序列的任何信息。
IQueryable<T>
在Expression
属性中以expression tree 的形式公开有关如何生成序列的信息。然后可以轻松地将这些信息映射到不同的指令集。
如果您在IEnumerable<T>
上调用Enumerable.Where
,您将传入一个与Func<T, bool>
兼容的编译方法。理论上,我们可以解析编译方法的 IL 以找出该方法的作用,并使用它来映射到另一组指令;但这非常复杂。不可避免地,解决此问题的唯一方法是将所有对象从服务器/提供者/数据源加载到内存中,并将编译后的方法应用于每个对象。
如果您在IQueryable<T>
上调用Queryable.Where
,您将传入一个根据定义表示不同代码操作的对象——Expression<Func<T, bool>>
。例如:
IQueryable<Person> qry = /* ... */;
qry = qry.Where(x => x.LastName.StartsWith("A"));
编译器将x => x.LastName.StartsWith("A")
转换为代表其各个部分的对象:
Lambda expression, returning a `bool`
with an `x` parameter of type `Person`
Call the `StartsWith` method, passing in a constant string `"A"`, on
Result of the `LastName` property, of
the `x` element
更多,调用Queryable.Where
本身也会修改底层表达式树:
Call to Queryable.Where, passing in
The object at `qry`, and
The previous lambda expression (see above)
当枚举查询时(使用foreach
,或调用ToList
,或类似的东西),信息可以很容易地从这个对象映射到另一种形式,例如SQL:
SELECT *
FROM Persons
WHERE LastName LIKE N'A%'
或网络请求:
http://example.com/Person?lastname[0]=a
调用Queryable.Where
后的最终表达式树将类似于此对象图,如果可以使用构造函数以及对象和集合初始化器构造表达式树:
var x = new ParameterExpression
Type = typeof(Person),
Name = "x"
;
new MethodCallExpression
Type = typeof(IQueryable<Person>),
Arguments = new ReadOnlyCollection<Expression>
new ConstantExpression
Type = typeof(EnumerableQuery<Person>)
,
new UnaryExpression
NodeType = ExpressionType.Quote,
Type = typeof(Expression<Func<Person, bool>>),
Operand = new Expression<Func<Person, bool>>
NodeType = ExpressionType.Lambda,
Type = typeof(Func<Person, bool>),
Parameters = new ReadOnlyCollection<ParameterExpression>
x
,
Body = new MethodCallExpression
Type = typeof(bool),
Object = new MemberExpression
Type = typeof(string),
Expression = x,
Member = typeof(Person).GetProperty("LastName")
,
Arguments = new ReadOnlyCollection<Expression>
new ConstantExpression
Type = typeof(string),
Value = "A"
,
Method = typeof(string).GetMethod("StartsWith", new[] typeof(string) )
,
ReturnType = typeof(bool)
,
Method = typeof(Queryable).GetMethod("Where", new[] typeof(IQueryable<Person>), typeof(Expression<Func<Person, bool>>) )
(注意。这是使用ExpressionTreeToString library 编写的。免责声明:我是作者。)
【讨论】:
谢谢朋友,现在很清楚了。 AsEnumerable() 和 AsQueryable() 是同一个伞吗? @MuhammadFaraz 或多或少——我认为AsEnumerable
允许你强制使用Enumerable.Where
;而AsQueryable
允许您强制使用Queryable.Where
。我的理解是,它们的效果与铸造相同。【参考方案2】:
Zev Spitz 已经很好地说明了这种差异,但我想补充一个极端情况下发生的例子,以突出差异。
假设您的数据在一台服务器上,而应用程序在另一台服务器上运行。这些都是通过互联网连接的。 假设您在数据中有一个包含 100 万行的表,并且您想要一个具有给定 ID 的表 thatId。 忽略您的连接方式,让变量 Table 成为您代码中该数据的表示形式。
现在,我们可以通过请求Table.Where(x => x.Id == thatId)
来获得我们想要的行。
如果 Table 是一个 IQueryable,那么这将被转换为对该行的 Table 请求,该请求将从服务器返回,该服务器将数据存储在运行应用程序的服务器的内存中。
另一方面,如果我们将其视为 IEnumerable,例如将其视为Table.AsEnumerable().Where(x => x.Id == thatId)
,那么这将变成对表中所有行的请求,然后将其传输到内存运行应用程序的服务器。只有在互联网上传输完所有这些数百万行之后,应用程序才会对数据进行排序并挑选出您真正想要的行。
【讨论】:
【参考方案3】:一个常见的区别是 IEnumerable 从内存中过滤数据,而 IQueryable 在服务器端进行过滤
你是对的,一切都在服务器中发生。但在上面的句子中,server side
表示 SQL Server,而不是运行 .NET 应用程序的服务器。所以主要思想是IQueryable
可以翻译成SQL脚本,在SQL Server中执行,相比将数据从SQL Server带到应用程序内存然后执行查询,这往往带来显着的性能提升内存中。
【讨论】:
【参考方案4】:不是 100% 肯定,但我认为这意味着使用 IQueryable,在我们使用 BE 中的 EF 和 SQLike 作为 DB 的示例中,其中编写的任何 Linq 过程都将转换为 SQL 和发送到数据库,它将详细说明此类 sql 代码,然后返回结果。
虽然 IEnumelable 缺少此功能,例如,如果您将整个实体转换为 IEnumerable,您将始终在 BE 内详细说明任何过滤器。
我希望我是对的,我已经很清楚了, 祝你愉快
【讨论】:
以上是关于我应该何时使用 IEnumerable 以及何时使用 IQueryable?的主要内容,如果未能解决你的问题,请参考以下文章
何时在 Linq 中使用 Cast() 和 Oftype()
我应该何时调用 StateHasChanged 以及 Blazor 何时自动拦截某些更改?