144

Can anyone explain what the difference is between:

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

and

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

Which is the correct approach if I wish to order by 3 items of data?

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
DazManCat
  • 3,540
  • 5
  • 30
  • 33

4 Answers4

243

You should definitely use ThenBy rather than multiple OrderBy calls.

I would suggest this:

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

Note how you can use the same name each time. This is also equivalent to:

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

If you call OrderBy multiple times, it will effectively reorder the sequence completely three times... so the final call will effectively be the dominant one. You can (in LINQ to Objects) write

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

which would be equivalent to

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

as the sort order is stable, but you absolutely shouldn't:

  • It's hard to read
  • It doesn't perform well (because it reorders the whole sequence)
  • It may well not work in other providers (e.g. LINQ to SQL)
  • It's basically not how OrderBy was designed to be used.

The point of OrderBy is to provide the "most important" ordering projection; then use ThenBy (repeatedly) to specify secondary, tertiary etc ordering projections.

Effectively, think of it this way: OrderBy(...).ThenBy(...).ThenBy(...) allows you to build a single composite comparison for any two objects, and then sort the sequence once using that composite comparison. That's almost certainly what you want.

ThatShawGuy
  • 1,363
  • 1
  • 14
  • 27
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    Thats what I thought but, for some reason the OrderBy,ThenBy,ThenBy does not seem to be sorting correctly so I wondered if I was using it correctly. – DazManCat Sep 22 '10 at 09:48
  • 18
    Note that in query syntax the keyword for ordering is actually orderby, not order by. (_sorry for the pedantry - just wanted to say I once corrected a Jon Skeet post_) – fostandy Aug 19 '14 at 01:33
  • 1
    Jon, something does not fit together for me from the *but you absolutely shouldn't* section (that relates to applying multiple order bys using linq fluent syntax since it translates to ThenBy, in local queries): *It doesn't perform well (because it reorders the whole sequence)* - do you mean the 2nd or 3rd order by reorders the whole sequence ? if so, how will it still translate to ThenBy after having re-ordered the sequence discarding the preceding ordering ? – Veverke Aug 09 '16 at 11:46
  • @Veverke: It reorders the whole sequence, but in a stable way, so if two values have the same z value, the ordering will depend on y and then on x. – Jon Skeet Aug 09 '16 at 11:48
  • I may not have understood how this work. If my sort criteria is a composed of a,b and c fields - orderby(a).thenby(b).thenby(c) - each new sort takes as input the output of the preceding sort, whereas orderby(a).orderby(b).orderby(c), if each reorder the whole sequence - sounds like the output of one sort is not the input to the other, and thus the results will be different. But you say it translates into thenby() calls - and this is what confused me. I did not get what you wanted to add with the clarification on "in a stable way, so if two values..." here. – Veverke Aug 09 '16 at 13:12
  • 1
    @Veverke: `OrderBy(a).OrderBy(b).OrderBy(c)` still uses the output of the preceding sort, and reorders the whole thing, but it preserves the existing order (from the preceding step) where two elements are equal under the new comparison. Imagine we just have `OrderBy(a).OrderBy(b)`. The results of `OrderBy(a)` are in increasing `a` order, and then those are reordered according to `b`. In the final result, if two values have the same `b` value, they will be ordered by `a` due to the sort being stable - so it's equivalent to `OrderBy(b).ThenBy(a)`. – Jon Skeet Aug 09 '16 at 21:14
  • Thanks for taking the time to add more into it. I would be lying if I said "Got it, thanks!" :), there is still some confusion in my mind with *still uses the output of the preceding sort, and reorders the whole thing, but it preserves the existing order (from the preceding step)* and on... I will go over this again at a later time. This all begain when I "took note" of this issue by adding an [entry to my blog](http://devrecipeshb.blogspot.co.il/2016/08/linq-orderby-vs-thenby-composite.html) - if my conclusions there are wrong, let me know. – Veverke Aug 10 '16 at 13:34
  • @Veverke: I'm afraid I don't have time to look at that right now. (On holiday, just checking things quickly.) I'll see if I can look later, but it's probably easiest to do this manually, with bits of paper, and see if it makes sense. – Jon Skeet Aug 10 '16 at 21:47
2

I found this distinction annoying in trying to build queries in a generic manner, so I made a little helper to produce OrderBy/ThenBy in the proper order, for as many sorts as you like.

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

There are a lot of ways you might use this depending on your use case, but if you were for example passed a list of sort columns and directions as strings and bools, you could loop over them and use them in a switch like:

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;

The result in sortedQuery is sorted in the desired order, instead of resorting over and over as the other answer here cautions.

Chris Moschini
  • 36,764
  • 19
  • 160
  • 190
1

if you want to sort more than one field then go for ThenBy:

like this

list.OrderBy(personLast => person.LastName)
            .ThenBy(personFirst => person.FirstName)
Alexander Zaldostanov
  • 2,907
  • 4
  • 30
  • 39
0

Yes, you should never use multiple OrderBy if you are playing with multiple keys. ThenBy is safer bet since it will perform after OrderBy.

summerGhost
  • 477
  • 4
  • 15