Commande LINQ par rapport à ThenBy

123

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 éléments de données?

DazManCat
la source

Réponses:

213

Vous devriez certainement utiliser ThenByplutôt que plusieurs OrderByappels.

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 OrderByplusieurs fois, cela réorganisera complètement la séquence trois fois ... donc l'appel final sera effectivement l'appel 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)

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

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

Le but de OrderByest de fournir la projection d'ordre "la plus importante"; puis utilisez ThenBy(à plusieurs reprises) pour spécifier des projections d'ordre secondaire, tertiaire, etc.

En fait, pensez-y de cette façon: OrderBy(...).ThenBy(...).ThenBy(...)vous permet de créer une seule comparaison composite pour deux objets, puis de trier la séquence une fois en utilisant cette comparaison composite. C'est presque certainement ce que vous voulez.

Jon Skeet
la source
2
C'est ce que je pensais mais, pour une raison quelconque, OrderBy, ThenBy, ThenBy ne semble pas trier correctement, alors je me suis demandé si je l'utilisais correctement.
DazManCat
14
Notez que dans la syntaxe de la requête, le mot-clé de classement est en fait orderby et non trié. ( désolé pour le pédantisme - je voulais juste dire que j'ai une fois corrigé un message de Jon Skeet )
fostandy
1
Jon, quelque chose ne me convient pas dans la section mais vous ne devriez absolument pas (qui concerne l'application de plusieurs commandes en utilisant la syntaxe linq fluent car elle se traduit par ThenBy, dans les requêtes locales): Cela ne fonctionne pas bien (car il réorganise toute la séquence) - voulez-vous dire le 2ème ou le 3ème ordre en réorganisant toute la séquence? si tel est le cas, comment cela se traduira-t-il encore en ThenBy après avoir réordonné la séquence en supprimant l'ordre précédent?
Veverke
@Veverke: Il réorganise toute la séquence, mais de manière stable, donc si deux valeurs ont la même valeur z, l'ordre dépendra de y puis de x.
Jon Skeet
1
@Veverke: OrderBy(a).OrderBy(b).OrderBy(c)utilise toujours la sortie du tri précédent, et réorganise le tout, mais il conserve l'ordre existant (de l'étape précédente) où deux éléments sont égaux sous la nouvelle comparaison. Imaginez que nous avons juste OrderBy(a).OrderBy(b). Les résultats de OrderBy(a)sont classés par aordre croissant , puis ils sont réorganisés en fonction de b. Dans le résultat final, si deux valeurs ont la même bvaleur, elles seront classées par acar le tri est stable - c'est donc équivalent à OrderBy(b).ThenBy(a).
Jon Skeet
2

J'ai trouvé cette distinction ennuyeuse en essayant de créer des requêtes de manière générique, j'ai donc fait une petite aide pour produire OrderBy / ThenBy dans le bon ordre, pour autant de types 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;
    }
  }
}

Il existe de nombreuses façons d'utiliser cela en fonction de votre cas d'utilisation, mais si vous deviez, par exemple, passer une liste de colonnes de tri et de directions sous forme de chaînes et de booléens, vous pourriez les parcourir en boucle et les utiliser dans un commutateur comme:

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 sortedQueryest trié dans l'ordre souhaité, au lieu de recourir à plusieurs reprises, comme l'autre réponse le met en garde.

Chris Moschini
la source
1
Ou juste quelques méthodes d'extension stackoverflow.com/a/45486019/1300910
huysentruitw
1

si vous souhaitez trier plus d'un champ, choisissez ThenBy:

comme ça

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

Oui, vous ne devez jamais utiliser plusieurs OrderBy si vous jouez avec plusieurs touches. ThenBy est un pari plus sûr car il fonctionnera après OrderBy.

étéGhost
la source