Порядок LINQ по пустому столбцу, где порядок возрастает, а значения нуля должны быть последними

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более понятен.
Джейсон
@ Джейсон, да, я не знал синтаксис для orderby, и меня отслеживали на стороне, ища его :)
PostMan

Ответы:

161

Попробуйте поставить оба столбца в одном порядке.

orderby p.LowestPrice.HasValue descending, p.LowestPrice

В противном случае каждый заказ - это отдельная операция над коллекцией, каждый раз переупорядочивающая его.

Это должно сначала упорядочить те, которые имеют значение, а затем порядок значений.

DaveShaw
источник
22
Распространенная ошибка, люди делают то же самое с синтаксисом Lamda - дважды используют .OrderBy вместо .ThenBy.
DaveShaw
1
Это работало, чтобы упорядочить поля со значениями сверху и пустыми полями снизу, я использовал это: orderby p.LowestPrice == null, p.LowestPrice ascending Надежда помогает кому-то.
Шайют
@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 :) Тем не менее, хорошее объяснение
се
18

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

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

Единственная причина, которая работает, состоит в том, что первое выражение OrderBy(), сортирует boolзначения: true/ false. falseрезультат идет первым, затем следует trueрезультат (обнуляемый) и ThenBy()сортирует ненулевые значения в алфавитном порядке.

Поэтому я предпочитаю делать что-то более читабельное, например:

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

Если SomeStringноль, он будет заменен "z"и затем отсортировать все по алфавиту.

ПРИМЕЧАНИЕ: это не окончательное решение, так как "z"идет первым, чем z-значения, как zebra.

ОБНОВЛЕНИЕ 6/6/2016 - По поводу комментария @jornhd, это действительно хорошее решение, но оно все же немного сложное, поэтому я рекомендую обернуть его в класс Extension, например, так:

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);
Jaider
источник
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 вместо нуля.
Нареш Равлани
Да. Просто замените ноль на 0.
Гурген Овсепян
это единственный ответ, который сработал для меня, остальные оставили нулевые значения в начале списка.
BMills
9

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

Мой окончательный ответ был:

.OrderByDescending(p => p.LowestPrice.HasValue).ThenBy(p => p.LowestPrice)
user1
источник
7

мое решение:

Array = _context.Products.OrderByDescending(p => p.Val ?? float.MinValue)
РТК
источник
7

Это то, что я придумал, потому что я использую методы расширения, а также мой элемент является строкой, поэтому нет .HasValue:

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

Это работает с объектами LINQ 2 в памяти. Я не проверял это с EF или любой БД ORM.

AaronLS
источник
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

  • Пользователи должны быть в алфавитном порядке
  • Учетные записи без имени First / LastName, в зависимости от их ADName, но в конце списка пользователей
  • Фиктивный пользователь с идентификатором «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     
dognose
источник