LINQ按空列升序排序,且空值应该排在最后

186

我正在尝试按价格对产品列表进行排序。

结果集需要按照LowestPrice列中的价格从低到高列出产品。然而,该列可以为null。

我可以这样按降序对列表进行排序:

var products = from p in _context.Products
   where p.ProductTypeId == 1
   orderby p.LowestPrice.HasValue descending
   orderby p.LowestPrice descending
   select p;

// returns:    102, 101, 100, null, null

但是我无法弄清如何按升序排序。

// i'd like: 100, 101, 102, null, null

23
"orderby p.LowestPrice ?? Int.MaxValue;"是一种简单的方式。 该代码片段使用了C#语言中的空合并运算符 (??),用于在"p.LowestPrice"和"Int.MaxValue"之间选择一个较小的值作为排序条件。 - PostMan
3
@PostMan:是的,它很简单,可以实现正确的结果,但是使用“OrderByDescending,ThenBy”更清晰明了。 - jason
@Jason,是的,我不知道“orderby”的语法,然后就分心去找它了 :) - PostMan
@PostMan 这个能用,但是你能解释一下它是怎么工作的吗?我感到很惊讶。 - Harkirat singh
@Harkiratsingh 这个问题是在2011年提出的,自那以后可能已经发生了变化... - PostMan
@Harkiratsingh 因为你想让 null 出现在升序列表的末尾,所以给它赋最大值,因为最大值总是出现在升序列表的末尾。此外,如果你想让 null 值出现在升序列表的开头,那么就给它赋最小值。 - maryhadalittlelamb
10个回答

193

尝试将两个列都放在同一个orderby中。

orderby p.LowestPrice.HasValue descending, p.LowestPrice
否则,每个orderby都是对集合的单独操作,每次重新对其进行重新排序。 这将按值的顺序对其进行排序,然后按值的顺序排序。

33
常见错误,人们在使用Lambda语法时会犯同样的错误——使用两次.OrderBy而不是.ThenBy。 - DaveShaw
4
我用以下代码将有值的字段置于前,空字段置于后:orderby p.LowestPrice == null, p.LowestPrice ascending希望能对某些人有所帮助。 - Shaiju T
@DaveShaw 谢谢你的提示 - 尤其是评论部分 - 非常整洁 - 我很喜欢。 - Demetris Leptos

116

了解 LINQ 查询语法及其如何转换为 LINQ 方法调用确实有所帮助。

事实证明,

var products = from p in _context.Products
               where p.ProductTypeId == 1
               orderby p.LowestPrice.HasValue descending
               orderby p.LowestPrice 
               select p;

编译器将把这段代码转换为

var products = _context.Products
                       .Where(p => p.ProductTypeId == 1)
                       .OrderByDescending(p => p.LowestPrice.HasValue)
                       .OrderBy(p => p.LowestPrice)
                       .Select(p => p);

这绝对不是你想要的。它按照Product.LowestPrice.HasValue降序排序,然后再按Product.LowestPrice降序重新排序整个集合。

你所需要的是

var products = _context.Products
                       .Where(p => p.ProductTypeId == 1)
                       .OrderByDescending(p => p.LowestPrice.HasValue)
                       .ThenBy(p => p.LowestPrice)
                       .Select(p => p);

使用查询语法可以获取该信息。
var products = from p in _context.Products
               where p.ProductTypeId == 1
               orderby p.LowestPrice.HasValue descending,
                       p.LowestPrice
               select p;

有关从查询语法到方法调用的翻译细节,请参阅语言规范。认真读一下吧。


4
不用写LINQ查询语法 :) 仅返回翻译后的文本。 - sehe

36

针对字符串值的解决方案真的很奇怪:

.OrderBy(f => f.SomeString == null).ThenBy(f => f.SomeString) 

唯一有效的原因是第一个表达式 OrderBy()bool 值进行排序:true/false。先排列出 false 的结果,然后是 true 的结果(可空值),ThenBy() 对非空值按字母顺序进行排序。
e.g.: [null, "coconut", null, "apple", "strawberry"]
First sort: ["coconut", "apple", "strawberry", null, null]
Second sort: ["apple", "coconut", "strawberry", null, null]
So, I prefer doing something more readable such as this:
.OrderBy(f => f.SomeString ?? "z")

If SomeString is null, it will be replaced by "z" and then sort everything alphabetically.

NOTE: This is not an ultimate solution since "z" goes first than z-values like zebra.

UPDATE 9/6/2016 - About @jornhd comment, it is really a good solution, but it still a little complex, so I will recommend to wrap it in a Extension class, such as this:

