LINQ OrderBy versus ThenBy

123

¿Alguien puede explicar cuál es la diferencia entre:

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

y

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

¿Cuál es el enfoque correcto si deseo ordenar por 3 elementos de datos?

DazManCat
fuente

Respuestas:

212

Definitivamente debe usar en ThenBylugar de múltiples OrderByllamadas.

Sugeriría esto:

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

Observe cómo puede usar el mismo nombre cada vez. Esto también es equivalente a:

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

Si llama OrderByvarias veces, efectivamente reordenará la secuencia por completo tres veces ... por lo que la llamada final será efectivamente la dominante. Usted puede (en LINQ a Objetos) de escritura

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

que sería equivalente a

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

ya que el orden de clasificación es estable, pero absolutamente no deberías:

  • Es dificil de leer
  • No funciona bien (porque reordena toda la secuencia)
  • Es posible que no funcione en otros proveedores (por ejemplo, LINQ to SQL)
  • Básicamente no es cómo OrderByfue diseñado para ser utilizado.

El objetivo OrderByes proporcionar la proyección de orden "más importante"; luego use ThenBy(repetidamente) para especificar proyecciones de pedidos secundarios, terciarios, etc.

Efectivamente, piénselo de esta manera: le OrderBy(...).ThenBy(...).ThenBy(...)permite construir una sola comparación compuesta para cualquiera de los dos objetos, y luego ordenar la secuencia una vez usando esa comparación compuesta. Eso es casi seguro lo que quieres.

Jon Skeet
fuente
2
Eso es lo que pensé pero, por alguna razón, OrderBy, ThenBy, ThenBy no parece estar ordenando correctamente, así que me pregunté si lo estaba usando correctamente.
DazManCat
14
Tenga en cuenta que en la sintaxis de consulta, la palabra clave para ordenar es en realidad ordenar por, no ordenar por. ( Perdón por la pedantería, solo quería decir que una vez
corrigí
1
Jon, algo no encaja para mí en la sección, pero absolutamente no deberías (que se relaciona con la aplicación de múltiples órdenes usando la sintaxis fluida de linq, ya que se traduce en ThenBy, en consultas locales): no funciona bien (porque reordena toda la secuencia) : ¿quiere decir que el segundo o tercer orden reordena toda la secuencia? Si es así, ¿cómo se traducirá a ThenBy después de haber reordenado la secuencia descartando el orden anterior?
Veverke el
@Veverke: reordena toda la secuencia, pero de manera estable, por lo que si dos valores tienen el mismo valor z, el orden dependerá de y y luego de x.
Jon Skeet
1
@Veverke: OrderBy(a).OrderBy(b).OrderBy(c)todavía usa la salida del orden anterior y reordena todo, pero conserva el orden existente (del paso anterior) donde dos elementos son iguales bajo la nueva comparación. Imagina que solo tenemos OrderBy(a).OrderBy(b). Los resultados de OrderBy(a)están en aorden creciente , y luego se reordenan según b. En el resultado final, si dos valores tienen el mismo bvalor, se ordenarán por ael tipo estable, por lo que es equivalente a OrderBy(b).ThenBy(a).
Jon Skeet
2

Encontré esta distinción molesta al tratar de generar consultas de manera genérica, por lo que hice un pequeño ayudante para producir OrderBy / ThenBy en el orden correcto, para todos los tipos que desee.

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

Hay muchas formas en que puede usar esto dependiendo de su caso de uso, pero si se le pasa, por ejemplo, una lista de columnas y direcciones de clasificación como cadenas y bools, puede recorrerlas y usarlas en un interruptor 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;

El resultado en sortedQueryse ordena en el orden deseado, en lugar de recurrir una y otra vez como la otra respuesta aquí advierte.

Chris Moschini
fuente
1
O simplemente algunos métodos de extensión stackoverflow.com/a/45486019/1300910
huysentruitw
1

si desea ordenar más de un campo, vaya a ThenBy:

Me gusta esto

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

Sí, nunca deberías usar múltiples OrderBy si estás jugando con múltiples teclas. EntoncesBy es una apuesta más segura, ya que se realizará después de OrderBy.

veranoGhost
fuente