LINQ OrderBy。它总是返回相同的有序列表吗?

时间:2014-09-18 21:06:52

标签: sql .net sql-server linq linq-to-objects

我正在尝试一个简单的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);

结果看起来很好,但是根本不会返回'edge case'项。

例如,

如果第一个结果以

的形式返回
    [{"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订单时生成相同的有序列表,但是共享相同属性的组中的确切顺序可能会发生变化。

引擎盖下的确切机制是什么?

1 个答案:

答案 0 :(得分:20)

简短回答: 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,排序通常是不确定的,因为 RDMS (关系数据库管理系统,如MS SQL Server)可能会直接使用带有随机数据透视选择的不稳定排序算法或随机性可能与数据库首先在文件系统中访问哪一行有关。

例如,假设文件系统中页面的大小最多可以容纳4行。

如果您插入以下数据,页面将填满:

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


如果需要插入新行,RDMS有两个选项:

  1. 创建新页面以分配新行。
  2. 将当前页面拆分为两页。因此,第一页将包含名称 A B ,第二页将包含 C D 。< / LI>

    假设RDMS选择选项1(以减少索引碎片)。如果您插入名为 C 且值 9 的新行,您将获得:

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


    可能名称列中的OrderBy子句将返回以下内容:

    | 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   |    |      |       |
    |      |       |    |      |       |
    


    可能名称列中的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}]