web-dev-qa-db-fra.com

LINQ OrderBy vs ThenBy

Quelqu'un peut-il expliquer quelle est la différence entre:

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

et

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

Quelle est la bonne approche si je souhaite commander par 3 données?

110
DazManCat

Vous devriez définitivement utiliser ThenBy plutôt que plusieurs appels OrderBy. (Je suppose que l'un des extraits de votre question était censé utiliser ThenBy. Au moment de la rédaction de cet article, les deux extraits sont identiques.)

Je suggérerais ceci:

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

Notez comment vous pouvez utiliser le même nom à chaque fois. Cela équivaut également à:

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

Si vous appelez OrderBy plusieurs fois, la séquence sera réorganisée complètement trois fois ..., de sorte que le dernier appel sera effectivement dominant. Vous pouvez (dans LINQ to Objects) écrire

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

ce qui équivaudrait à

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

l'ordre de tri est stable, mais vous ne devez absolument pas:

  • C'est difficile à lire
  • Il ne fonctionne pas bien (car il réorganise la séquence entière)
  • Cela peut très bien pas fonctionner chez d’autres fournisseurs (par exemple, LINQ to SQL)
  • Ce n’est fondamentalement pas la façon dont OrderBy a été conçu pour être utilisé.

Le but de OrderBy est de fournir la "plus importante" projection de classement; utilisez ensuite ThenBy (à plusieurs reprises) pour spécifier des projections de commandes secondaires, tertiaires, etc.

Effectivement, réfléchissez-y de la manière suivante: OrderBy(...).ThenBy(...).ThenBy(...) vous permet de créer une comparaison composite unique pour deux objets quelconques, puis de trier la séquence une fois en utilisant cette comparaison composite. C'est presque certainement ce que vous voulez.

195
Jon Skeet

J'ai trouvé cette distinction agaçante en essayant de créer des requêtes de manière générique. J'ai donc créé une petite aide pour produire OrderBy/ThenBy dans le bon ordre, pour autant de sortes que vous le souhaitez.

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

Cela peut être utilisé de différentes manières en fonction de votre cas d'utilisation, mais si, par exemple, vous passez une liste de colonnes de tri et d'indications sous forme de chaînes et d'objets booléens, vous pouvez les parcourir et les utiliser comme suit:

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;

Le résultat dans sortedQuery est trié dans l'ordre souhaité, au lieu d'être utilisé encore et encore comme le dit l'autre réponse ici.

2
Chris Moschini

si vous voulez trier plus d'un champ, optez pour ThenBy:

comme ça 

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

Oui, vous ne devriez jamais utiliser plusieurs OrderBy si vous jouez avec plusieurs clés . ThenBy est un pari plus sûr puisqu'il sera performant après OrderBy.

0
summerGhost