为啥添加两个 .OrderBy(或 .OrderByDescending)语句会以相反的顺序应用排序?

Posted

技术标签:

【中文标题】为啥添加两个 .OrderBy(或 .OrderByDescending)语句会以相反的顺序应用排序?【英文标题】:Why does adding two .OrderBy (or .OrderByDescending) statements apply the sorts in reverse order?为什么添加两个 .OrderBy(或 .OrderByDescending)语句会以相反的顺序应用排序? 【发布时间】:2021-04-30 17:41:04 【问题描述】:

我在今天重构的一些代码中遇到了以下内容。

context.Entities.Where(x => x.ForeignKeyId == id)
    .OrderBy(x => x.FirstSortField)
    .OrderBy(x => x.SecondSortField);

最初,我取出.OrderBy(x => x.FirstSortField) 认为第一个OrderBy 语句将被第二个OrderBy 替换。经过测试,我发现它生成的SQL是ORDER BY SecondSortField, FirstSortField

因此,等价的其实是:

context.Entities.Where(x => x.ForeignKeyId == id)
    .OrderBy(x => x.SecondSortField)
    .ThenBy(x => x.FirstSortField);

谁能解释 EF6 这样做的原因?在我看来,将第一个排序字段替换为第二个会更直观。

【问题讨论】:

第一个OrderBy替换为第二个。您的“等价物”不是等价物。 @GertArnold,这就是我最初认为它会做的事情,直到我开始测试。例如,如果您在 LinqPad 中尝试此操作,它为这两个语句生成的 SQL 是等效的。 @JoelCoehoorn 这不是 LINQ-to-objects。该查询非常简单地转换为一个 ORDER BY 语句。看到它发生在我面前。 @WyattEarp 因为您似乎实际看到了生成的ORDER BY,所以我想知道这里的定义因素是什么。你也在SQL Server吗?您基于此的实际查询是否在某些似乎并不重要的方面有所不同?真的,不是我不相信你,而是这种不同让我着迷。 [t0] 是 LINQ-to-SQL 前缀。 EF 生成其他前缀。 【参考方案1】:

我只能得出结论,我们实际上是在这里查看 LINQ-to-SQL。在 Linqpad 直到 v. 5 中,很容易犯这个错误,因为在创建新连接时很容易忽略 EF6 DbContext 驱动程序的选择。 (在 Linqpad v6 中这个选择更加明显)。

我已经在 EF6、EF-core 3 和 5 以及 LINQ-to-SQL 中测试了报告的行为。 只有在后者中,我看到生成的 SQL 语句在 ORDER BY 中有两列。

声明...

Products.OrderBy(p => p.Description).OrderBy(p => p.LastSale)

...由 LINQ-to-SQL 翻译为:

SELECT [t0].[ID], [t0].[Description], [t0].[Discontinued], [t0].[LastSale]
FROM [Product] AS [t0]
ORDER BY [t0].[LastSale], [t0].[Description]

原因在 this answer 中进行了解释,归结为:LastSale 是主要的排序字段,因为它在某种程度上覆盖了第一个 OrderBy

所有 EF 查询只有ORDER BY LastSale

我必须说同意 EF 的实施。正如this answer 解释的那样,两个连续排序的结果取决于排序算法。这意味着我们可以肯定地说 any LINQ 查询的结果将按LastSale 排序,而LastSale 组内的排序不确定。然后,IMO,将第二个OrderBy 语句作为第一个语句的完全覆盖处理是 SQL 翻译的更好选择,因此可以看出没有任何期望可以基于第一个语句。对我来说,它更直观。

消息是:按多个字段排序时要明确。使用OrderBy - ThenBy。不要依赖数据库提供商处理连续的OrderBy 语句。

【讨论】:

由于延迟执行的性质,我也同意 EF 的实施。我看到,如果我在 LinqPad 中使用 EF6 更改为自定义类型的数据上下文,我会看到预期的行为。感谢您帮助我理解!【参考方案2】:

这都是关于本地数据的,但 EF 希望在构建表达式树和编写查询时进行逻辑等效。

您应该研究stable sorting 的概念。当您使用稳定的排序算法时,相等项目的原始顺序会被保留。

