我是不是误解了 LINQ to SQL .AsEnumerable()?

Posted

技术标签:

【中文标题】我是不是误解了 LINQ to SQL .AsEnumerable()?【英文标题】:Am I misunderstanding LINQ to SQL .AsEnumerable()?我是否误解了 LINQ to SQL .AsEnumerable()? 【发布时间】:2011-03-24 07:44:28 【问题描述】:

考虑这段代码:

var query = db.Table
              .Where(t => SomeCondition(t))
              .AsEnumerable();

int recordCount = query.Count();
int totalSomeNumber = query.Sum();
decimal average = query.Average();

假设query 需要很长时间才能运行。我需要获取记录数,总共返回SomeNumber,并在最后取平均值。根据我的阅读,我认为.AsEnumerable() 将使用 LINQ-to-SQL 执行查询,然后对 CountSumAverage 使用 LINQ-to-Objects。相反,当我在 LINQPad 中执行此操作时,我看到相同的查询运行了 3 次。如果我用.ToList() 替换.AsEnumerable(),它只会被查询一次。

我是否遗漏了 AsEnumerable 是什么/做什么?

【问题讨论】:

了解AsEnumerable()行为的一个非常有用的问题 【参考方案1】:

调用AsEnumerable() 不会执行查询,而枚举它会执行。

IQueryable 是允许LINQ to SQL 发挥其魔力的接口。 IQueryable 实现了IEnumerable,所以当您调用AsEnumerable() 时,您正在更改从那里调用的扩展方法,即从IQueryable 方法到IEnumerable 方法(即从LINQ to SQL 更改在这种特殊情况下,LINQ to Objects)。但是您没有执行实际查询,只是更改了整个执行的方式

要强制执行查询,您必须调用ToList()

【讨论】:

认为“什么都没有发生”当然是不正确的。虽然AsEnumerable 在调用它时不会评估查询,但它肯定会产生影响。对查询进一步调用的任何内容都将使用 LINQ to 对象进行评估,因此您无法将其他元素组合到将成为 SQL 语句一部分的查询(另一个 WhereOrderBy 或任何类似性质的元素)。 一个很好的例子来说明它的作用和你为什么要使用它:假设你正在构建一个查询并且第一个块以OrderBy(...)结尾,现在类型是IOrderedEnumerable所以以后你可以继续追加ThenBy(...),甚至稍后你可以说return originalQuery.AsEnumerable()将它转换回普通的IEnumerable 我更喜欢做同样事情的 ToArray,除非你特别需要 List 实现。 @rushonerok ToList() 更快,因此除非您的对象存在很长时间,否则请使用ToList 而不是ToArray,请参阅***.com/a/16323412/691294 @Backwards_Dave 好吧,是的,我不同意这一点。似乎我驳斥的陈述现在已被删除,但一般情况是分配比需要更多的内存(即ToList())比确保适量的内存(即ToArray())更快。我的论点是,如果您不知道使用哪一个,请使用ToList(),因为它的抽象性更高,并且不会降低性能。【参考方案2】:

是的。 AsEnumerable 所做的只是使CountSumAverage 函数在客户端执行(换句话说,它将把整个结果集返回给客户端,然后客户端将执行这些聚合,而不是在 SQL 中创建 COUNT() SUM()AVG() 语句)。

【讨论】:

但是OP的意思是他假设你说的是真的,但经验测试表明它不是。 -1 这根本不是真的。 IQueryable 实现了 IEnumerable,因此对 AsEnumerable 的调用是无操作的,不会强制执行查询。 @James,贾斯汀:你误会了。我从来没有说过AsEnumerable() 会导致查询评估,我说添加它唯一会做的是聚合被评估时,它们将在客户端完成(整个结果集将在在客户端进行枚举,并计算聚合)而不是被翻译成SQL语句。 请注意,当您有 IOrderedEnumerable 时,您可能希望使用 AsEnumerable(),因为您以某种类型的 OrderBy 结束了查询的第一部分。 @JustinNiessner 该评论显然是事实错误,静态类型转换 EVER 是如何NOP 的?它改变了整个执行方案......这很重要,因为 LINQ 是作为扩展方法(即静态类型)而不是继承(动态/运行时类型)构建的。【参考方案3】:

Justin Niessner's answer 完美。

我只想在这里引用一个 MSDN 的解释:.NET Language-Integrated Query for Relational Data

与 ToList() 和 ToArray() 不同,AsEnumerable() 运算符不会导致执行查询。它仍然被推迟。 AsEnumerable() 运算符仅更改查询的静态类型,将 IQueryable 转换为 IEnumerable,诱使编译器将查询的其余部分视为本地执行。

我希望这就是它的意思:

IQueryable-methods 到 IEnumerable-methods(即从 LINQ 到 SQL 到 LINQ 到 Objects 的变化

一旦它是 LINQ to Objects,我们就可以应用对象的方法(例如ToString())。这是关于 LINQ 的常见问题之一的解释 - Why LINQ to Entities does not recognize the method 'System.String ToString()?

根据ASENUMERABLE - codeblog.jonskeet,AsEnumerable可以派上用场:

数据库中查询的某些方面,然后在 .NET 中进行更多操作 - 特别是如果有些方面您基本上无法在 LINQ to SQL(或您使用的任何提供程序)中实现。

它还说:

我们所做的只是将通过查询传播的序列的编译时类型从 IQueryable 更改为 IEnumerable——但这意味着编译器将使用 Enumerable 中的方法(获取委托,并在 LINQ 中执行对象)而不是 Queryable 中的那些(采用表达式树,通常在进程外执行)。

最后,也看到这个相关的问题:Returning IEnumerable vs. IQueryable

【讨论】:

【参考方案4】:

嗯,你在正确的轨道上。问题是IQueryableAsEnumerable 调用之前的语句)也是IEnumerable,因此该调用实际上是一个 nop。它需要强制它使用特定的内存数据结构(例如,ToList())来强制查询。

【讨论】:

【参考方案5】:

我认为 ToList 会强制 Linq 从数据库中获取记录。然后,当您执行后续计算时,它们是针对内存中的对象完成的,而不是涉及数据库。

将返回类型保留为 Enumerable 意味着在执行计算的代码调用它之前不会获取数据。我猜这个敲击是数据库被击中了 3 次 - 每次计算一次,并且数据没有持久化到内存中。

【讨论】:

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

LINQ to SQL和外键的基本误解

Linq To Sql Contains 参数数量最大不能超过 2100 怎么解决?

LINQ to entity中的一些误解

linq-to-sql 是不是处理动态查询?

使用 LINQ to SQL 是不是有助于防止 SQL 注入

静态方法是不是适用于 Linq To SQL DAL?