Порядок LINQ по стовпцю "null", де порядок зростає, а нулі мають бути останніми


141

Я намагаюся сортувати список продуктів за їх ціною.

У наборі результатів потрібно перераховувати продукти за ціною від низької до високої за стовпцем LowestPrice. Однак цей стовпчик є нульовим.

Я можу сортувати список у порядку зменшення таким чином:

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

11
orderby p.LowestPrice ?? Int.MaxValue;це простий спосіб.
PostMan

3
@PostMan: Так, це просто, він досягає правильного результату, але OrderByDescending, ThenByчіткіший.
Ясон

@Jason, так, я не знав синтаксису для orderbyта шукав його в стороні :)
PostMan

Відповіді:


160

Спробуйте розмістити обидва стовпці в одному порядку.

orderby p.LowestPrice.HasValue descending, p.LowestPrice

В іншому випадку кожне замовлення - це окрема операція над колекцією, яка кожного разу повторно замовляє його.

При цьому слід впорядкувати спочатку значення, а потім - порядок значення.


21
Поширена помилка: люди роблять те саме з синтаксисом Lamda - використовуючи .OrderBy двічі замість .ThenBy.
DaveShaw

1
Це працювало, щоб замовити поля зі значеннями вгорі та нульовими полями внизу, я використав це: orderby p.LowestPrice == null, p.LowestPrice ascending Надія допомагає комусь.
shaijut

@DaveShaw дякую за пораду - особливо коментар один - дуже охайний - люблю
Demetris Leptos

86

Це дійсно допомагає зрозуміти синтаксис запитів LINQ і як він перекладається на виклики методу LINQ.

Виявляється, що

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

буде переведений компілятором в

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

Це рішуче не те, що ви хочете. Ця сортування по Product.LowestPrice.HasValueв descendingпорядку , а потім повторно сортує всю колекцію з допомогою Product.LowestPriceв descendingпорядку.

Те, що ти хочеш, так і є

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
+1 або просто ... не пишіть синтаксис запиту LINQ :) Тим не менш, добре пояснення
Sehe

18

Рішення для рядкових значень дійсно дивно:

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

Єдина причина, яка працює - це те, що перший вираз OrderBy(), сортування boolзначень: true/ false. falseРезультат спочатку слід за trueрезультатом (зведений ThenBy()на нуль ) і сортуйте ненульові значення за алфавітом.

Отже, я вважаю за краще робити щось більш читабельне, як це:

.OrderBy(f => f.SomeString ?? "z")

Якщо SomeStringnull, його буде замінено "z"і потім сортувати все за алфавітом.

ПРИМІТКА. Це не є кінцевим рішенням, оскільки "z"йде першим, ніж значення z zebra.

ОНОВЛЕННЯ 6/6/2016 - Про коментар @jornhd, це дійсно хороше рішення, але воно все ще мало складне, тому я рекомендую перетворити його в клас розширення, наприклад такий:

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);
    }
}

І просто використовувати його так:

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

2
Я думаю, що це читабельніше, без неприємної константи: .OrderBy (f => f.SomeString! = Null? 0: 1) .ThenBy (f => f.SomeString)
jornhd

14

У мене є інший варіант у цій ситуації. Мій список є objList, і я маю замовити, але нулі повинні бути в кінці. моє рішення:

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

Це може працювати в сценаріях, коли бажає результату з іншими значеннями, такими як 0 замість нуля.
Нареш Равлані

Так. Просто замініть null на 0.
Гурген Овсепян

це єдина відповідь, яка працювала на мене, решта зберегла нулі на початку списку.
BMills

9

Я намагався знайти рішення LINQ для цього, але не зміг знайти це з відповідей тут.

Моя остаточна відповідь:

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

7

моє рішення:

Array = _context.Products.OrderByDescending(p => p.Val ?? float.MinValue)

7

Це те, що я придумав, тому що я використовую методи розширення, а також мій елемент є рядком, тому немає .HasValue:

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

Це працює з об'єктами LINQ 2 в пам'яті. Я не перевіряв це за допомогою EF чи будь-якої DB ORM.


0

Нижче наведено метод розширення, щоб перевірити наявність нуля, якщо ви хочете сортувати за дочірнім властивістю keySelector.

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

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 is char(1) in Oracle db.

Зауважте, що r.SUP_APPROVED_IND.Trim() == nullтрактується як trim(SUP_APPROVED_IND) is nullв Oracle db.

Дивіться це для деталей: Як я можу запитувати нульові значення в рамках сутності?


0

Ще один варіант (був зручний для нашого сценарію):

У нас є таблиця користувачів, що зберігає ADName, LastName, FirstName

  • Користувачі мають бути в алфавітному порядку
  • Облікові записи, які також не мають Першого / Остання прізвища, виходячи з їх імені ADN - але наприкінці списку користувачів
  • Користувач-манекен з ідентифікатором "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();

що дає очікуваний результат:

ID | ADName |      First Name | LastName | SortIndex
0    No Selection  null         null     | 0
5    AD\ama        Amanda       Whatever | 5
1    AD\jon        Jon          Doe      | 5
4    AD\Accounting null         null     | 10
3    AD\Support    null         null     | 10     
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.