假设您有这样的数据,其中包含明显的名字/姓氏字段:

布拉德·琼斯 汤姆史密斯 山姆·琼斯 吉姆·多伊 詹姆斯·史密斯 瑞恩史密斯

如果您最初仅按名字订购,您会得到:

布拉德·琼斯 詹姆斯·史密斯 吉姆·多伊 瑞恩史密斯 山姆·琼斯 汤姆史密斯

如果您现在使用这个排序列表,并再次按姓氏排序,您会得到一个按两个字段排序的结果,其中后面的排序优先于前面的排序...但是您只能保证准确如果排序稳定则排序

吉姆·多伊 布拉德·琼斯 山姆·琼斯 詹姆斯·史密斯 瑞恩史密斯 汤姆史密斯

这给我们带来了 .Net 使用什么算法以及它是否稳定的问题。 To the documentation we go,我们可以在备注部分找到这个:

此方法执行稳定排序

具体算法在此处没有记录。我相信这是Quicksort,但将其从文档中删除可能是有意为之,以便维护人员在发现更好的东西时更新满足稳定性要求的最佳可用选项。

但是,同样,这是针对本地数据的。数据库会按照 SQL 的指示执行操作。

【讨论】:

文档还声明:“由于 IOrderedEnumerable 继承自 IEnumerable,因此您可以对 OrderBy、OrderByDescending、ThenBy 或 ThenByDescending 的调用结果调用 OrderBy 或 OrderByDescending。 这样做会引入一个新的主排序,它会忽略先前建立的排序。”“稳定”属性确实保留了第二次排序中相等项的顺序,因此结果看起来像排序关于 OrderBy 调用的反转取决于正在排序的数据的内容。 @CodeCaster 该声明具有误导性。在我的例子中,在第一类之后,Brad Jones 排在 Jim Doe 之前。我相信他们试图说的是第二次调用 OrderBy() 可以覆盖它,我们在第二个例子中看到,Brad Jones 紧随其后。它忽略了顺序,因为它没有特别注意,但是因为顺序存在于 IOrderedEnumerable 中,所以您会得到可预测的结果。如果这是对实体的 linq,您可能会对使用 .ThenBy() 有所了解。但是对于 EF,这将被转换为 SQL 并执行 OP 想要的操作。【参考方案3】:

其实,这很简单,而且很有道理:

查询的第一部分

context.Entities.Where(x => x.ForeignKeyId == id)

或多或少会像这样翻译成 SQL

select * from Entities

添加第一个订单

.OrderBy(x => x.FirstSortField)

它会被翻译成这个

select * from (
   select * from Entities
)
order by FirstSortField

然后添加第二个订单

.OrderBy(x => x.FirstSortField)
.OrderBy(x => x.SecondSortField)

将被翻译成:

select * from (
   select * from (
      select * from Entities
   )
   order by FirstSortField
)
order by SecondSortField

实体框架足够聪明,可以简化为类似

select * from Entities
order by SecondSortField, FirstSortField

【讨论】:

有点太聪明了,因为语义上应该只保留order by SecondSortField。没有TOP 的子查询基本上是无序的 不,实体框架足够聪明,可以放弃第一个 OrderBy。请在发布前检查此类内容。 @GertArnold,你应该检查一下自己。 EF 曾经“覆盖” orderbys,但目前由它组成。不信你自己试试看。 好吧,也许我疯了,但我看到两个链接的 OrderBy 在 SQL 中被翻译成一个 ORDER BY。 EF6(问题是关于)和 EF 核心 3。 @GertArnold 你确定(你使用不同的属性每个 order by :D?我用 EF6 打开 LinqPad,它在 .Net Core 3、3.1 和 5.0 中转换两个 order by。跨度>

以上是关于为啥添加两个 .OrderBy(或 .OrderByDescending)语句会以相反的顺序应用排序?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 EF Core 最后要添加一个额外的 ORDER BY

SQL中的group by为啥是按照分组的第二个字段排序的呢?

为啥使用 order by 添加时 distinct(column) 会返回重复项?

为啥在mysql中第一个union两个子句的order by不起作用

使用 SQL 序数位置表示法的好处?

sql 数据量很大 ,为啥加了order by速度变慢了?