LINQ OrderBy versus ThenBy

123

Alguém pode explicar qual é a diferença entre:

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 é a abordagem correta se eu desejar solicitar por 3 itens de dados?

DazManCat
fonte

Respostas:

212

Você definitivamente deve usar ThenBye não várias OrderBychamadas.

Eu sugeriria isso:

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

Observe como você pode usar o mesmo nome toda vez. Isso também é equivalente a:

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

Se você ligar OrderByvárias vezes, ele efetivamente reordenará a sequência completamente três vezes ... para que a chamada final seja efetivamente a dominante. Você pode (no LINQ to Objects) escrever

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

o que seria equivalente a

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

como a ordem de classificação é estável, mas você absolutamente não deve:

  • É difícil de ler
  • Não funciona bem (porque reordena toda a sequência)
  • Pode não funcionar em outros provedores (por exemplo, LINQ to SQL)
  • Basicamente, não é como OrderByfoi projetado para ser usado.

O objetivo de OrderByé fornecer a projeção de pedidos "mais importante"; use ThenBy(repetidamente) para especificar projeções secundárias, terciárias, etc.

Efetivamente, pense dessa maneira: OrderBy(...).ThenBy(...).ThenBy(...)permite criar uma única comparação composta para dois objetos e, em seguida, classifique a sequência uma vez usando essa comparação composta. Isso é quase certamente o que você deseja.

Jon Skeet
fonte
2
Foi o que pensei, mas, por algum motivo, o OrderBy, ThenBy, ThenBy não parece estar sendo classificado corretamente, então eu me perguntei se o estava usando corretamente.
DazManCat
14
Observe que, na sintaxe da consulta, a palavra-chave para pedidos é realmente orderby, não order by. ( Desculpe pelo pedantismo - só queria dizer uma vez eu corrigi um post Jon Skeet )
fostandy
1
Jon, algo não se encaixa para mim na seção, mas você absolutamente não deve (que se refere à aplicação de ordens múltiplas usando a sintaxe fluq do linq, uma vez que se traduz em ThenBy, em consultas locais): Ele não funciona bem (porque reordena a sequência inteira) - você quer dizer a 2ª ou a 3ª ordem reordenando a sequência inteira? se sim, como ainda será traduzido para o ThenBy depois de ter reordenado a sequência descartando a ordem anterior?
Veverke 9/08/16
@Veverke: Reordena toda a sequência, mas de maneira estável, portanto, se dois valores tiverem o mesmo valor z, a ordem dependerá de y e, em seguida, de x.
Jon Skeet
1
@Veverke: OrderBy(a).OrderBy(b).OrderBy(c)ainda usa a saída da classificação anterior e reordena a coisa toda, mas preserva a ordem existente (da etapa anterior) em que dois elementos são iguais na nova comparação. Imagine que apenas temos OrderBy(a).OrderBy(b). Os resultados de OrderBy(a)estão em aordem crescente e, em seguida, são reordenados de acordo com b. No resultado final, se dois valores tiverem o mesmo bvalor, eles serão ordenados adevido à classificação ser estável - portanto, é equivalente a OrderBy(b).ThenBy(a).
Jon Skeet
2

Achei essa distinção irritante ao tentar criar consultas de maneira genérica, então ajudei um pouco a produzir OrderBy / ThenBy na ordem correta, para quantos tipos você quiser.

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

Há várias maneiras de você usar isso dependendo do seu caso de uso, mas se, por exemplo, você passar uma lista de colunas e direções de classificação como strings e bools, poderá fazer um loop sobre elas e usá-las em uma opção como:

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;

O resultado em sortedQueryé classificado na ordem desejada, em vez de recorrer repetidamente, como a outra resposta aqui alerta.

Chris Moschini
fonte
1
Ou apenas alguns métodos de extensão stackoverflow.com/a/45486019/1300910
huysentruitw
1

se você quiser classificar mais de um campo, vá para ThenBy:

como isso

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

Sim, você nunca deve usar múltiplos OrderBy se estiver tocando com várias teclas. ThenBy é uma aposta mais segura, pois será executada após OrderBy.

summerGhost
fonte