Commande LINQ par colonne nulle où l'ordre est croissant et les valeurs nulles doivent être les dernières

141

J'essaye de trier une liste de produits par leur prix.

L'ensemble de résultats doit répertorier les produits par prix de bas en haut par colonne LowestPrice. Cependant, cette colonne peut être Nullable.

Je peux trier la liste par ordre décroissant comme ceci:

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

Cependant, je ne peux pas comprendre comment trier cela par ordre croissant.

// i'd like: 100, 101, 102, null, null
sf.
la source
11
orderby p.LowestPrice ?? Int.MaxValue;est un moyen simple.
PostMan
3
@PostMan: Oui, c'est simple, cela donne le bon résultat, mais OrderByDescending, ThenByc'est plus clair.
jason
@Jason, ouais, je ne connaissais pas la syntaxe du orderby, et je me suis retrouvé à la recherche de côté :)
PostMan

Réponses:

161

Essayez de mettre les deux colonnes dans le même ordre

orderby p.LowestPrice.HasValue descending, p.LowestPrice

Sinon, chaque commande est une opération distincte sur la collection qui la réorganise à chaque fois.

Cela devrait ordonner ceux avec une valeur en premier, "puis" l'ordre de la valeur.

DaveShaw
la source
22
Erreur courante, les gens font de même avec la syntaxe Lamda - en utilisant .OrderBy deux fois au lieu de .ThenBy.
DaveShaw
1
Cela a fonctionné pour commander des champs avec des valeurs en haut et des champs nuls en bas, j'ai utilisé ceci: orderby p.LowestPrice == null, p.LowestPrice ascending Hope aide quelqu'un.
shaijut du
@DaveShaw merci pour le tuyau - en particulier le commentaire - très bien rangé - j'adore
Demetris Leptos
86

Cela aide vraiment à comprendre la syntaxe de requête LINQ et comment elle est traduite en appels de méthode LINQ.

Il se trouve que

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

sera traduit par le compilateur en

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

Ce n'est absolument pas ce que vous voulez. Ce genre de Product.LowestPrice.HasValuedans l' descendingordre et puis re-trie toute la collection par Product.LowestPricedans l' descendingordre.

Ce que tu veux c'est

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

que vous pouvez obtenir en utilisant la syntaxe de requête en

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

Pour plus de détails sur les traductions de la syntaxe de requête aux appels de méthode, consultez la spécification du langage. Sérieusement. Lis le.

Jason
la source
4
+1 ou tout simplement ... ne pas écrire la syntaxe de requête LINQ :) Bonne explication néanmoins
sehe
18

La solution pour les valeurs de chaîne est vraiment bizarre:

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

La seule raison qui fonctionne est que la première expression OrderBy(),, trie les boolvaleurs: true/ false. falseresult aller d'abord suivi du truerésultat (nullables) et ThenBy()trier les valeurs non nulles par ordre alphabétique.

Donc, je préfère faire quelque chose de plus lisible comme celui-ci:

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

Si SomeStringest nul, il sera remplacé par "z", puis triera tout par ordre alphabétique.

REMARQUE: ce n'est pas une solution ultime car "z"passe en premier par les valeurs z comme zebra.

MISE À JOUR 9/6/2016 - À propos du commentaire @jornhd, c'est vraiment une bonne solution, mais cela reste un peu complexe, je recommanderai donc de l'envelopper dans une classe d'extension, comme celle-ci:

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

Et utilisez-le simplement comme:

var sortedList = list.NullableOrderBy(f => f.SomeString);
Jaider
la source
2
Je pense que c'est plus lisible, sans la constante méchante: .OrderBy (f => f.SomeString! = Null? 0: 1) .ThenBy (f => f.SomeString)
jornhd
14

J'ai une autre option dans cette situation. Ma liste est objList, et je dois commander mais les valeurs nulles doivent être à la fin. ma décision:

var newList = objList.Where(m=>m.Column != null)
                     .OrderBy(m => m.Column)
                     .Concat(objList.where(m=>m.Column == null));
Gurgen Hovsepyan
la source
Cela peut fonctionner dans des scénarios où l'on veut un résultat avec d'autres valeurs comme 0 au lieu de null.
Naresh Ravlani
Oui. Remplacez simplement nul par 0.
Gurgen Hovsepyan
c'est la seule réponse qui a fonctionné pour moi, le reste a gardé les valeurs nulles au début de la liste.
BMills
9

J'essayais de trouver une solution LINQ à cela mais je n'ai pas pu la résoudre à partir des réponses ici.

Ma réponse finale a été:

.OrderByDescending(p => p.LowestPrice.HasValue).ThenBy(p => p.LowestPrice)
utilisateur1
la source
7

ma décision:

Array = _context.Products.OrderByDescending(p => p.Val ?? float.MinValue)
RTK
la source
7

C'est ce que j'ai trouvé parce que j'utilise des méthodes d'extension et que mon élément est une chaîne, donc non .HasValue:

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

Cela fonctionne avec les objets LINQ 2 en mémoire. Je ne l'ai pas testé avec EF ou tout autre ORM DB.

AaronLS
la source
0

Vous trouverez ci-dessous une méthode d'extension pour vérifier la valeur null si vous souhaitez trier sur la propriété enfant d'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);
}

Et utilisez-le simplement comme:

var sortedList = list.NullableOrderBy(x => x.someObject, y => y.someObject?.someProperty);
Manish Patel
la source
0

Voici une autre façon:

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

Notez qu'il r.SUP_APPROVED_IND.Trim() == nullest traité commetrim(SUP_APPROVED_IND) is null dans Oracle db.

Voir ceci pour plus de détails: Comment puis-je rechercher des valeurs nulles dans le cadre d'entité?

Léonid Minkov
la source
0

Une autre option (était pratique pour notre scénario):

Nous avons une table utilisateur, stockant ADName, LastName, FirstName

  • Les utilisateurs doivent être alphabétiques
  • Les comptes sans prénom / nom également, en fonction de leur ADName - mais à la fin de la liste d'utilisateurs
  • Utilisateur factice avec ID "0" ("Aucune sélection") Doit toujours être le plus haut.

Nous avons modifié le schéma de la table et ajouté une colonne "SortIndex", qui définit certains groupes de tri. (Nous avons laissé un espace de 5, donc nous pouvons insérer des groupes plus tard)

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

Maintenant, au niveau des requêtes, ce serait:

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

dans les expressions de méthode:

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

ce qui donne le résultat attendu:

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
la source