为什么添加两个.orderby(或.orderbydescent)语句以相反的顺序应用排序?



我在今天重构的一些代码中遇到了以下问题:

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

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

因此,等效的实际上是:
context.Entities.Where(x => x.ForeignKeyId == id)
.OrderBy(x => x.SecondSortField)
.ThenBy(x => x.FirstSortField);
谁能解释一下EF6这样做的原因?在我看来,用第二个排序字段替换第一个排序字段会更直观。

我只能得出结论,这里我们实际上是在看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]

这个答案解释了原因,它归结为:LastSale是主导排序字段,因为它在某种程度上覆盖了第一个OrderBy

所有EF查询只有ORDER BY LastSale

我必须说同意EF的执行。正如这个答案所解释的,两个连续排序的结果取决于排序算法。这意味着我们可以肯定的是,来自任何LINQ查询的结果将由LastSale排序,而LastSale组中的排序是不确定的。然后,在我看来,对于SQL转换来说,处理第二个OrderBy语句作为第一个语句的完全覆盖是一个更好的选择,这样可以看到,没有期望可以基于第一个语句。对我来说,这更直观。

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

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

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

假设你有这样的数据有明显的姓/名字段:

<>以前布拉德·琼斯汤姆·史密斯山姆·琼斯吉姆能源部詹姆斯•史密斯如果你一开始只按名字下单,你会得到这个:

<>以前布拉德·琼斯詹姆斯•史密斯吉姆能源部瑞安史密斯山姆·琼斯如果你现在取这个排序过的列表,再次按姓氏排序,你得到的结果是按两个字段排序,其中后面的排序优先于前面的排序…但是只有在排序稳定的情况下才能保证精确的顺序:

<>以前吉姆能源部布拉德·琼斯山姆·琼斯詹姆斯•史密斯瑞安史密斯这给我们带来了。net使用什么算法,以及它是否稳定的问题。我们进入文档,在备注部分找到:

此方法执行稳定排序

具体的算法是,这里没有记录。我相信这是一种快速排序,但将其排除在文档之外可能是有意的,以便允许维护者在发现更好的东西时更新满足稳定性要求的最佳可用选项。

但是,同样,这是针对本地数据的。数据库将执行SQL命令。

实际上,这很简单,而且完全有意义:

查询的第一部分

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

最新更新