Skip() 和 Take() asEnumerable 与 asQueryable

Posted

技术标签:

【中文标题】Skip() 和 Take() asEnumerable 与 asQueryable【英文标题】:Skip() and Take() as Enumerable vs as Queryable 【发布时间】:2018-02-03 20:01:38 【问题描述】:

我最近在尝试使用 skip 和 take in LINQ 语句时遇到了一个错误。

我的陈述是这样的。

DbConxtext.MyTable.Get(c => c.UserID == id)
    .OrderBy(orderProperty).Skip(index).Take(length).ToList();

这给了我这个错误

“OFFSET”附近的语法不正确。\r\n在 FETCH 语句中选项 NEXT 的使用无效

我发现这是因为 OF​​FSET NEXT 和 FETCH 在 sql server 2008 上不起作用,但我知道我在代码中的其他地方使用了分页,它们都运行良好。

有效的那些和这个有效的区别在于 SkipTake 是 Enumerable 对有效的扩展和 Queryable 的扩展。

因此,将 AsEnumerable() 添加到查询中解决了我的问题。这似乎生成了使用 SELECT TOP(10) 而不是 OFFSETFETCH 的 SQL。

编辑: 再次阅读后,我意识到 AsEnumerable 不会生成不同的 SQL。它将改为执行查询并在内存中执行 Skip Take。

DbConxtext.MyTable.Get(c => c.UserID == id)
    .OrderBy(orderProperty).AsEnumerable().Skip(index).Take(length).ToList();

我的问题是使用 Skip 和 Take 作为 Enumerable 与 Queryable 的扩展有什么区别。

以及为什么 EF 决定在这两种情况下生成不同的 SQL。

【问题讨论】:

This seemed to generate SQL that uses SELECT TOP(10) 它不会做这样的事情。您应该查看生成的实际 SQL,以了解两个查询之间的区别。 不同之处在于SkipTake将应用于内存而不是DB。基本上你会得到所有的结果,然后过滤到你想要的结果。 Skip() 用于忽略像 skip(2) 这样的值,这将忽略前 2 个值。 Take() 用于获取诸如 take(2) 之类的顶部值,这将获取顶部 2 个值。 为什么你认为使用AsEnumerable时会生成一个select? AsEnumerable 表示在此处停止生成 SQL。我有兴趣了解为什么人们相信关于编程的错误信息;是什么让你产生了这种信念? @EricLippert 我现在意识到这是不正确的,但我在 LINQPad 4 中运行该查询后得出了这个结论,它有一个功能可以让您查看从语句生成的 SQL,我看到查询正在使用 SELECT TOP。所以我认为这是它在没有给我错误时必须使用的查询。完全忘记了 AsEnumerable 将执行查询。 :// 【参考方案1】:

使用SkipTake 作为EnumerableQueryable 的扩展有什么区别。

当您在实现IQueryable 的类型上调用SkipTake 时,将绑定Queryable 扩展方法,并且底层Linq 提供程序(例如Linq-to-Entities)将处理@987654330 @ 和/或 Take 并将其转换为底层数据提供者的命令(例如 SQL 语句)。提供者是否真正支持它们或处理它们正确直到运行时才能知道。

当你在实现IEnumerable(但不是IQueryable)的类型上调用它们时,Enumerable 扩展方法将被绑定,它只处理由Queryable 生成的内存集合上的命令查询。

为什么 EF 决定在这两种情况下生成不同的 SQL。

在您的第二种情况下,生成的 SQL 查询仅包含命令,直到您注入 AsEnumerable()。这就是 EF 提供者看到的所有内容。从那时起,命令将绑定到 Enumerable 扩展方法,并将在内存中处理剩余的命令。

这似乎生成了使用 SELECT TOP(10) 的 SQL

我非常怀疑这一点。 应该发生的是SQL查询将返回所有条记录,但Take生成的内存迭代器只会返回前十个。

如果您希望为 SQL 2008 数据库正确处理 SKIP 和 TAKE,请参阅 this question 了解替代解决方案。

【讨论】:

请注意,.AsEnumerable().Skip().Take() 对于 Skip() 和 SQL Server 的小值相对有效。客户端只需要 Read() 并忽略几行。 @DavidBrowne-Microsoft Skip 是这样,Take 不是这样。 @DavidBrowne-Microsoft 但是整个集合都会被读入内存,对吗?因此,如果您从 1M 行中取出 10 行,内存效率会大大降低,不是吗? @Servy Take 在任何一种情况下都是相同的,它们被加载并保存在内存中。 @D Stanley 读取第一个 Skip+Take 行(如果启用,则添加到 ChangeTracker)。但是其余的行从未从数据库中获取。 刚刚对此进行了测试,虽然尾随行永远不会具体化为对象,但处理枚举器调用 SqlDataReader.Dispose() 将通过网络从服务器获取行。所以它实际上并不便宜。考虑到这一点,可能是因为批处理/过程可能还有其他命令尚未运行,解除批处理阻塞的唯一方法是完成获取结果。

以上是关于Skip() 和 Take() asEnumerable 与 asQueryable的主要内容,如果未能解决你的问题,请参考以下文章

Linq 到实体 Skip() 和 Take()

具有实体框架并使用 orderby 和 skip/take 的规范模式

LINQ 查询添加 orderby 使 Skip 和 Take 不起作用 Linqpad

LinQ中Skip()方法和Take()方法的使用

Linq 使用skip和take分页

Linq 使用skip和take分页