A better way to do dynamic OrderBy() in C#
A common feature in various applications is to sort some collection by one of it’s properties, dependent on some input like the column clicked by the user. An implementation might take a string
or enum
for the column plus a bool
or enum
for ascending vs. descending.
The code then looks something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
switch (orderByField)
{
case "hired":
if (desc)
{
queryable = queryable.OrderByDescending(x => x.DateHired);
}
else
{
queryable = queryable.OrderBy(x => x.DateHired);
}
break;
case "name":
if (desc)
{
queryable = queryable.OrderByDescending(x => x.Name);
}
else
{
queryable = queryable.OrderBy(x => x.Name);
}
break;
case "age":
if (desc)
{
queryable = queryable.OrderByDescending(x => x.Age);
}
else
{
queryable = queryable.OrderBy(x => x.Age);
}
break;
// etc.
}
This turns into ugly spaghetti code fast. Lots of lines for something that should be trivial, and this is just three columns. Hard to read, hard to maintain, surely there has to be a better way.
So, why can’t you just do something like this?
1
2
3
4
5
6
7
8
9
10
11
private static readonly Dictionary<string, object> OrderFunctions =
new Dictionary<string, object>
{
{ "hired", x => x.DateHired },
{ "name", x => x.Name },
{ "age", x => x.Age }
};
// ...
queryable = desc
? queryable.OrderByDescending(OrderFunctions[orderByField])
: queryable.OrderBy(OrderFunctions[orderByField]);
The reason the above won’t work is that the lambda expressions get types Func<string, DateTime>
and Func<string, string>
etc. which denotes a delegate that is pretty much a pointer to a method, i.e. a compiled piece of code. In order to pass this logic on to a SQL Database or Sitecore ContentSearch, it needs to be an expression tree that can then be converted to the equivalent SQL statement or other implementation of that domain.
As well, LINQ-to-SQL (and possibly other LINQ-to-Something) needs to know the return-type (DateTime
, string
and int
above) of the Func<>
at runtime, and this is hidden if the value-type of the Dictionary<>
is object
. To make this work, we need to explicitly state the type of each function and set the value-type of our Dictionary<>
to dynamic
:
1
2
3
4
5
6
7
private static readonly Dictionary<string, dynamic> OrderFunctions =
new Dictionary<string, dynamic>
{
{ "hired", (Expression<Func<SearchResultItem, DateTime>>)(x => x.DateHired) },
{ "name", (Expression<Func<SearchResultItem, string>>)(x => x.Name) },
{ "age", (Expression<Func<SearchResultItem, int>>)(x => x.Age) }
};
Alright, so far so good, but now the .OrderBy()
and .OrderByDescending()
extension methods seem kaput? The problem is that the extension method syntax doesn’t play nice with our dynamic
type, so finally these have to be rewritten like so:
1
2
3
queryable = desc
? Queryable.OrderByDescending(queryable, OrderFunctions[orderByField])
: Queryable.OrderBy(queryable, OrderFunctions[orderByField]);
Et voilà, from 34 lines of unreadable spaghetti to 10 lines of easily maintained and extendable code!
This works, but the Expression<Func<SearchResultItem,
part is a bit lengthy and repetitive. Unfortunately Expression<>
is a sealed
class, but you can make a wrapper for it. This wrapper has the actual Expression<Func<>>
as a property, and I like to make an interface that defines the property, so you could have multiple implementations like OrderSitecoreBy<T>
and OrderSqlBy<T>
based on their input-type (SearchResultItem
in our case). As well this allows the Dictionary<>
to return type IOrderBy
rather than dynamic
.
Update May 9th 2022: To keep the nicer syntax of something.OrderBy(func)
, I’ve added the some extension methods that work with the aforementioned IOrderBy
interface. This cleans up the calls, particularly when stringing together multiple .ThenBy()
calls. The end result then looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// OrderByExtensions.cs
public static class OrderByExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, IOrderBy orderBy)
{
return Queryable.OrderBy(source, orderBy.Expression);
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, IOrderBy orderBy)
{
return Queryable.OrderByDescending(source, orderBy.Expression);
}
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, IOrderBy orderBy)
{
return Queryable.ThenBy(source, orderBy.Expression);
}
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, IOrderBy orderBy)
{
return Queryable.ThenByDescending(source, orderBy.Expression);
}
}
// IOrderBy.cs
public interface IOrderBy
{
dynamic Expression { get; }
}
// OrderBy.cs
using System;
using System.Linq.Expressions;
public class OrderBy<T> : IOrderBy
{
private readonly Expression<Func<SearchResultItem, T>> expression;
public OrderBy(Expression<Func<SearchResultItem, T>> expression)
{
this.expression = expression;
}
public dynamic Expression => this.expression;
}
// Code that needs mapping from string to ordering-expression.
private static readonly Dictionary<string, IOrderBy> OrderFunctions =
new Dictionary<string, IOrderBy>
{
{ "hired", new OrderBy<DateTime>(x => x.DateHired) },
{ "name", new OrderBy<string>(x => x.Name) },
{ "age", new OrderBy<int>(x => x.Age) }
};
// ...
queryable = desc
? queryable.OrderByDescending(OrderFunctions[orderByField])
: queryable.OrderBy(OrderFunctions[orderByField]);
And the nice thing is you can pass the IOrderBy
expression trees around like variables. The logic behind a sort-column like "hired"
could be kept in a completely different assembly or namespace to keep a good separation of concerns.
Comments
This is very cool! I’m a little upset that I couldn’t come up with this myself, but I learned something. Also, one non-expert suggestion is to use nameof with the EF class and property as the Dictionary key, and on the call, avoiding “magic strings”.
So this looks like it is a one trick pony for the SearchResultItem from a third party tool.
Hi Lars,
Since I focus on Sitecore on this blog, I can see why it might look like that. In reality I use this for Entity Framework as well with a generics-version of the code that looks like:
Then you can instantiate with something like
new OrderBy<YourEFDbSetType, DateTime>(x => x.LastModified)
and the rest of the code is the same.Hi Graham, How I define the ‘queryable’ variable to begin with. In my case I have to sort a list of objects not by one field , by several fields. the sorting definition is a list by itself.
Thanks
Hi Itzhak,
You could do something like this:
The line in the
foreach
is a shorthand to say “if queryable isn’t defined yet, define it fromyourListOfObjects
with the firstsortingDefinition
asOrderBy()
, else expand it withThenBy()
“.Thanks a lot, it’s an amazing solution!
Where do I find class SearchResultItem?
Hi Graham,
SearchResultItem
is the object that’s being ordered to begin with.In my example it comes from the Sitecore API, in the
Sitecore.ContentSearch.SearchTypes
namespace, but if you are using this for Linq-to-SQL it’ll be one of the classes generated by Entity Framework.Thanks, excellent solution!
Thanks, useful info!