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 的使用无效
我发现这是因为 OFFSET NEXT 和 FETCH 在 sql server 2008 上不起作用,但我知道我在代码中的其他地方使用了分页,它们都运行良好。
有效的那些和这个有效的区别在于 Skip 和 Take 是 Enumerable 对有效的扩展和 Queryable 的扩展。
因此,将 AsEnumerable() 添加到查询中解决了我的问题。这似乎生成了使用 SELECT TOP(10) 而不是 OFFSET 和 FETCH 的 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,以了解两个查询之间的区别。
不同之处在于Skip
和Take
将应用于内存而不是DB。基本上你会得到所有的结果,然后过滤到你想要的结果。
Skip() 用于忽略像 skip(2) 这样的值,这将忽略前 2 个值。 Take() 用于获取诸如 take(2) 之类的顶部值,这将获取顶部 2 个值。
为什么你认为使用AsEnumerable时会生成一个select? AsEnumerable 表示在此处停止生成 SQL。我有兴趣了解为什么人们相信关于编程的错误信息;是什么让你产生了这种信念?
@EricLippert 我现在意识到这是不正确的,但我在 LINQPad 4 中运行该查询后得出了这个结论,它有一个功能可以让您查看从语句生成的 SQL,我看到查询正在使用 SELECT TOP。所以我认为这是它在没有给我错误时必须使用的查询。完全忘记了 AsEnumerable 将执行查询。 ://
【参考方案1】:
使用
Skip
和Take
作为Enumerable
与Queryable
的扩展有什么区别。
当您在实现IQueryable
的类型上调用Skip
或Take
时,将绑定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-MicrosoftSkip
是这样,Take
不是这样。
@DavidBrowne-Microsoft 但是整个集合都会被读入内存,对吗?因此,如果您从 1M 行中取出 10 行,内存效率会大大降低,不是吗?
@Servy Take 在任何一种情况下都是相同的,它们被加载并保存在内存中。 @D Stanley 读取第一个 Skip+Take 行(如果启用,则添加到 ChangeTracker)。但是其余的行从未从数据库中获取。
刚刚对此进行了测试,虽然尾随行永远不会具体化为对象,但处理枚举器调用 SqlDataReader.Dispose() 将通过网络从服务器获取行。所以它实际上并不便宜。考虑到这一点,可能是因为批处理/过程可能还有其他命令尚未运行,解除批处理阻塞的唯一方法是完成获取结果。以上是关于Skip() 和 Take() asEnumerable 与 asQueryable的主要内容,如果未能解决你的问题,请参考以下文章
具有实体框架并使用 orderby 和 skip/take 的规范模式