Ordine LINQ per colonna nulla in cui l'ordine è crescente e i valori null devono essere gli ultimi


141

Sto cercando di ordinare un elenco di prodotti in base al loro prezzo.

Il set di risultati deve elencare i prodotti per prezzo dal più basso al più alto per colonna LowestPrice. Tuttavia, questa colonna è nullable.

Posso ordinare l'elenco in ordine decrescente in questo modo:

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

Tuttavia, non riesco a capire come ordinare questo in ordine crescente.

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

11
orderby p.LowestPrice ?? Int.MaxValue;è un modo semplice.
PostMan,

3
@PostMan: Sì, è semplice, ottiene il risultato giusto, ma OrderByDescending, ThenByè più chiaro.
Jason,

@Jason, sì, non conoscevo la sintassi di orderby, e sono stato monitorato lateralmente per cercarlo :)
PostMan,

Risposte:


161

Prova a mettere entrambe le colonne nello stesso ordine.

orderby p.LowestPrice.HasValue descending, p.LowestPrice

Altrimenti ogni ordine è un'operazione separata sulla raccolta che lo riordina ogni volta.

Questo dovrebbe prima ordinare quelli con un valore, "poi" l'ordine del valore.


22
Errore comune, le persone fanno lo stesso con la sintassi di Lamda - usando .OrderBy due volte invece di .ThenBy.
DaveShaw,

1
Questo ha funzionato per ordinare i campi con i valori in alto e i campi nulli in basso l'ho usato: la orderby p.LowestPrice == null, p.LowestPrice ascending speranza aiuta qualcuno.
Shaijut,

@DaveShaw grazie per il suggerimento - in particolare quello del commento - molto ordinato - lo adoro
Demetris Leptos

86

Aiuta davvero a capire la sintassi della query LINQ e come viene tradotta nelle chiamate al metodo LINQ.

Si scopre che

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

sarà tradotto dal compilatore in

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

Questo non è assolutamente quello che vuoi. Questo ordina Product.LowestPrice.HasValuein descendingordine e quindi riordina l'intera raccolta Product.LowestPricein descendingordine.

Quello che vuoi è

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

che è possibile ottenere utilizzando la sintassi della query tramite

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

Per i dettagli delle traduzioni dalla sintassi della query alle chiamate al metodo, consultare le specifiche della lingua. Sul serio. Leggilo.


4
+1 o solo ... non scrivere la sintassi della query LINQ :) Buona spiegazione comunque
vedi il

18

La soluzione per i valori di stringa è davvero strana:

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

L'unico motivo che funziona è perché la prima espressione OrderBy(), ordina i boolvalori: true/ false. falserisultato vai prima segui il truerisultato (nullable) e ThenBy()ordina i valori non nulli in ordine alfabetico.

Quindi, preferisco fare qualcosa di più leggibile come questo:

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

Se SomeStringè null, verrà sostituito da "z"e quindi tutto in ordine alfabetico.

NOTA: questa non è una soluzione definitiva poiché "z"va prima dei valori z come zebra.

AGGIORNAMENTO 06/09/2016 - A proposito del commento di @jornhd, è davvero una buona soluzione, ma è ancora un po 'complesso, quindi consiglierò di avvolgerlo in una classe Extension, come questa:

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

E usalo semplicemente come:

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

2
Penso che questo sia più leggibile, senza la costante cattiva: .OrderBy (f => f.SomeString! = Null? 0: 1) .ThenBy (f => f.SomeString)
jornhd

14

Ho un'altra opzione in questa situazione. La mia lista è objList e devo ordinare ma i null devono essere alla fine. la mia decisione:

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

Questo può funzionare in scenari in cui si desidera ottenere risultati con altri valori come 0 al posto di null.
Naresh Ravlani,

Sì. Basta sostituire null a 0.
Gurgen Hovsepyan,

questa è l'unica risposta che ha funzionato per me, il resto ha mantenuto i valori nulli all'inizio dell'elenco.
BMills

9

Stavo cercando di trovare una soluzione LINQ a questo, ma non sono riuscito a risolverlo dalle risposte qui.

La mia risposta finale è stata:

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

7

la mia decisione:

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

7

Questo è quello che mi è venuto in mente perché sto usando metodi di estensione e anche il mio oggetto è una stringa, quindi no .HasValue:

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

Funziona con gli oggetti LINQ 2 in memoria. Non l'ho provato con EF o nessun DB ORM.


0

Di seguito è riportato il metodo di estensione per verificare la presenza di null se si desidera ordinare la proprietà figlio di un 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);
}

E usalo semplicemente come:

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

0

Ecco un altro modo:

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

Si noti che r.SUP_APPROVED_IND.Trim() == nullviene trattato cometrim(SUP_APPROVED_IND) is null in Oracle db.

Vedi questo per i dettagli: Come posso eseguire una query per valori null nel framework di entità?


0

Un'altra opzione (utile per il nostro scenario):

Abbiamo una tabella utente, che memorizza ADName, LastName, FirstName

  • Gli utenti dovrebbero essere alfabetici
  • Account senza nome / cognome, anche in base al loro nome AD - ma alla fine dell'elenco utenti
  • Utente fittizio con ID "0" ("Nessuna selezione") Dovrebbe essere sempre il primo.

Abbiamo modificato lo schema della tabella e aggiunto una colonna "SortIndex", che definisce alcuni gruppi di ordinamento. (Abbiamo lasciato un vuoto di 5, in modo da poter inserire gruppi in seguito)

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

Ora, per quanto riguarda le query, sarebbe:

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

nelle espressioni di metodo:

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

che produce il risultato atteso:

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     
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.