LINQ order by null kolumna, gdzie kolejność jest rosnąca, a wartości null powinny być ostatnie

141

Próbuję posortować listę produktów według ich ceny.

Zestaw wyników musi zawierać listę produktów według ceny od najniższej do najwyższej według kolumny LowestPrice . Jednak ta kolumna ma wartość null.

Mogę posortować listę malejąco w następujący sposób:

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

Jednak nie mogę dowiedzieć się, jak posortować to w kolejności rosnącej.

// i'd like: 100, 101, 102, null, null
sf.
źródło
11
orderby p.LowestPrice ?? Int.MaxValue;to prosty sposób.
PostMan
3
@PostMan: Tak, to proste, daje właściwy wynik, ale OrderByDescending, ThenByjest jaśniejsze.
jason
@Jason, tak, nie znałem składni dla orderbyi zostałem śledzony, szukając go :)
PostMan

Odpowiedzi:

160

Spróbuj umieścić obie kolumny w tej samej kolejności

orderby p.LowestPrice.HasValue descending, p.LowestPrice

W przeciwnym razie każde zlecenie jest osobną operacją na kolekcji, za każdym razem zmieniając jego kolejność.

Powinno to uporządkować najpierw te, które mają wartość, a następnie kolejność wartości.

DaveShaw
źródło
21
Częstym błędem jest to, że ludzie robią to samo ze składnią Lamda - używając dwukrotnie .OrderBy zamiast .ThenBy.
DaveShaw
1
To działało, aby uporządkować pola z wartościami na górze i pustymi polami na dole. Użyłem tego: orderby p.LowestPrice == null, p.LowestPrice ascending Nadzieja pomaga komuś.
shaijut
@DaveShaw dziękuję za wskazówkę - szczególnie komentarz - bardzo schludny - uwielbiam to
Demetris Leptos
86

Naprawdę pomaga zrozumieć składnię zapytania LINQ i sposób jej tłumaczenia na wywołania metod LINQ.

Okazało się, że

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

zostaną przetłumaczone przez kompilator na

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

To zdecydowanie nie jest to, czego chcesz. Te rodzaje by Product.LowestPrice.HasValuew descendingkolejności, a następnie ponownie sortuje przez całą kolekcję Product.LowestPricew descendingporządku.

To czego chcesz

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

które można uzyskać za pomocą składni zapytania

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

Aby uzyskać szczegółowe informacje na temat tłumaczenia składni zapytania na wywołania metod, zobacz specyfikację języka. Poważnie. Przeczytaj to.

Jason
źródło
4
+1 lub po prostu ... nie pisać składni zapytań LINQ :) mimo wszystko dobre wytłumaczenie
sehe
18

Rozwiązanie dla wartości łańcuchowych jest naprawdę dziwne:

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

Jedynym powodem, dla którego to działa, jest to, że pierwsze wyrażenie OrderBy(),, sortuje boolwartości: true/ false. falsewynik najpierw przejdź do truewyniku ( ThenBy()wartości null ) i sortuj wartości niezerowe w kolejności alfabetycznej.

Dlatego wolę zrobić coś bardziej czytelnego, takiego jak to:

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

Jeśli SomeStringjest null, zostanie zastąpiony przez"z" a następnie posortowany alfabetycznie.

UWAGA: To nie jest ostateczne rozwiązanie, ponieważ "z"jest pierwsze niż wartości z, takie jak zebra.

UPDATE 9/6/2016 - O komentarzu @jornhd, jest to naprawdę dobre rozwiązanie, ale wciąż jest trochę skomplikowane, więc zalecę zawinięcie go w klasę rozszerzenia, taką jak ta:

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

I po prostu użyj go jak:

var sortedList = list.NullableOrderBy(f => f.SomeString);
Jaider
źródło
2
Myślę, że jest to bardziej czytelne, bez paskudnej stałej: .OrderBy (f => f.SomeString! = Null? 0: 1) .ThenBy (f => f.SomeString)
jornhd
14

Mam inną opcję w tej sytuacji. Moja lista to objList i muszę zamówić, ale wartości null muszą być na końcu. moja decyzja:

var newList = objList.Where(m=>m.Column != null)
                     .OrderBy(m => m.Column)
                     .Concat(objList.where(m=>m.Column == null));
Gurgen Hovsepyan
źródło
Może to działać w scenariuszach, w których chce się uzyskać wynik z innymi wartościami, takimi jak 0 zamiast null.
Naresh Ravlani
Tak. Po prostu zamień zera na 0.
Gurgen Hovsepyan
to jedyna odpowiedź, która mi pomogła, reszta zachowała wartości puste na początku listy.
BMills
9

Próbowałem znaleźć rozwiązanie LINQ, ale nie mogłem tego rozwiązać na podstawie odpowiedzi tutaj.

Moja ostateczna odpowiedź brzmiała:

.OrderByDescending(p => p.LowestPrice.HasValue).ThenBy(p => p.LowestPrice)
użytkownik1
źródło
7

moja decyzja:

Array = _context.Products.OrderByDescending(p => p.Val ?? float.MinValue)
RTK
źródło
7

Oto, co wymyśliłem, ponieważ używam metod rozszerzających, a mój element jest ciągiem, więc nie .HasValue:

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

Działa to z obiektami LINQ 2 w pamięci. Nie testowałem tego z EF ani żadnym DB ORM.

AaronLS
źródło
0

Poniżej znajduje się metoda rozszerzenia do sprawdzania wartości null, jeśli chcesz posortować według właściwości podrzędnej 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);
}

I po prostu użyj go jak:

var sortedList = list.NullableOrderBy(x => x.someObject, y => y.someObject?.someProperty);
Manish Patel
źródło
0

Oto inny sposób:

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

Zauważ, że r.SUP_APPROVED_IND.Trim() == nulljest traktowany jakotrim(SUP_APPROVED_IND) is null w bazie danych Oracle.

Zobacz to, aby uzyskać szczegółowe informacje: Jak mogę wysyłać zapytania o wartości null w ramach jednostki?

Leonid Minkov
źródło
0

Inna opcja (była przydatna w naszym scenariuszu):

Mamy tabelę użytkowników, przechowującą ADName, LastName, FirstName

  • Użytkownicy powinni być alfabetyczni
  • Konta bez imienia i nazwiska również na podstawie ich ADName - ale na końcu listy użytkowników
  • Fałszywy użytkownik o identyfikatorze „0” („Brak wyboru”) Powinien zawsze znajdować się na górze.

Zmieniliśmy schemat tabeli i dodaliśmy kolumnę „SortIndex”, która definiuje niektóre grupy sortowania. (Pozostawiliśmy odstęp 5, więc możemy wstawić grupy później)

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

Teraz, pod względem zapytania, byłoby to:

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

w wyrażeniach metod:

db.User.OrderBy(u => u.SortIndex).ThenBy(u => u.LastName).ThenBy(u => u.FirstName).ThenBy(u => u.AdName).ToList();

co daje oczekiwany wynik:

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
źródło