了解 LINQ to SQL 中的 .AsEnumerable()

Posted

技术标签:

【中文标题】了解 LINQ to SQL 中的 .AsEnumerable()【英文标题】:Understanding .AsEnumerable() in LINQ to SQL 【发布时间】:2011-03-19 16:22:32 【问题描述】:

给定以下 LINQ to SQL 查询:

var test = from i in Imports
           where i.IsActive
           select i;

解释的 SQL 语句是:

SELECT [t0].[id] AS [Id] .... FROM [Imports] AS [t0] WHERE [t0].[isActive] = 1

假设我想在 select 中执行一些无法转换为 SQL 的操作。我的理解是,实现此目的的常规方法是执行 AsEnumerable() 从而将其转换为可行的对象。

鉴于此更新的代码:

var test = from i in Imports.AsEnumerable()
           where i.IsActive
           select new 
            
               // Make some method call 
           ;

并更新了 SQL:

SELECT [t0].[id] AS [Id] ... FROM [Imports] AS [t0] 

请注意在执行的 SQL 语句中缺少 where 子句。

这是否意味着整个“Imports”表被缓存到内存中? 如果表包含大量记录,这是否会降低性能?

帮助我了解幕后实际发生的事情。

【问题讨论】:

看看另一个问题的例子:[在此处输入链接描述][1] [1]:***.com/a/14129116/1792434 【参考方案1】:

AsEnumerable的原因是为了

AsEnumerable(TSource)(IEnumerable(TSource)) 可用于在查询之间进行选择 序列时的实现 实现 IEnumerable(T) 但也有 一组不同的公共查询 可用的方法

因此,当您之前调用Where 方法时,您调用的是与IEnumerable.Where 不同的Where 方法。 Where 语句用于 LINQ 转换为 SQL,新的 Where 是采用 IEnumerableIEnumerable 语句,枚举它并产生匹配项。这就解释了为什么您会看到正在生成的不同 SQL。在将 Where 扩展名应用到您的代码的第二个版本之前,该表将从数据库中完整获取。这可能会造成严重的瓶颈,因为整个表必须在内存中,或者更糟的是,整个表必须在服务器之间传输。允许 SQL 服务器执行 Where 并做它最擅长的事情。

【讨论】:

所以它仍然有延迟执行的权利吗?以上面的查询为例,当我执行“test.Dump()”之类的操作时,整个“Imports”表不会只存储在内存中。对吗? @Mike Fielden 整个表将通过内存,因为 Where 正在枚举它。我不确定是否所有行都会同时加载到内存中。 @YuriyFaktorovich:那么它为什么存在呢?请告诉使用 AsEnumerable 的专业人士。谢谢 @Unbreakable 例如,当您想要应用查询实现不支持的过滤器(例如一些复杂的 lambda 表达式)时。您可以首先应用带有一些初始过滤的Where 方法,然后调用AsEnumerable,然后调用另一个Where 来细化过滤条件(使用查询不支持的lambda)。通过这种方式,您可以克服查询实现的限制,同时仍然能够预先过滤查询,这样您就不会获取所有数据。 我刚刚意识到这基本上就是 Jon Hanna 在他的回答中所描述的。【参考方案2】:

我相信 AsEnumerable 只是告诉编译器使用哪些扩展方法(在这种情况下是为 IEnumerable 而不是为 IQueryable 定义的扩展方法)。 查询的执行仍会延迟,直到您调用 ToArray 或对其进行枚举。

【讨论】:

这没有任何意义。 AsEnumerable 是一种方法。它并没有特别“告诉编译器”任何事情。 是的,它是一个方法,但它的返回类型是 Enumerable,因此编译器在编译 LINQ 查询(Enumerable 查询)时将使用不同的方法。从文档中: AsEnumerable(IEnumerable) 方法除了将源的编译时类型从实现 IEnumerable 的类型更改为 IEnumerable 本身之外没有任何效果。 msdn.microsoft.com/en-us/library/bb335435.aspx【参考方案3】:

在枚举通过的点,然后将查询数据库,并检索整个结果集。

一个部分和部分的解决方案可能是一种方式。考虑

var res = (
    from result in SomeSource
    where DatabaseConvertableCriterion(result)
    && NonDatabaseConvertableCriterion(result)
    select new result.A, result.B
);

假设 NonDatabaseConvertableCriterion 需要来自结果的字段 C。因为 NonDatabaseConvertableCriterion 就像它的名字所暗示的那样,所以这必须作为枚举来执行。但是,请考虑:

var partWay =
(
    from result in SomeSource
    where DatabaseConvertableCriterion(result)
    select new result.A, result.B, result.C
);
var res =
(
    from result in partWay.AsEnumerable()
    where NonDatabaseConvertableCriterion select new result.A, result.B
);

在这种情况下,当枚举、查询或以其他方式使用 res 时,将尽可能多的工作传递给数据库,数据库将返回足够的数据以继续工作。假设确实不可能重写,让所有的工作都可以发送到数据库,这可能是一个合适的折衷方案。

【讨论】:

【参考方案4】:

AsEnumerable 的三种实现方式。

DataTableExtensions.AsEnumerable

扩展 DataTable 以为其提供 IEnumerable 接口,以便您可以针对 DataTable 使用 Linq。

Enumerable.AsEnumerable<TSource>ParallelEnumerable.AsEnumerable<TSource>

AsEnumerable<TSource>(IEnumerable<TSource>) 方法无效 除了将源的编译时类型从一个类型更改为 实现 IEnumerable<T>IEnumerable<T> 本身。

AsEnumerable<TSource>(IEnumerable<TSource>)可以用来选择 序列实现时的查询实现之间 IEnumerable<T> 但也有一组不同的公共查询方法 可用的。例如,给定一个泛型类Table,它实现 IEnumerable<T> 并有自己的方法如WhereSelectSelectMany,对 Where 的调用将调用公共的 Where 方法 Table。表示数据库表的 Table 类型可以有 Where 将谓词参数作为表达式树的方法 并将树转换为 SQL 以进行远程执行。如果远程执行 不需要,例如因为谓词调用本地 方法,AsEnumerable<TSource> 方法可以用来隐藏 自定义方法,而不是使用标准查询运算符 可用。

换句话说。

如果我有一个

IQueryable<X> sequence = ...;

来自 LinqProvider,例如 Entity Framework,我也这样做,

sequence.Where(x => SomeUnusualPredicate(x));

该查询将在服务器上组合和运行。这将在运行时失败,因为 EntityFramework 不知道如何将 SomeUnusualPredicate 转换为 SQL。

如果我希望它改为使用 Linq to Objects 运行语句,我会这样做,

sequence.AsEnumerable().Where(x => SomeUnusualPredicate(x));

现在服务器将返回所有数据,并且将使用来自 Linq to Objects 的Enumerable.Where,而不是查询提供程序的实现。

Entity Framework 不知道如何解释SomeUnusualPredicate 没关系,我的函数将被直接使用。 (但是,这可能是一种低效的方法,因为所有行都将从服务器返回。)

【讨论】:

以上是关于了解 LINQ to SQL 中的 .AsEnumerable()的主要内容,如果未能解决你的问题,请参考以下文章

C# 中的 LINQ to SQL 查询

实体框架/Linq to sql 模型到业务模型

Linq to Sql:多个左外连接

理解LINQ to SQL中的.AsEnumerable()

为啥 Linq-to-sql 错误地删除了我的联合中的字段?

从 C# 保存到 SQL 中的 DATE 类型列 - Linq to SQL