了解LINQ to SQL中的.AsEnumerable()方法

54

假设有以下 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”表被缓存在内存中?如果表中包含大量记录,这会减慢性能吗?

帮我理解这里实际上正在发生什么。


看一下另一个问题的例子:在此输入链接说明 - komizo
4个回答

43
AsEnumerable 的作用是,在一个实现了 IEnumerable(T) 接口的序列中,当该序列还拥有一组不同的公用查询方法时,选择查询实现。在之前的代码中使用的是另外一个 Where 方法,即 IEnumerable.Where,它是为了将 LINQ 转换为 SQL 所使用的。而新的 Where 是 IEnumerable 接口中的一个方法,它接收一个 IEnumerable 参数,对其进行枚举并返回匹配的项。这就解释了为什么会看到生成的 SQL 不同,当使用第二个版本的代码时,整张表将从数据库中完整地取出,然后再应用 Where 扩展方法,这可能会造成严重的瓶颈,因为整张表必须全部加载到内存中,或者更糟的情况是整张表需要在服务器之间传输。让 SQL Server 来执行 Where 并发挥其最大的优势。

1
那么它仍然具有延迟执行的功能,对吗?以上面的查询为例,整个“Imports”表不会被存储在内存中,只有活动的部分,在我执行类似于“test.Dump()”这样的操作时。是这样的吗? - Mike Fielden
@Mike Fielden 好的,整个表格将通过 Where 枚举它来进入内存。我不确定所有行是否会同时加载到内存中。 - Yuriy Faktorovich
1
@YuriyFaktorovich:那么它为什么存在呢?也请告诉我们使用AsEnumerable的优点,谢谢。 - Unbreakable
1
例如,当您想要应用一个过滤器(例如一些复杂的lambda表达式),而查询实现不支持时,可以先应用一个带有一些初始过滤的“Where”方法,然后调用“AsEnumerable”,然后再使用另一个“Where”来细化过滤条件(使用查询不支持的lambda)。这样,您就可以克服查询实现的限制,同时仍然能够预先过滤查询,以便不获取所有数据。 - Jakub Januszkiewicz
我刚意识到这基本上就是Jon Hanna在他的回答中所描述的。 - Jakub Januszkiewicz

9

当枚举被枚举时,将查询数据库并检索整个结果集。

部分解决方案可能是一种方式。请考虑

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 时,尽可能多的工作将被传递给数据库,数据库将返回足够的内容以继续工作。假设确实不可能重写代码以便所有工作都可以发送到数据库,那么这可能是一个合适的妥协方案。

6

有三个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>但也具有不同的公共查询方法集可用时选择查询实现。例如,给定一个实现IEnumerable<T>并具有自己的方法(如WhereSelectSelectMany)的通用类Table,对Where的调用将调用Table的公共Where方法。表示数据库表的Table类型可以具有一个将谓词参数作为表达式树并将树转换为SQL以进行远程执行的Where方法。如果不希望进行远程执行,例如因为谓词调用了本地方法,则可以使用AsEnumerable<TSource>方法隐藏自定义方法,并使标准查询运算符可用。

换句话说。

如果我有一个

IQueryable<X> sequence = ...;

我使用像Entity Framework这样的Linq提供程序,然后进行操作。

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,但我的函数将直接被使用。(然而,这可能是一种低效的方法,因为所有行都将从服务器返回。)

2
我认为AsEnumerable只是告诉编译器要使用哪些扩展方法(在本例中,是为IEnumerable定义的方法而不是为IQueryable定义的方法)。查询的执行仍然被延迟,直到您调用ToArray或枚举它。

这没有任何意义。AsEnumerable是一个方法。它不会向编译器传达任何特定的信息。 - Pete Montgomery
3
是的,它是一种方法,但它的返回类型是Enumerable,因此编译器在编译LINQ查询时将使用不同的方法(Enumerable的方法)。根据文档:AsEnumerable<TSource>(IEnumerable<TSource>)方法除了将源的编译时类型从实现IEnumerable<T>类型更改为IEnumerable<T>本身以外,没有其他效果。http://msdn.microsoft.com/en-us/library/bb335435.aspx - Razvi
我同意@Razvi的观点,AsEnumerable()会推迟查询的执行,直到你调用任何返回数据集的Linq方法。 - rasika godawatte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接