LINQ OrderBy a ThenBy

123

Czy ktoś może wyjaśnić, jaka jest różnica między:

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

i

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

Jakie jest właściwe podejście, jeśli chcę zamówić 3 pozycje?

DazManCat
źródło

Odpowiedzi:

213

Zdecydowanie powinieneś używać ThenByzamiast wielu OrderBypołączeń.

Sugerowałbym to:

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

Zwróć uwagę, jak za każdym razem możesz używać tej samej nazwy. Jest to również równoważne z:

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

Jeśli sprawdzisz OrderBywiele razy, skutecznie zmieni to kolejność całkowicie trzy razy ... więc ostateczne wezwanie będzie w rzeczywistości dominującym. Państwo może (w LINQ to Objects) zapisu

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

co byłoby równoważne z

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

ponieważ porządek sortowania jest stabilny, ale absolutnie nie powinieneś:

  • Trudno to czytać
  • Nie działa dobrze (ponieważ zmienia kolejność całej sekwencji)
  • Może nie działać w innych dostawcach (np. LINQ to SQL)
  • W zasadzie nie jest to sposób, w jaki OrderByzostał zaprojektowany do użytku.

Chodzi o OrderBydostarczenie „najważniejszej” projekcji porządkującej; następnie użyj ThenBy(wielokrotnie), aby określić projekcje zamawiania drugorzędnych, trzeciorzędnych itp.

Pomyśl o tym w ten sposób: OrderBy(...).ThenBy(...).ThenBy(...)pozwala zbudować pojedyncze porównanie złożone dla dowolnych dwóch obiektów, a następnie posortować sekwencję raz, używając tego porównania złożonego. Prawie na pewno tego chcesz.

Jon Skeet
źródło
2
Tak myślałem, ale z jakiegoś powodu OrderBy, ThenBy, ThenBy nie wydaje się sortować poprawnie, więc zastanawiałem się, czy używam go poprawnie.
DazManCat
14
Zauważ, że w składni zapytania słowem kluczowym służącym do porządkowania jest w rzeczywistości sortowanie, a nie sortowanie według. ( przepraszam za pedanterię - chciałem tylko powiedzieć, że kiedyś poprawiłem post Jona Skeeta )
fostandy.
1
Jon, coś nie pasuje do mnie z sekcji, ale absolutnie nie powinieneś jej używać (dotyczy to stosowania wielu porządków przy użyciu płynnej składni linq, ponieważ w zapytaniach lokalnych przekłada się na ThenBy): Nie działa dobrze (ponieważ zmienia kolejność całej sekwencji) - czy masz na myśli drugie lub trzecie zamówienie, zmieniając kolejność całej sekwencji? jeśli tak, w jaki sposób będzie to nadal przekładać się na ThenBy po ponownym uporządkowaniu sekwencji, odrzucając poprzednią kolejność?
Veverke,
@Veverke: Zmienia kolejność całej sekwencji, ale w stabilny sposób, więc jeśli dwie wartości mają tę samą wartość z, kolejność będzie zależeć od y, a następnie od x.
Jon Skeet,
1
@Veverke: OrderBy(a).OrderBy(b).OrderBy(c)nadal używa wyjścia poprzedniego rodzaju i zmienia kolejność całości, ale zachowuje istniejącą kolejność (z poprzedniego kroku), w której dwa elementy są równe w nowym porównaniu. Wyobraź sobie, że właśnie to zrobiliśmy OrderBy(a).OrderBy(b). Wyniki OrderBy(a)są w akolejności rosnącej , a następnie są one uporządkowane zgodnie z b. W ostatecznym wyniku, jeśli dwie wartości mają tę samą bwartość, zostaną uporządkowane według, aponieważ sortowanie jest stabilne - więc jest równoważne OrderBy(b).ThenBy(a).
Jon Skeet
2

To rozróżnienie było dla mnie irytujące, próbując budować zapytania w sposób ogólny, więc zrobiłem mały pomocnik, aby utworzyć OrderBy / ThenBy we właściwej kolejności, dla dowolnej liczby rodzajów.

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

Istnieje wiele sposobów, w jakie możesz tego użyć, w zależności od przypadku użycia, ale jeśli na przykład przekazałeś listę kolumn sortowania i kierunków jako łańcuchy i wartości logiczne, możesz je zapętlić i użyć w przełączniku, takim jak:

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;

Wynik sortedQueryjest sortowany w żądanej kolejności, zamiast ciągłego uciekania się, jak ostrzega druga odpowiedź.

Chris Moschini
źródło
1
Lub po prostu niektóre metody rozszerzeń stackoverflow.com/a/45486019/1300910
huysentruitw
1

jeśli chcesz posortować więcej niż jedno pole, przejdź do ThenBy:

lubię to

list.OrderBy(personLast => person.LastName)
            .ThenBy(personFirst => person.FirstName)
Alexander Zaldostanov
źródło
0

Tak, nigdy nie powinieneś używać wielu OrderBy, jeśli grasz z wieloma klawiszami. ThenBy jest bezpieczniejszym zakładem, ponieważ będzie działać po OrderBy.

summerGhost
źródło