我应该何时使用 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 =&gt; 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 =&gt; x.Id == thatId) 来获得我们想要的行。

如果 Table 是一个 IQueryable,那么这将被转换为对该行的 Table 请求,该请求将从服务器返回,该服务器将数据存储在运行应用程序的服务器的内存中。

另一方面,如果我们将其视为 IEnumerable,例如将其视为Table.AsEnumerable().Where(x =&gt; 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?的主要内容,如果未能解决你的问题,请参考以下文章

何时在 F# 中使用序列而不是列表?

何时在 Linq 中使用 Cast() 和 Oftype()

我应该何时调用 StateHasChanged 以及 Blazor 何时自动拦截某些更改?

为啥存在 Google maven 存储库以及我应该何时使用它?

何时不使用承诺 [关闭]

在 GraphQL 服务器设置中何时使用 Redis 以及何时使用 DataLoader