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

Я пытаюсь сортировать список продуктов по их цене.

В результирующем наборе необходимо отображать продукты по цене от низкого до высокого по столбцу 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

Ответ 1

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

orderby p.LowestPrice.HasValue descending, p.LowestPrice

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

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

Ответ 2

Это действительно помогает понять синтаксис запроса 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;

Подробнее о переводах из синтаксиса запроса в вызовы методов см. спецификацию языка. Шутки в сторону. Прочтите.

Ответ 3

У меня есть другой вариант в этой ситуации. Мой список является objList, и я должен заказать, но нули должны быть в конце. мое решение:

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

Ответ 4

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

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

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

Итак, я предпочитаю делать что-то более читаемое, например следующее:

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

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

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

ОБНОВЛЕНИЕ 9/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);

Ответ 5

мое решение:

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

Ответ 6

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

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

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

Ответ 7

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

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

Это работает с объектами LINQ 2 в памяти. Я не тестировал его с помощью EF или любого DB ORM.

Ответ 8

Ниже приведен метод расширения для проверки на нулевое значение, если вы хотите отсортировать по дочернему свойству 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);