LINQ OrderBy против ThenBy

123

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

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 элементам данных?

DazManCat
источник

Ответы:

213

Вам определенно следует использовать, ThenByа не несколько OrderByвызовов.

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

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, ThenBy, похоже, не сортируются правильно, поэтому я подумал, правильно ли я его использую.
DazManCat
14
Обратите внимание, что в синтаксисе запроса ключевое слово для упорядочивания на самом деле orderby, а не order by. ( извините за педантизм - просто хотел сказать, что однажды исправил сообщение Джона Скита )
fostandy
1
Джон, мне что-то не подходит из раздела, но вам абсолютно не следует (это относится к применению нескольких порядков с использованием синтаксиса linq fluent, поскольку он переводится в ThenBy в локальных запросах): он не работает хорошо (потому что он переупорядочивает всю последовательность) - вы имеете в виду 2-й или 3-й порядок, переупорядочивая всю последовательность? если да, то как он по-прежнему будет преобразован в ThenBy после изменения порядка следования, отбрасывая предыдущий порядок?
Veverke
@Veverke: он меняет порядок всей последовательности, но стабильно, поэтому, если два значения имеют одинаковое значение z, порядок будет зависеть от y, а затем от x.
Джон Скит,
1
@Veverke: по- OrderBy(a).OrderBy(b).OrderBy(c)прежнему использует результат предыдущей сортировки и переупорядочивает все, но сохраняет существующий порядок (из предыдущего шага), в котором два элемента равны при новом сравнении. Представьте, что у нас только что есть OrderBy(a).OrderBy(b). Результаты OrderBy(a)отображаются в aпорядке возрастания , а затем они переупорядочиваются в соответствии с b. В конечном результате, если два значения имеют одинаковое bзначение, они будут упорядочены по aпричине стабильности сортировки - поэтому это эквивалентно OrderBy(b).ThenBy(a).
Джон Скит,
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;
    }
  }
}

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

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сортируется в желаемом порядке, а не повторяется снова и снова, как предупреждает другой ответ.

Крис Москини
источник
1
Или просто некоторые методы расширения stackoverflow.com/a/45486019/1300910
huysentruitw 03
1

если вы хотите отсортировать более одного поля, выберите ThenBy:

как это

list.OrderBy(personLast => person.LastName)
            .ThenBy(personFirst => person.FirstName)
Александр Залдостанов
источник
0

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

summerGhost
источник