LINQ OrderBy contro ThenBy


123

Qualcuno può spiegare qual è la differenza tra:

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

e

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

Qual è l'approccio corretto se desidero ordinare per 3 elementi di dati?

Risposte:


213

Dovresti assolutamente usare ThenBypiuttosto che più OrderBychiamate.

Suggerirei questo:

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

Nota come puoi usare lo stesso nome ogni volta. Questo è anche equivalente a:

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

Se chiami OrderBypiù volte, riordinerà effettivamente la sequenza completamente tre volte ... quindi la chiamata finale sarà effettivamente quella dominante. È possibile (in LINQ to Objects) scrivere

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

che sarebbe equivalente a

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

poiché l'ordinamento è stabile, ma non dovresti assolutamente:

  • È difficile da leggere
  • Non funziona bene (perché riordina l'intera sequenza)
  • Potrebbe non funzionare in altri provider (ad esempio LINQ to SQL)
  • Fondamentalmente non è come è OrderBystato progettato per essere utilizzato.

Lo scopo di OrderByè fornire la proiezione di ordinamento "più importante"; quindi utilizzare ThenBy(ripetutamente) per specificare le proiezioni di ordinamento secondarie, terziarie ecc.

In effetti, pensala in questo modo: OrderBy(...).ThenBy(...).ThenBy(...)ti consente di creare un singolo confronto composito per due oggetti qualsiasi, quindi ordinare la sequenza una volta utilizzando quel confronto composito. Quasi certamente è quello che vuoi.


2
Questo è quello che pensavo ma, per qualche motivo, OrderBy, ThenBy, ThenBy non sembra essere ordinato correttamente, quindi mi sono chiesto se lo stavo usando correttamente.
DazManCat

14
Si noti che nella sintassi della query la parola chiave per l'ordinamento è in realtà orderby, non order by. ( scusa per la pedanteria - volevo solo dire che una volta ho corretto un post di Jon Skeet )
fostandy

1
Jon, qualcosa non va bene per me dalla sezione ma non dovresti assolutamente (che si riferisce all'applicazione di più by di ordine usando la sintassi fluente di linq poiché si traduce in ThenBy, nelle query locali): non funziona bene (perché riordina l'intera sequenza) - intendi il 2 ° o 3 ° ordine riordina l'intera sequenza? in caso affermativo, come si tradurrà ancora in ThenBy dopo aver riordinato la sequenza scartando l'ordinamento precedente?
Veverke

@Veverke: riordina l'intera sequenza, ma in modo stabile, quindi se due valori hanno lo stesso valore z, l'ordinamento dipenderà da y e quindi da x.
Jon Skeet

1
@Veverke: OrderBy(a).OrderBy(b).OrderBy(c)usa ancora l'output dell'ordinamento precedente e riordina l'intera cosa, ma conserva l'ordine esistente (dal passaggio precedente) in cui due elementi sono uguali nel nuovo confronto. Immagina di averlo appena fatto OrderBy(a).OrderBy(b). I risultati di OrderBy(a)sono in aordine crescente , quindi quelli vengono riordinati in base a b. Nel risultato finale, se due valori hanno lo stesso bvalore, verranno ordinati in base aal fatto che l'ordinamento è stabile, quindi è equivalente a OrderBy(b).ThenBy(a).
Jon Skeet

2

Ho trovato questa distinzione fastidiosa nel tentativo di creare query in modo generico, quindi ho creato un piccolo aiuto per produrre OrderBy / ThenBy nell'ordine corretto, per tutti i tipi che desideri.

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

Ci sono molti modi in cui potresti usarlo a seconda del tuo caso d'uso, ma se ad esempio ti venisse passato un elenco di colonne di ordinamento e direzioni come stringhe e bool, potresti scorrere su di esse e usarle in uno switch come:

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;

Il risultato sortedQueryviene ordinato nell'ordine desiderato, invece di ricorrere più e più volte come avverte l'altra risposta qui.


1
O solo alcuni metodi di estensione stackoverflow.com/a/45486019/1300910
huysentruitw

1

se vuoi ordinare più di un campo, scegli ThenBy:

come questo

list.OrderBy(personLast => person.LastName)
            .ThenBy(personFirst => person.FirstName)

0

Sì, non dovresti mai usare più OrderBy se stai giocando con più chiavi. ThenBy è una scommessa più sicura poiché si esibirà dopo OrderBy.

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.