LINQ OrderBy порівняно з потімBy


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 дані?

Відповіді:


212

Вам неодмінно слід використовувати 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 до об'єктів) писати

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

що було б рівнозначно

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

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

  • Це важко читати
  • Це не дуже добре (тому що вона переробляє всю послідовність)
  • Він може не працювати в інших постачальників (наприклад, LINQ до SQL)
  • Це в основному не те, як OrderByбуло розроблено для використання.

Сенс OrderByполягає у наданні "найважливішої" замовлення проекції; потім використовуйте ThenBy(повторно) для вказівки вторинних, третинних тощо замовлення проекцій.

Ефективно, подумайте про це так: OrderBy(...).ThenBy(...).ThenBy(...)дозволяє побудувати єдине складене порівняння для будь-яких двох об'єктів, а потім сортувати послідовність один раз, використовуючи це складене порівняння. Це майже точно, що ви хочете.


2
Ось що я подумав, але, чомусь, OrderBy, OndaBy, OndaBy не здається сортувати правильно, тому я задумався, чи правильно я його використовую.
DazManCat

14
Зауважте, що в синтаксисі запитів ключове слово для впорядкування насправді orderby, а не order by. ( вибачте за педантичність - просто хотів сказати, що одного разу виправив пост Джона Скіта )
fostandy

1
Джон, для мене щось не підходить, але ви абсолютно не повинні розділяти (це стосується застосування декількох байдів порядку, використовуючи синтаксис fluq fluent, оскільки він перекладається на OndaBy, в локальних запитах): Це не працює добре (тому що це переупорядковує всю послідовність) - ви маєте на увазі 2-й чи 3-й порядки під перепорядкуванням всієї послідовності? якщо так, то як він все ще переведеться на OndaBy після переупорядкування послідовності, що відкидає попереднє замовлення?
Веверке

@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 / OndaBy у належному порядку, на стільки різновидів, скільки вам подобається.

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

1

якщо ви хочете відсортувати більше одного поля, тоді перейдіть до OndaBy:

подобається це

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

0

Так, ніколи не слід використовувати кілька OrderBy, якщо ви граєте з декількома клавішами. ТодіBy - більш безпечна ставка, оскільки вона буде виконуватись після OrderBy.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.