Mehrfache "Bestellung von" in LINQ

1582

Ich habe zwei Tabellen moviesund categoriesund erhalte zuerst eine geordnete Liste nach Kategorie- ID und dann nach Name .

Die Filmtabelle enthält drei Spalten: ID, Name und CategoryID . Die Kategorietabelle hat zwei Spalten ID und Name .

Ich habe Folgendes versucht, aber es hat nicht funktioniert.

var movies = _db.Movies.OrderBy( m => { m.CategoryID, m.Name })
Sasha
quelle
Dies ist der Grund, warum dies nicht funktionieren kann: Der Lambda-Ausdruck in Klammern soll einen Wert zurückgeben, mit dem die Artikel sortiert werden können: m.CategoryID ist eine Zahl, mit der die Artikel bestellt werden können. Aber "m.CategoryID, m.Name" macht in diesem Zusammenhang keinen Sinn.
Chiccodoro
9
Dann suchen Sie?
eka808
Wenn Sie sie zufällig in absteigender Reihenfolge sortieren möchten, ist hier der richtige Weg.
RBT

Antworten:

2831

Dies sollte für Sie funktionieren:

var movies = _db.Movies.OrderBy(c => c.Category).ThenBy(n => n.Name)
Nathan W.
quelle
4
Danke natürlich für die Antwort ... Aber anstatt Var movies = _db.Movies.Orderby(c => c.Category).ThenBy(n => n.Name) wenn ich Var movies = _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name) 2 mal "orderBy" benutze, warum ist das Ergebnis anders?
147
@devendra, Ergebnis ist anders, weil das zweite "OrderBy" über die Sammlung arbeitet, die das Ergebnis des ersten "OrderBy" ist und seine Artikel neu
68
Wie um alles in der Welt bin ich die ganze Zeit gegangen, ohne es zu wissen ThenBy?! ( Bearbeiten: sieht aus wie es in .NET 4.0 eingeführt wurde, was erklärt, wie es unbemerkt an mir vorbeiging.)
Jordan Gray
13
Dies ist seit dem Hinzufügen von LINQ vorhanden. Diese Antwort ist vor .NET 4.0.
Nathan W
10
Ja, ich bin zu dem Schluss gekommen, dass zu schnell basierend auf 3.5 nicht in der Versions-Dropdown-Liste auf der Dokumentationsseite enthalten ist . Ich hätte ganz nach den Versionsinformationen suchen sollen. Danke für die Korrektur. :)
Jordan Gray
594

Mit der Nicht-Lambda-Abfragesyntax LINQ können Sie Folgendes tun:

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

[BEARBEITEN, um Kommentar zu adressieren] Um die Sortierreihenfolge zu steuern, verwenden Sie die Schlüsselwörter ascending(dies ist die Standardeinstellung und daher nicht besonders nützlich) oder descendingwie folgt :

var movies = from row in _db.Movies 
             orderby row.Category descending, row.Name
             select row;
Scott Stafford
quelle
1
Gibt es in dieser Syntax keine Möglichkeit, zwischen absteigend und nicht absteigend hin und her zu wechseln?
ehdv
1
Eigentlich ist Ihre Antwort das Äquivalent zu _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Richtiger istfrom row in _db.Movies orderby row.Category descending orderby row.Name select row
Lodewijk
9
@Lodewijk: Ich glaube du hast das genau rückwärts. In Ihrem Beispiel wird row.Name als primäre Spalte und row.Category als sekundäre Spalte angezeigt. Dies entspricht _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Die beiden von Ihnen bereitgestellten Snippets entsprechen einander und nicht den OPs.
Scott Stafford
6
Der einzige Nachteil bei der Verwendung der SQL-Syntax für Linq ist, dass nicht alle Funktionen unterstützt werden, die meisten, aber nicht alle
Joshua G
74

Neue hinzufügen":

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

Das funktioniert auf meiner Box. Es gibt etwas zurück, das zum Sortieren verwendet werden kann. Es gibt ein Objekt mit zwei Werten zurück.

Ähnlich, aber anders als beim Sortieren nach einer kombinierten Spalte wie folgt.

