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:
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 |
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:
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 |
public static IOrderedQueryable<TSource> ApplySortDirection<TSource, TKey> (this IQueryable<TSource> source, System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector, string sortDirection) { var orderedSource = source as IOrderedQueryable<TSource>; 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:
1 2 |
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:
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 |
public static IOrderedQueryable<TSource> ApplySortDirection<TSource, TKey> (this IQueryable<TSource> source, System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector, string sortDirection) { if (source.Expression.Type.Name.Contains("IOrderedQueryable")) { var orderedSource = (IOrderedQueryable<TSource>) 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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.
This is nice idea!
I would just change the “IOrderedQueryable” string to typeof(IOrderedQueryable).Name
Thanks for this!
Hi, your suggestion makes it possible for compile time type checking for IOrderedQueryable. However it would incur a minor performance penalty at run-time because of Reflection. Classes like IOrderedQueryable are not expected to break or change names even between major .Net versions. So I guess I would keep it as a string only.
You are right! Personally, I’d still prefer this though:
public static string ORDERED_QUERYABLE_NAME = typeof(IOrderedQueryable).Name;
Also, your second to last code snippet has a line that should be removed:
var orderedSource = source as IOrderedQueryable;
Yeah the static member is the better way I would agree. And thanks for pointing out, have removed the redundant line too. Cheers!!