Asontu

Herman Scheele
.NET, C#, Sitecore developer and Data Science engineer @ theFactor.e.

profile for asontu at Stack Overflow, Q&A for professional and enthusiast programmers

Tech-blog about C#, Sitecore, T-SQL, Front-end, Python, Data Science and everything in between run by Herman Scheele

RSS Archive
2 April 2020

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.

tags: C# - Back-end

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”.

Herman, (author) 28 October 2022

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:

public class OrderBy<TToOrder, TBy> : IOrderBy
{
	private readonly Expression<Func<TToOrder, TBy>> expression;
	
	public OrderBy(Expression<Func<TToOrder, TBy>> expression)
	{
		this.expression = expression;
	}

	public dynamic Expression => this.expression;
}

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

Herman, (author) 22 October 2022

Hi Itzhak,

You could do something like this:

string[] sortingDefinitions = new string[3] { "age", "hired", "name" };

IOrderedQueryable<SearchResultItem>? queryable = null;

foreach (var sortingDefinition in sortingDefinitions)
{
    queryable = queryable?.ThenBy(OrderFunctions[sortingDefinition])
        ?? yourListOfObjects.AsQueryable().OrderBy(OrderFunctions[sortingDefinition]);
}

The line in the foreach is a shorthand to say “if queryable isn’t defined yet, define it from yourListOfObjects with the first sortingDefinition as OrderBy(), else expand it with ThenBy().

Herman, (author) 06 July 2022

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.