LINQ OrderBy против ThenBy

Может ли кто-нибудь объяснить, в чем разница между:

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

и

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

Какой правильный подход, если я хочу заказать по 3 элементам данных?

Ответ 1

Вы должны использовать ThenBy вместо нескольких вызовов OrderBy. (Я предполагаю, что один из фрагментов в вашем вопросе должен был использовать ThenBy. На момент написания этого документа два фрагмента идентичны.)

Я бы предложил следующее:

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

Обратите внимание, как вы можете использовать одно и то же имя каждый раз. Это также эквивалентно:

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

Если вы назовете OrderBy несколько раз, он будет эффективно переупорядочить последовательность всего три раза... так что окончательный вызов будет фактически доминирующим. Вы можете (в LINQ to Objects) написать

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

который был бы эквивалентен

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

поскольку порядок сортировки стабилен, но вы абсолютно не должны:

  • Трудно прочитать
  • Он не работает хорошо (потому что он переупорядочивает всю последовательность)
  • Он может не работать в других провайдерах (например, LINQ to SQL)
  • В основном это не то, как OrderBy был предназначен для использования.

Точка OrderBy заключается в предоставлении "самой важной" проекции проектирования; затем используйте ThenBy (повторно), чтобы указать вторичные, третичные и другие заказы на заказы.

Эффективно подумайте об этом следующим образом: OrderBy(...).ThenBy(...).ThenBy(...) позволяет вам построить единое композитное сравнение для любых двух объектов, а затем отсортировать последовательность после использования этого комбинированного сравнения. Это почти наверняка то, что вы хотите.

Ответ 2

Я обнаружил это различие, раздражающее попытку создания запросов в общем виде, поэтому я сделал небольшой помощник для создания OrderBy/ThenBy в правильном порядке, для любого количества, которое вам нравится.

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

Существует много способов использовать это в зависимости от вашего варианта использования, но если вы, например, передали список столбцов и направлений сортировки в виде строк и bools, вы можете их перебрать и использовать в переключателе, например

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;

Результат в sortedQuery сортируется в желаемом порядке, вместо того, чтобы прибегать к повторению, поскольку другой ответ здесь предостерегает.

Ответ 3

если вы хотите отсортировать несколько полей, затем перейдите к ThenBy:

как это

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

Ответ 4

Да, вы не должны использовать несколько OrderBy, если играете с несколькими ключами. ThenBy более безопасно, так как он будет выполнять после OrderBy.