LINQ OrderBy versus ThenBy

123

Kann jemand erklären, was der Unterschied ist zwischen:

tmp = invoices.InvoiceCollection
              .OrderBy(sort1 => sort1.InvoiceOwner.LastName)
              .OrderBy(sort2 => sort2.InvoiceOwner.FirstName)
              .OrderBy(sort3 => sort3.InvoiceID);

und

tmp = invoices.InvoiceCollection
              .OrderBy(sort1 => sort1.InvoiceOwner.LastName)
              .ThenBy(sort2 => sort2.InvoiceOwner.FirstName)
              .ThenBy(sort3 => sort3.InvoiceID);

Welches ist der richtige Ansatz, wenn ich nach 3 Daten bestellen möchte?

DazManCat
quelle

Antworten:

212

Sie sollten auf jeden Fall verwenden , ThenByanstatt mehrOrderBy Anrufe.

Ich würde dies vorschlagen:

tmp = invoices.InvoiceCollection
              .OrderBy(o => o.InvoiceOwner.LastName)
              .ThenBy(o => o.InvoiceOwner.FirstName)
              .ThenBy(o => o.InvoiceID);

Beachten Sie, wie Sie jedes Mal denselben Namen verwenden können. Dies entspricht auch:

tmp = from o in invoices.InvoiceCollection
      orderby o.InvoiceOwner.LastName,
              o.InvoiceOwner.FirstName,
              o.InvoiceID
      select o;

Wenn Sie OrderBymehrmals anrufen , wird die Sequenz effektiv dreimal vollständig neu angeordnet. Der letzte Anruf ist also effektiv der dominierende. Sie können (in LINQ to Objects) schreiben

foo.OrderBy(x).OrderBy(y).OrderBy(z)

das wäre gleichbedeutend mit

foo.OrderBy(z).ThenBy(y).ThenBy(x)

da die Sortierreihenfolge stabil ist, sollten Sie aber unbedingt nicht:

  • Es ist schwer zu lesen
  • Es funktioniert nicht gut (weil es die gesamte Sequenz neu ordnet)
  • Es kann gut nicht funktioniert bei anderen Anbietern (z. B. LINQ to SQL).
  • Es ist im Grunde nicht so, wie OrderByes verwendet werden soll.

Es OrderBygeht darum, die "wichtigste" Ordnungsprojektion bereitzustellen. dann benutzeThenBy (wiederholt), um sekundäre, tertiäre usw. Projektionen anzugeben.

Stellen Sie sich das effektiv so vor: OrderBy(...).ThenBy(...).ThenBy(...)Sie können einen einzelnen zusammengesetzten Vergleich für zwei beliebige Objekte erstellen und die Sequenz dann einmal mit diesem zusammengesetzten Vergleich sortieren . Das ist mit ziemlicher Sicherheit das, was Sie wollen.

Jon Skeet
quelle
2
Das habe ich mir gedacht, aber aus irgendeinem Grund scheint OrderBy, ThenBy, ThenBy nicht richtig zu sortieren, also habe ich mich gefragt, ob ich es richtig verwendet habe.
DazManCat
14
Beachten Sie, dass in der Abfragesyntax das Schlüsselwort für die Bestellung tatsächlich "orderby" und nicht "order by" lautet. ( Entschuldigung für die Pedanterie - ich wollte nur sagen, dass ich einmal einen Jon Skeet-Beitrag korrigiert habe )
Fostandy
1
Jon, etwas passt nicht zu mir aus dem Abschnitt, aber du solltest es unbedingt nicht (das bezieht sich auf das Anwenden von Bys mit mehreren Ordnungen unter Verwendung der fließenden Linq-Syntax, da es in lokalen Abfragen in ThenBy übersetzt wird): Es funktioniert nicht gut (weil es ordnet die gesamte Sequenz neu an) - meinen Sie die 2. oder 3. Ordnung, indem Sie die gesamte Sequenz neu ordnen? Wenn ja, wie wird es nach der Neuordnung der Sequenz, bei der die vorhergehende Reihenfolge verworfen wurde, immer noch in ThenBy übersetzt?
Veverke
@Veverke: Es ordnet die gesamte Sequenz neu, aber auf stabile Weise. Wenn also zwei Werte den gleichen z-Wert haben, hängt die Reihenfolge von y und dann von x ab.
Jon Skeet
1
@Veverke: Verwendet OrderBy(a).OrderBy(b).OrderBy(c)immer noch die Ausgabe der vorhergehenden Sortierung und ordnet das Ganze neu an, behält jedoch die vorhandene Reihenfolge (aus dem vorhergehenden Schritt) bei, in der zwei Elemente unter dem neuen Vergleich gleich sind. Stellen Sie sich vor, wir haben nur OrderBy(a).OrderBy(b). Die Ergebnisse von OrderBy(a)sind in aufsteigender aReihenfolge, und dann werden diese entsprechend neu geordnet b. Wenn im Endergebnis zwei Werte den gleichen bWert haben, werden sie nach sortiert, ada die Sortierung stabil ist - das entspricht also OrderBy(b).ThenBy(a).
Jon Skeet
2

