LINQ的OrderBy方法,它是否总是返回相同顺序的列表?

7

我是一个简单的OrderBy语句,目标数据如下:

要排序的目标数据如下:

[
  {"id":40, "description":"aaa", "rate":1},
  {"id":1,  "description":"bbb", "rate":1},
  {"id":4,  "description":"ccc", "rate":2},
  {"id":19, "description":"aaa", "rate":1} 
]

我会按照比率属性对项目进行排序。

奇怪的是,如果我“排序”它们,它会通过给定的偏移量“跳过”一些项目,然后只“取”部分数据。

例如:

var result = items.OrderBy(i => i.rate);
var result = result.Skip(2);
var result = result.Take(2);

结果大部分看起来都不错,但是“边缘情况”项根本没有返回。
例如,如果第一个结果返回如下:
    [{"id":40, "description":"aaa", "rate":1}, {"id":1, "description":"bbb", "rate":1}]

第二个结果返回如下:
    [{"id":1, "description":"bbb", "rate":1}, {"id":4, "description":"ccc", "rate":2}]

第二次查询调用未返回“id:19”的项目。相反,“id:1”的项目返回了两次。

我猜测SQL OrderBy语句不能每次都产生相同的排序列表,OrderBy按照给定属性排序,但共享相同属性的组内确切顺序可能会改变。

底层的确切机制是什么?


如果您不指定顺序,则不能保证顺序。如果您指定了多个列,例如 order by rate, description,那么它将按 rate 排序,然后在 rate 值重复的情况下按 description 排序。仍然可能存在多行具有相等的 ratedescription 值,它们的顺序将保持未指定状态。id 通常用作解决决斗者以确保稳定的顺序:order by rate, description, id - HABO
一个实际的原因是顺序可能不同:一些快速排序算法随机选择枢轴。 - usr
1个回答

24

简短回答: LINQ to Objects使用稳定排序算法,因此我们可以说它是确定性的,而LINQ to SQL取决于通常是不确定性的Order By数据库实现。

确定性排序算法在不同运行时始终具有相同的行为。

在您的示例中,OrderBy子句中存在重复项。为了获得保证和预测可能的排序,必须使其中一个排序子句或排序子句的组合是唯一的。

在LINQ中,您可以通过添加另一个OrderBy子句来引用唯一属性来实现,例如
items.OrderBy(i => i.Rate).ThenBy(i => i.ID)

长回答:

LINQ to Objects使用稳定排序,如此链接中所述:MSDN

在LINQ to SQL中,它取决于底层数据库的排序算法,并且通常是不稳定的,例如在MS SQL Server中 (MSDN)。

在稳定排序中,如果两个元素的键相等,则元素的顺序将被保留。相反,不稳定排序不保留具有相同键的元素的顺序。

Wikipedia example

因此,对于LINQ to SQL,排序通常是不确定性的,因为关系数据库管理系统(例如MS SQL Server)可以直接使用带有随机中心选择的不稳定排序算法,或者随机性可能与数据库首先访问文件系统的哪个行相关联。

例如,想象一下,文件系统中的页面大小可以容纳最多4行。

如果您插入以下数据,则该页面将已满:

     Page 1
| Name | Value |
|------|-------|
|   A  |   1   |
|   B  |   2   |
|   C  |   3   |
|   D  |   4   |


如果需要插入新行,则关系数据库管理系统有两个选项:

  1. 创建一个新页面以分配新行。
  2. 将当前页面分成两个页面。因此,第一页将保存名称为AB的内容,而第二页将保存CD

假设RDMS选择选项1(以减少索引碎片),如果插入名称为C和值为9的新行,则会出现:

     Page 1              Page 2
| Name | Value |    | Name | Value |
|------|-------|    |------|-------|
|   A  |   1   |    |   C  |   9   |
|   B  |   2   |    |      |       |
|   C  |   3   |    |      |       |
|   D  |   4   |    |      |       |


很可能,按照 Name 列进行排序将返回以下结果:

| Name | Value |
|------|-------|
|   A  |   1   |
|   B  |   2   |
|   C  |   3   |
|   C  |   9   | -- Value 9 appears after because it was at another page
|   D  |   4   |


现在,假设 RDMS 选择选项2(为具有多个主轴的存储系统增加插入性能)。 如果您插入名称为 C 和值为 9 的新行,则会得到:

     Page 1              Page 2
| Name | Value |    | Name | Value |
|------|-------|    |------|-------|
|   A  |   1   |    |   C  |   3   |
|   B  |   2   |    |   D  |   4   |
|   C  |   9   |    |      |       |
|      |       |    |      |       |


可能,按照 Name 列的 OrderBy 条件将会返回以下结果:

| Name | Value |
|------|-------|
|   A  |   1   |
|   B  |   2   |
|   C  |   9   |  -- Value 9 appears before because it was at the first page
|   C  |   3   | 
|   D  |   4   |
关于您的例子:
我相信您在问题中打错了一些内容,因为您使用了items.OrderBy(i => i.rate).Skip(2).Take(2);,但第一个结果没有显示一个具有Rate = 2的行。这是不可能的,因为Skip将忽略前两行,它们具有Rate = 1,所以您的输出必须显示Rate = 2的行。
您已经用database标记了您的问题,因此我认为您正在使用LINQ to SQL。在这种情况下,结果可能是不确定性的,您可能会得到以下结果:
结果1:
[{"id":40, "description":"aaa", "rate":1},
 {"id":4, "description":"ccc", "rate":2}]

结果2:

[{"id":1, "description":"bbb", "rate":1},
 {"id":4, "description":"ccc", "rate":2}]

如果你使用了 items.OrderBy(i => i.rate).ThenBy(i => i.ID).Skip(2).Take(2);,那么唯一可能的结果是:

[{"id":40, "description":"aaa", "rate":1},
 {"id":4, "description":"ccc", "rate":2}]

1
值得注意的是,LINQ to Objects 在 OrderBy 中使用稳定排序,但转换为 SQL 的 LINQ 查询将使用其数据源实现的 ORDER BY。 - rdans
谢谢@RyanDansie,我已经将你的注释添加到我的答案中。 - Zanon

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