C# Linq查询执行顺序

4

考虑以下方法:

public IEnumerable<Owner> GetOwners(OwnerParameters ownerParameters)
{
    return FindAll()
        .OrderBy(on => on.Name)
        .Skip((ownerParameters.PageNumber - 1) * ownerParameters.PageSize)
        .Take(ownerParameters.PageSize)
        .ToList();
}
FindAll() 是一个存储库模式方法,返回 IQueryable<Owner>。在 .Skip().Take() 方法之前添加 .OrderBy() 是否意味着将检索并排序来自 Owner 数据表的所有元素,还是 Linq 会考虑到 .Skip().Take() 方法可能缩小了所需的 Owner 元素,并且只有在检索这些元素之后才进行排序?

编辑:Profiler 日志:

SELECT XXX
FROM [Owners] AS [a]
ORDER BY [a].[Name]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY',N'@__p_0 int,@__p_1 int',@__p_0=0,@__p_1=10

投票关闭此问题的人,您所说的“需要更多细节或清晰度”是指什么? - astralmaster
3个回答

6

最终,这取决于 FindAll() 做了什么以及它返回了什么:

  • 如果它返回 IEnumerable<T>,则它正在使用LINQ-to-Objects,这基本上只是按字面意思执行您的指令;如果您排序然后分页,那么它就会先排序再分页;如果您先分页再排序,那么它就会先分页再排序
  • 然而,如果它返回 IQueryable<T>,则查询正在被“合成” - 并且只有在 ToList() 中实际执行时,提供程序模型才有机会检查您的查询树并构建最适合的实现,这通常意味着编写包含 ORDER BY 和适用于特定关系数据库管理系统(RDBMS) 的某些分页提示的 SQL 查询;如果您的代码先分页再排序(这很少见),那么我预计大多数提供程序要么编写某种可怕的子查询来描述它,要么干脆抛出异常(可能是 NotSupportedException)。

肯定是 IQueryable<Owner>。而且 RDBMS 是 SQL Server。这是否意味着,不会检索整个 Owner 表,而只会检索 .Take().Skip() 请求的部分,并在之后进行排序? - astralmaster
@astralmaster 我完全期望它发出一个查询,在数据库服务器上进行排序和分页,只返回相关页面;但是!如果不确定:请使用任何可用的分析和监控工具。 - Marc Gravell
@MarcGravell:我不同意这个观点,即“只有在之后才排序”。数据库需要在使用分页之前应用排序,否则返回的行是任意的。所以也许我误解了OP的评论或者你的回答,或者你忽略了它。 - Tim Schmelter
@TimSchmelter 哦,是的,那是我表述不清 / 口误了; 我说“是”是指“否” :) 不过,yes 后面的话似乎很清楚。我已经删掉了“是”(其余内容不变)。 - Marc Gravell
在SQL Server中,它会生成像这样的代码:SELECT ... FROM ... ORDER BY ... OFFSET x ROWS FETCH NEXT y ROWS ONLY,该查询在服务器端进行排序并仅获取所需行,但是该查询在MS SQL Server上速度较慢。自定义查询要好得多。 - Cetin Basoz

0
一个快速的示例来证明查询顺序很重要。上面给出的解释是很好的。
var test1 = await _dbContext.UserActivityLogs
.Where(x => x.ExternalSyncLogId == request.Id)
.Skip(0).Take(25)
.AsNoTracking().ToListAsync();

这个查询被转译为

exec sp_executesql N'SELECT [u].[Id], [u].[ActionType], [u].[ActivityEndTime], [u].[ActivityStartTime], [u].[ActivityType], [u].[Created], [u].[CreatedBy], [u].[EntityId], [u].[ExternalSyncLogId], [u].[LastModified], [u].[LastModifiedBy], [u].[RequestBody], [u].[ResponseBody], [u].[Source], [u].[TenantId]
FROM [UserActivityLogs] AS [u]
WHERE ([u].[ExternalSyncLogId] IS NULL
ORDER BY (SELECT 1)
OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY',N'@__p_1 int,@__p_2 int',@__p_1=0,@__p_2=25

执行时间:29

但如果我们只是改变顺序,那么

var test2 = await _dbContext.UserActivityLogs
.Skip(0).Take(25)
.Where(x => x.ExternalSyncLogId == request.Id)
.AsNoTracking().ToListAsync();

翻译为

exec sp_executesql N'SELECT [t].[Id], [t].[ActionType], [t].[ActivityEndTime], [t].[ActivityStartTime], [t].[ActivityType], [t].[Created], [t].[CreatedBy], [t].[EntityId], [t].[ExternalSyncLogId], [t].[LastModified], [t].[LastModifiedBy], [t].[RequestBody], [t].[ResponseBody], [t].[Source], [t].[TenantId]
FROM (
    SELECT [u].[Id], [u].[ActionType], [u].[ActivityEndTime], [u].[ActivityStartTime], [u].[ActivityType], [u].[Created], [u].[CreatedBy], [u].[EntityId], [u].[ExternalSyncLogId], [u].[LastModified], [u].[LastModifiedBy], [u].[RequestBody], [u].[ResponseBody], [u].[Source], [u].[TenantId]
    FROM [UserActivityLogs] AS [u]
    ORDER BY (SELECT 1)
    OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
) AS [t]
WHERE [t].[ExternalSyncLogId] IS NULL',N'@__p_0 int,@__p_1 int',@__p_0=0,@__p_1=25

执行时间:474


0

不会检索所有记录。根据您的后端,将生成一个查询,按名称排序,跳过N行并获取接下来的M行。只有查询结果由.ToList()触发才会被检索。 例如:在MS SQL服务器中,查询可能如下所示:

Select top(M) row_number() over (order by Name) as RowNo, * 
from myTable
where RowNo > N

那种类型的查询并不是很有效,但这是另一回事,你可以创建自定义解决方案。

编辑:我记得 MS SQL 代码生成错误。它是:

SELECT ...
FROM ...
ORDER BY ...
OFFSET x ROWS FETCH NEXT y ROWS ONLY

那个也很慢。如果你考虑速度问题,那么可以编写自己的SQL并与Linq一起发送。基本上你要做的就是保持lastRetrieved值并将其设置为参数:

select top(NTake) ... from ... 
order by ... 
where orderedByValue > @lastRetrievedValue 

(您可以在Linq中发送原始的SQL查询)


谢谢。能详细说明一下为什么这不太有效吗? - astralmaster
@astralmaster,创建row_number()...然后获取top(N)是一个缓慢的操作(不是TOP N,而是row_number()部分很慢)。如果您有大量数据并且需要获取许多页面,则会变得很慢。相反,使用自定义查询,在客户端保留上次检索的名称,并请求大于该名称的名称,速度更快(假设您在名称上有索引)。实际上,关于此事有一篇文章,这种自定义查询方法要快得多。如果我能找到那篇文章,我会添加链接。 - Cetin Basoz
那么你会说,在这种情况下使用Linq和Entity Framework会产生显著较慢的查询,而宁愿自己构建一个查询? - astralmaster
@astralmaster,是的。但是快慢...是相对于其用户而言的。对我来说慢的速度,在一个小数据集中可能对你来说是可以接受的(在那里自定义查询的收益将是微不足道的)。我仍然找不到那篇帖子:( - 尽管那是我的一篇帖子。 - Cetin Basoz
1
@astralmaster,我终于找到了。https://dev59.com/RFsW5IYBdhLWcg3w2aKK#34533010#34533010 - Cetin Basoz

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