Ich fand diese Unterscheidung ärgerlich, als ich versuchte, Abfragen generisch zu erstellen, und machte einen kleinen Helfer, um OrderBy / ThenBy in der richtigen Reihenfolge für so viele Arten zu produzieren, wie Sie möchten.

public class EFSortHelper
{
  public static EFSortHelper<TModel> Create<TModel>(IQueryable<T> query)
  {
    return new EFSortHelper<TModel>(query);
  }
}  

public class EFSortHelper<TModel> : EFSortHelper
{
  protected IQueryable<TModel> unsorted;
  protected IOrderedQueryable<TModel> sorted;

  public EFSortHelper(IQueryable<TModel> unsorted)
  {
    this.unsorted = unsorted;
  }

  public void SortBy<TCol>(Expression<Func<TModel, TCol>> sort, bool isDesc = false)
  {
    if (sorted == null)
    {
      sorted = isDesc ? unsorted.OrderByDescending(sort) : unsorted.OrderBy(sort);
      unsorted = null;
    }
    else
    {
      sorted = isDesc ? sorted.ThenByDescending(sort) : sorted.ThenBy(sort)
    }
  }

  public IOrderedQueryable<TModel> Sorted
  {
    get
    {
      return sorted;
    }
  }
}

Es gibt viele Möglichkeiten, wie Sie dies je nach Anwendungsfall verwenden können. Wenn Sie jedoch beispielsweise eine Liste mit Sortierspalten und -anweisungen als Zeichenfolgen und Bools übergeben bekommen, können Sie diese durchlaufen und in einem Schalter wie den folgenden verwenden.

var query = db.People.AsNoTracking();
var sortHelper = EFSortHelper.Create(query);
foreach(var sort in sorts)
{
  switch(sort.ColumnName)
  {
    case "Id":
      sortHelper.SortBy(p => p.Id, sort.IsDesc);
      break;
    case "Name":
      sortHelper.SortBy(p => p.Name, sort.IsDesc);
      break;
      // etc
  }
}

var sortedQuery = sortHelper.Sorted;

Das Ergebnis in sortedQuerywird in der gewünschten Reihenfolge sortiert, anstatt immer wieder darauf zurückzugreifen, wie die andere Antwort hier warnt.

Chris Moschini
quelle
1
Oder nur einige Erweiterungsmethoden stackoverflow.com/a/45486019/1300910
huysentruitw
1

Wenn Sie mehr als ein Feld sortieren möchten, wählen Sie ThenBy:

so was

list.OrderBy(personLast => person.LastName)
            .ThenBy(personFirst => person.FirstName)
Alexander Zaldostanov
quelle
0

Ja, Sie sollten niemals mehrere OrderBy verwenden, wenn Sie mit mehreren Tasten spielen. ThenBy ist sicherer, da es nach OrderBy ausgeführt wird.

Sommergeist
quelle