public static class MyExtensions
{
    public static IOrderedEnumerable<T> NullableOrderBy<T>(this IEnumerable<T> list, Func<T, string> keySelector)
    {
        return list.OrderBy(v => keySelector(v) != null ? 0 : 1).ThenBy(keySelector);
    }
}

And simple use it like:

var sortedList = list.NullableOrderBy(f => f.SomeString);

2
我认为这样更易读,没有那个讨厌的常量: .OrderBy(f => f.SomeString != null ? 0 : 1).ThenBy(f => f.SomeString) - jornhd

19

在这种情况下,我还有另一个选项。 我的列表是objList,我必须对其进行排序,但空值必须排在最后。 我的决定:

var newList = objList.Where(m=>m.Column != null)
                     .OrderBy(m => m.Column)
                     .Concat(objList.where(m=>m.Column == null));

这可以在需要结果为0而不是null的情况下使用。 - Naresh Ravlani
是的。只需将 null 替换为 0。 - Gurgen Hovsepyan
这是唯一对我有效的答案,其他答案仍在列表开头保留了空值。 - BMills

15

我的决定:

Array=_context.Products.OrderBy(p=>p.Val ?? float.MaxValue)
这将把 NULL 值视为仅用于排序的 float.MaxValue,这将使空值排在列表末尾,允许我们按升序排列并排除空值。

根据 OP 的要求,应按升序和最大值排序 _context.Products.OrderBy(p => p.Val ?? float.MaxValue)。 - Michael Freidgeim

14

我曾试图从这里的答案中找到一个使用LINQ的解决方案,但最终无法解决问题。

我的最终答案是:

.OrderByDescending(p => p.LowestPrice.HasValue).ThenBy(p => p.LowestPrice)

8

因为我使用了扩展方法,并且我的项目是字符串,所以我想到了这个解决方案,因此没有.HasValue:

.OrderBy(f => f.SomeString == null).ThenBy(f => f.SomeString)

这个代码适用于内存中使用的LINQ 2对象。我没有测试过它是否适用于EF或任何数据库ORM。


1
以下是用于检查空值的扩展方法,如果您想要按键选择器的子属性进行排序。
public static IOrderedEnumerable<T> NullableOrderBy<T>(this IEnumerable<T> list, Func<T, object> parentKeySelector, Func<T, object> childKeySelector)
{
    return list.OrderBy(v => parentKeySelector(v) != null ? 0 : 1).ThenBy(childKeySelector);
}

然后简单地使用它,就像这样:

var sortedList = list.NullableOrderBy(x => x.someObject, y => y.someObject?.someProperty);

1

另一种选择(对于我们的情况很方便):

我们有一个用户表,存储ADName、LastName、FirstName

  • 用户应该按字母顺序排列
  • 没有姓或名的账户也应该根据其ADName排在用户列表的末尾
  • ID为“0”的虚拟用户(“无选择”)应始终处于最顶端。

我们修改了表模式并添加了一个“SortIndex”列,它定义了一些排序组。(我们留了5个间隔,以便以后插入组)

ID | ADName |      First Name | LastName | SortIndex
0    No Selection  null         null     | 0
1    AD\jon        Jon          Doe      | 5
3    AD\Support    null         null     | 10     
4    AD\Accounting null         null     | 10
5    AD\ama        Amanda       Whatever | 5

现在,按查询方式来说,它将是:

SELECT * FROM User order by SortIndex, LastName, FirstName, AdName;

在方法表达式中:
db.User.OrderBy(u => u.SortIndex).ThenBy(u => u.LastName).ThenBy(u => u.FirstName).ThenBy(u => u.AdName).ToList();

这句话的意思是:“产生预期结果的代码如下:”。其中的HTML标签

表示段落。

ID | ADName |      First Name | LastName | SortIndex
0    No Selection  null         null     | 0
1    AD\jon        Jon          Doe      | 5
5    AD\ama        Amanda       Whatever | 5
4    AD\Accounting null         null     | 10
3    AD\Support    null         null     | 10     

0

这里还有另一种方法:

//Acsending
case "SUP_APPROVED_IND": qry =
                            qry.OrderBy(r => r.SUP_APPROVED_IND.Trim() == null).
                                    ThenBy(r => r.SUP_APPROVED_IND);

                            break;
//….
//Descending
case "SUP_APPROVED_IND": qry =
                            qry.OrderBy(r => r.SUP_APPROVED_IND.Trim() == null).
                                    ThenByDescending(r => r.SUP_APPROVED_IND); 

                            break;

SUP_APPROVED_IND 在 Oracle 数据库中是 char(1) 类型。

请注意,在 Oracle 数据库中,r.SUP_APPROVED_IND.Trim() == null 被视为 trim(SUP_APPROVED_IND) is null

有关详细信息,请参见:如何在实体框架中查询空值?


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