var movies = _db.Movies.OrderBy( m => (m.CategoryID.ToString() + m.Name))
Alex
quelle
21
Seien Sie vorsichtig, wenn Sie das für Zahlen verwenden.
WoF_Angel
7
Sie können je nach Bedarf OrderByDescending und ThenBy oder OrderBy und ThenByDescending verwenden.
Ali Shah Ahmed
6
Ich bin mir ziemlich sicher, dass .OrderBy( m => new { m.CategoryID, m.Name })und .OrderBy( m => new { m.Name, m.CategoryID })werde die gleichen Ergebnisse erzielen, anstatt die beabsichtigte Priorität zu respektieren. Manchmal scheint es Ihnen rein zufällig die gewünschte Reihenfolge zu geben. Außerdem m.CategoryID.ToString() + m.Namewerden falsche Ordnungen erzeugt, wenn CategoryID eine ist int. Beispielsweise wird etwas mit id = 123, name = 5 mal nach id = 1234, name = etwas anstelle von vorher angezeigt. Es ist auch nicht ineffizient, Zeichenfolgenvergleiche durchzuführen, bei denen int-Vergleiche auftreten können.
AaronLS
7
Wenn ich versuche, nach einem anonymen Typ zu bestellen, erhalte ich eine ArgumentException mit der Meldung "Mindestens ein Objekt muss IComparable implementieren.". Ich sehe, dass andere dabei einen Vergleicher deklarieren müssen. Siehe stackoverflow.com/questions/10356864/… .
Robert Gowland
4
Das ist absolut falsch. Die Bestellung nach einem neuen anonymen Typ ohne ICompariable-Implementierung kann nicht funktionieren, da die Eigenschaften eines anonymen Typs nicht geordnet sind. Es würde nicht wissen, ob zuerst nach CategoryID oder zuerst nach Name sortiert werden soll, geschweige denn, ob sie in entgegengesetzter Reihenfolge sortiert werden sollen.
Triynko
29

Verwenden Sie die folgende Zeile in Ihrem DataContext, um die SQL-Aktivität im DataContext in der Konsole zu protokollieren. Dann können Sie genau sehen, was Ihre linq-Anweisungen von der Datenbank anfordern:

_db.Log = Console.Out

Die folgenden LINQ-Anweisungen:

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

UND

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

Erstellen Sie die folgende SQL:

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

Während das Wiederholen eines OrderBy in Linq die resultierende SQL-Ausgabe umzukehren scheint:

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

UND

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

Erstellen Sie die folgende SQL (Name und CategoryId werden umgeschaltet):

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

Ich habe einige Erweiterungsmethoden erstellt (siehe unten), damit Sie sich keine Sorgen machen müssen, ob ein IQueryable bereits bestellt wurde oder nicht. Wenn Sie nach mehreren Eigenschaften bestellen möchten, gehen Sie wie folgt vor:

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

Dies ist besonders hilfreich, wenn Sie die Reihenfolge dynamisch erstellen, z. B. aus einer Liste von zu sortierenden Eigenschaften.

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
quelle
1
Diese Antwort ist Gold! Ich werde die Prüfung für queryable.IsOrdered () mit der Antwort von diesem Posten kombinieren, eine Methode zu haben , die eine Sortierrichtung nimmt: stackoverflow.com/questions/388708
SwissCoder
1
Auf diese Weise sollte die Linq-Implementierung an erster Stelle stehen! OrderBy ist schlecht gestaltet ...
Andrzej Martyna
1
@ Marie Sie haben den Anwendungsfall eindeutig nicht verstanden. Wie erstellen Sie beispielsweise eine Bestellung dynamisch aus einer Liste von Eigenschaften? Das ist der einzige Weg. Bitte stimmen Sie nicht ab und überdenken Sie es nicht. Vielen Dank.
sjkm
Meinetwegen. Ich sehe immer noch keinen Vorteil, aber ich werde meine Stimme zurücknehmen
Marie
wird dies funktionieren mitnullable datetime
Aggie
16

Es gibt mindestens eine weitere Möglichkeit, dies mit LINQ zu tun, obwohl dies nicht die einfachste ist. Sie können dies mit der OrberBy()Methode tun , die ein verwendet IComparer. Zuerst müssen Sie eine IComparerfür die MovieKlasse wie folgt implementieren :

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

Dann können Sie die Filme mit der folgenden Syntax bestellen:

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

Wenn Sie die Reihenfolge für eines der Elemente auf absteigend ändern müssen, ändern Sie einfach das x und y innerhalb der Compare() Methode des MovieComparer.

umsichtiger Kodierer
quelle
1
Ich mag dies als allgemeiner als damals, da Sie mit dem Vergleich seltsame Dinge tun können, einschließlich verschiedener Vergleichsobjekte mit verschiedenen Algorithmen, die bereit sind. Dies ist besser als meine bevorzugte Lösung, bevor ich etwas darüber erfahre, nämlich eine Klasse zu erstellen, die die IComparable-Schnittstelle implementiert.
Gerard ONeill
1
Seit 2012 (.NET Version 4.5) müssen Sie keine Klasse mehr MovieComparerselbst erstellen . stattdessen kannst du tun _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); } }));. Wenn Sie die Logik lieber als einen Ausdruck anstatt als if... schreiben möchten, kann elsedie Lamda natürlich (x, y) => expreinfacher sein.
Jeppe Stig Nielsen
3

Wenn Sie ein generisches Repository verwenden

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

sonst

> _db.Module.Where(x=> ......).OrderBy(x => new { x.Level, x.Rank}).ToList();
Saint-Martin Guillaume
quelle
1
Anonyme Ausdrücke werden lokal vom Entity-Framework-Kern analysiert. Der LINQ-Ausdruck konnte nicht übersetzt werden und wird lokal ausgewertet.
Alhpe