A few days ago, I was working on some Entity Framework stuff where I encountered the following logic for applying sort expressions to the EF query:
var query = this.context.users.AsQueryable(); if (sort == "username") { if (sortDir == "ASC") { query = query.OrderBy(u => u.username); } else if (sortDir == "DESC") { query = query.OrderByDescending(u => u.username); } } else if (sort == "email") { if (sortDir == "ASC") { query = query.OrderBy(u => u.email); } else if (sortDir == "DESC") { query = query.OrderByDescending(u => u.email); } } else if (sort == "lastActivityTime") { if (sortDir == "ASC") { query = query.OrderBy(u => u.lastActivityTime); } else if (sortDir == "DESC") { query = query.OrderByDescending(u => u.lastActivityTime); } }
I felt it was an awful lot of code and code repetition especially when you are working with LINQ and extension methods, its just against the spirit of it.
So I thought what if we create a generic extension method that can take an IQueryable
and apply sorting on it, taking into account if the IQueryable
expression was already sorted. My first attempt started with something like the following:
public static IOrderedQueryableApplySortDirection (this IQueryable source, System.Linq.Expressions.Expression > keySelector, string sortDirection) { var orderedSource = source as IOrderedQueryable ; if (orderedSource != null) { switch (sortDirection) { case "DESC": return (orderedSource.ThenByDescending(keySelector)); default: return (orderedSource.ThenBy(keySelector)); } } else { switch (sortDirection) { case "DESC": return (source.OrderByDescending(keySelector)); default: return (source.OrderBy(keySelector)); } } }
However it failed on any of the following lines:
orderedSource.ThenByDescending... orderedSource.ThenBy...
generating an exception. As I poked around scratching my head, I realised an IQueryable
instance is always cast’able to an IOrderedQueryable
instance even when no sort expression has been applied on an IQueryable
instance atleast once. So now the challenge was to figure out if an IQueryable
had already sort expression applied to it. This was essential to decide whether to invoke the ThenBy
sort methods or the OrderBy
sort methods.
Luckily I found the IQueryable.Expression.Type.Name
property which stored the actual type name of the instance represented by query
, and the type name differed for an IQueryable
instance vs an IOrderedQueryable
instance. This proved all that was needed to complete the generic sort method successfully:
public static IOrderedQueryableApplySortDirection (this IQueryable source, System.Linq.Expressions.Expression > keySelector, string sortDirection) { if (source.Expression.Type.Name.Contains("IOrderedQueryable")) { var orderedSource = (IOrderedQueryable ) source; switch (sortDirection) { case "DESC": return (orderedSource.ThenByDescending(keySelector)); default: return (orderedSource.ThenBy(keySelector)); } } else { switch (sortDirection) { case "DESC": return (source.OrderByDescending(keySelector)); default: return (source.OrderBy(keySelector)); } } }
And the above method helped us reduce our sorting expressions to the following:
var query = this.context.users.AsQueryable(); if (sort == "username") { query = query.ApplySortDirection(u => u.username, sortDir); } else if (sort == "email") { query = query.ApplySortDirection(u => u.email, sortDir); } else if (sort == "lastActivityTime") { query = query.ApplySortDirection(u => u.lastActivityTime, sortDir); }
A whole lot compact and concise I hope you would agree.