繁体   English   中英

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

[英]Why does adding two .OrderBy (or .OrderByDescending) statements apply the sorts in reverse order?

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

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 这样做的原因? 在我看来,用第二个排序字段替换第一个排序字段会更直观。

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

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

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

Brad Jones
Tom Smith
Sam Jones
Jim Doe
James Smith
Ryan Smith

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

Brad Jones
James Smith
Jim Doe
Ryan Smith
Sam Jones
Tom Smith

如果您现在采用此排序列表,并再次按姓氏排序,您将得到按两个字段排序的结果,其中后面的排序优先于前面的排序......但只有在排序稳定时,您才能保证确切的顺序

Jim Doe
Brad Jones
Sam Jones
James Smith
Ryan Smith
Tom Smith

这给我们带来了 algorithm.Net 使用什么以及它是否稳定的问题。 对于我们 go 的文档,我们在备注部分中找到了这个:

此方法执行稳定排序

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

但是,同样,这是针对本地数据的。 数据库将执行 SQL 告诉他们的操作。

我只能得出结论,我们实际上在这里查看的是 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组中的排序不确定。 然后,IMO,它是 SQL 翻译的更好选择来处理第二个OrderBy语句作为第一个语句的完全覆盖,因此可以看出没有任何期望可以基于第一个语句。 对我来说,它更直观。

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

实际上,这很简单,而且完全有道理:

查询的第一部分

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM