Multiple «commander par» dans LINQ

1582

J'ai deux tables, movieset categories, et j'obtiens une liste ordonnée par categoryID d' abord, puis par nom .

La table de film a trois colonnes ID, Name et CategoryID . Le tableau des catégories comporte deux colonnes ID et Nom .

J'ai essayé quelque chose comme ce qui suit, mais cela n'a pas fonctionné.

var movies = _db.Movies.OrderBy( m => { m.CategoryID, m.Name })
Sasha
la source
Voici pourquoi cela ne peut pas fonctionner: L'expression lambda entre parenthèses est censée renvoyer une valeur qui peut être utilisée pour ordonner les articles: m.CategoryID est un nombre qui peut être utilisé pour ordonner les articles. Mais "m.CategoryID, m.Name" n'a pas de sens dans ce contexte.
chiccodoro
9
.Alors c'est ce que vous cherchez?
eka808
Si par hasard vous souhaitez les trier par ordre décroissant, voici la voie à suivre.
RBT

Réponses:

2831

Cela devrait fonctionner pour vous:

var movies = _db.Movies.OrderBy(c => c.Category).ThenBy(n => n.Name)
Nathan W
la source
4
Merci pour la réponse bien sûr ... Mais au lieu de Var movies = _db.Movies.Orderby(c => c.Category).ThenBy(n => n.Name) si j'utilise Var movies = _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name) 2 fois "orderBy" pourquoi le résultat est-il différent?
147
@devendra, le résultat est différent car le deuxième "OrderBy" fonctionne sur la collection qui est le résultat du premier "OrderBy" et réorganise ses articles
68
Comment diable suis-je allé tout ce temps sans le savoir ThenBy?! ( Edit: on dirait qu'il a été introduit dans .NET 4.0, ce qui explique comment il est passé devant moi sans être remarqué.)
Jordan Gray
13
Cela existe depuis l'ajout de LINQ. Cette réponse est antérieure à .NET 4.0.
Nathan W
10
Oui, j'ai conclu que trop hâtivement basé sur 3.5 ne pas être dans la liste déroulante de la version dans la page de documentation ; J'aurais dû chercher tout le long pour les informations de version. Merci pour la correction. :)
Jordan Gray
594

En utilisant LINQ non-lambda, à syntaxe de requête, vous pouvez faire ceci:

var movies = from row in _db.Movies 
             orderby row.Category, row.Name
             select row;

[MODIFIER pour répondre au commentaire] Pour contrôler l'ordre de tri, utilisez les mots-clés ascending(qui sont par défaut et donc pas particulièrement utiles) ou descending, comme ceci:

var movies = from row in _db.Movies 
             orderby row.Category descending, row.Name
             select row;
Scott Stafford
la source
1
Il n'y a pas moyen de basculer entre descendant et non dans cette syntaxe, n'est-ce pas?
ehdv
1
En fait, votre réponse est équivalente à _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Plus correct estfrom row in _db.Movies orderby row.Category descending orderby row.Name select row
Lodewijk
9
@Lodewijk: Je pense que vous avez exactement cela à l'envers. Votre exemple finira par avoir row.Name étant la colonne principale et row.Category secondaire, qui est l'équivalent de _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Les deux extraits que vous fournissez sont équivalents l'un à l'autre, pas aux PO.
Scott Stafford
6
Le seul inconvénient de l'utilisation de la syntaxe SQL pour Linq est que toutes les fonctions ne sont pas prises en charge, la plupart mais pas toutes
Joshua G
74

Ajouter "nouveau":

var movies = _db.Movies.OrderBy( m => new { m.CategoryID, m.Name })

Cela fonctionne sur ma boîte. Il retourne quelque chose qui peut être utilisé pour trier. Il renvoie un objet avec deux valeurs.

Similaire, mais différent du tri par colonne combinée, comme suit.

var movies = _db.Movies.OrderBy( m => (m.CategoryID.ToString() + m.Name))
Alex
la source
21
Soyez prudent lorsque vous utilisez cela pour les chiffres.
WoF_Angel
7
Vous pouvez utiliser OrderByDescending et ThenBy, ou OrderBy et ThenByDescending, selon vos besoins.
Ali Shah Ahmed
6
Je suis assez sûr que .OrderBy( m => new { m.CategoryID, m.Name })et .OrderBy( m => new { m.Name, m.CategoryID })produira les mêmes résultats plutôt que sur la priorité voulue. Il semblera parfois vous donner l'ordre que vous souhaitez uniquement par coïncidence. De plus m.CategoryID.ToString() + m.Name, des commandes incorrectes seront produites si CategoryID est un int. Par exemple, quelque chose avec id = 123, nom = 5 fois apparaîtra après id = 1234, nom = quelque chose au lieu d'avant. Il n'est pas non plus inefficace de faire des comparaisons de chaînes où des comparaisons int peuvent se produire.
AaronLS
7
Lorsque j'essaie de passer commande sur un type anonyme, j'obtiens une ArgumentException avec le message «Au moins un objet doit implémenter IComparable». Je vois que d'autres doivent déclarer un comparateur lors de cette opération. Voir stackoverflow.com/questions/10356864/… .
Robert Gowland
4
C'est absolument faux. La commande par un nouveau type anonyme qui n'a pas d'implémentation ICompariable ne peut pas fonctionner, car il n'y a pas d'ordre sur les propriétés d'un type anonyme. Il ne saurait pas s'il faut trier sur CategoryID d'abord ou sur Name d'abord, et encore moins s'ils devaient être triés dans des ordres opposés.
Triynko
29

utilisez la ligne suivante sur votre DataContext pour consigner l'activité SQL sur le DataContext sur la console - vous pouvez alors voir exactement ce que vos instructions linq demandent à la base de données:

_db.Log = Console.Out

Les instructions LINQ suivantes:

var movies = from row in _db.Movies 
             orderby row.CategoryID, row.Name
             select row;

ET

var movies = _db.Movies.OrderBy(m => m.CategoryID).ThenBy(m => m.Name);

produire le SQL suivant:

SELECT [t0].ID, [t0].[Name], [t0].CategoryID
FROM [dbo].[Movies] as [t0]
ORDER BY [t0].CategoryID, [t0].[Name]

Alors que la répétition d'un OrderBy dans Linq semble inverser la sortie SQL résultante:

var movies = from row in _db.Movies 
             orderby row.CategoryID
             orderby row.Name
             select row;

ET

var movies = _db.Movies.OrderBy(m => m.CategoryID).OrderBy(m => m.Name);

produire le SQL suivant (le nom et le CategoryId sont commutés):

SELECT [t0].ID, [t0].[Name], [t0].CategoryID
FROM [dbo].[Movies] as [t0]
ORDER BY [t0].[Name], [t0].CategoryID
Oliver Slay
la source
24

J'ai créé quelques méthodes d'extension (ci-dessous) afin que vous n'ayez pas à vous inquiéter si un IQueryable est déjà commandé ou non. Si vous souhaitez classer par plusieurs propriétés, procédez comme suit:

// We do not have to care if the queryable is already sorted or not. 
// The order of the Smart* calls defines the order priority
queryable.SmartOrderBy(i => i.Property1).SmartOrderByDescending(i => i.Property2);

Cela est particulièrement utile si vous créez l'ordre de manière dynamique, par exemple à partir d'une liste de propriétés à trier.

public static class IQueryableExtension
{
    public static bool IsOrdered<T>(this IQueryable<T> queryable) {
        if(queryable == null) {
            throw new ArgumentNullException("queryable");
        }

        return queryable.Expression.Type == typeof(IOrderedQueryable<T>);
    }

    public static IQueryable<T> SmartOrderBy<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
        if(queryable.IsOrdered()) {
            var orderedQuery = queryable as IOrderedQueryable<T>;
            return orderedQuery.ThenBy(keySelector);
        } else {
            return queryable.OrderBy(keySelector);
        }
    }

    public static IQueryable<T> SmartOrderByDescending<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
        if(queryable.IsOrdered()) {
            var orderedQuery = queryable as IOrderedQueryable<T>;
            return orderedQuery.ThenByDescending(keySelector);
        } else {
            return queryable.OrderByDescending(keySelector);
        }
    }
}
sjkm
la source
1
Cette réponse est or! Je vais combiner la vérification de queryable.IsOrdered () avec la réponse de ce post, pour avoir une méthode unique qui prend une direction de tri: stackoverflow.com/questions/388708
SwissCoder
1
De cette façon, l'implémentation de Linq devrait aller en premier lieu! OrderBy est mal conçu ...
Andrzej Martyna
1
@Marie, vous n'avez clairement pas compris le cas d'utilisation. Comment créer dynamiquement un classement à partir d'une liste de propriétés par exemple? C'est la seule façon. Veuillez ne pas voter et reconsidérer. Je vous remercie.
sjkm
C'est suffisant. Je ne vois toujours pas l'avantage mais je reprendrai mon vote
Marie
est-ce que cela fonctionnera avecnullable datetime
aggie
16

Il existe au moins une autre façon de procéder à l'aide de LINQ, mais ce n'est pas la plus simple. Vous pouvez le faire en utilisant la OrberBy()méthode qui utilise un IComparer. Vous devez d'abord implémenter un IComparerpour la Movieclasse comme ceci:

public class MovieComparer : IComparer<Movie>
{
    public int Compare(Movie x, Movie y)
    {
        if (x.CategoryId == y.CategoryId)
        {
            return x.Name.CompareTo(y.Name);
        }
        else
        {
            return x.CategoryId.CompareTo(y.CategoryId);
        }
    }
}

Ensuite, vous pouvez commander les films avec la syntaxe suivante:

var movies = _db.Movies.OrderBy(item => item, new MovieComparer());

Si vous avez besoin de changer l'ordre en descendant pour l'un des éléments, il suffit de changer les x et y à l'intérieur de la Compare() méthode du en MovieComparerconséquence.

prudentcoder
la source
1
J'aime que cela soit plus général que par le passé, car vous pouvez faire des choses étranges avec la comparaison, y compris avoir différents objets de comparaison avec différents algorithmes prêts à l'emploi. C'est mieux que ma solution préférée avant d'en savoir plus sur la création d'une classe qui implémente l'interface IComparable.
Gerard ONeill
1
Depuis 2012 (.NET version 4.5), vous n'avez plus à créer de classe MovieComparervous - même; à la place, vous pouvez le faire _db.Movies.OrderBy(item => item, Comparer<Movie>.Create((x, y) => { if (x.CategoryId == y.CategoryId) { return x.Name.CompareTo(y.Name); } else { return x.CategoryId.CompareTo(y.CategoryId); } }));. Bien sûr, si vous préférez écrire la logique comme une seule expression, au lieu de if... else, alors le lamda (x, y) => exprpeut être plus simple.
Jeppe Stig Nielsen
3

Si utiliser un référentiel générique

> lstModule = _ModuleRepository.GetAll().OrderBy(x => new { x.Level,
> x.Rank}).ToList();

autre

> _db.Module.Where(x=> ......).OrderBy(x => new { x.Level, x.Rank}).ToList();
Saint-martin Guillaume
la source
1
Les expressions anonymes seront analysées localement par le noyau du cadre d'entité. L'expression LINQ n'a pas pu être traduite et sera évaluée localement.
alhpe