Sunday, April 10, 2011

Lambda Wrangling For Fun : Part 1 – Sorting Single Columns

 

No doubt that a lot of us remember the good old days when we wrote web applications with grids that had sortable columns. For those crazy old timers like myself who were writing this sort of thing before the naff ASP.NET grids came out, we remember the process to go something along these lines:

  • Obtain the name of the column to sort the data set on.
  • Obtain the direction of the sort
  • Construct the ORDER BY clause in the SELECT query to be executed to return the data sorted in the correct way.
  • Execute said query and render the data set appropriately

Setting aside all the discussion about the crudeness of the solution, one thing can be said in favour of this approach – it was simple.

With a more formal service-oriented approach, one constructs a method on the service for each column to be sorted on and implements the appropriate LINQ query statically.

(Yes, I’m aware of Dynamic LINQ, but pretend it doesn’t exist for now.)

The object of this post is to discuss an approach with the flexibility of run-time column selection and the type-safety and inherent power of LINQ.

Dissecting a Lambda

System.Linq.Enumerable has a few extension methods which provide ordering semantics. Let’s consider the most commonly used one – ‘OrderBy’. In its simplest form, it looks like this:

public static System.Linq.IOrderedEnumerable OrderBy(this System.Collections.Generic.IEnumerable source, 
System.Func keySelector)

Member of System.Linq.Enumerable

Summary:
Sorts the elements of a sequence in ascending order according to a key.

Type Parameters:
TSource: The type of the elements of source.
TKey: The type of the key returned by keySelector.

Parameters:
source: A sequence of values to order.
keySelector: A function to extract a key from an element.

Returns:
An System.Linq.IOrderedEnumerable whose elements are sorted according to a key.

So this OrderBy function takes a collection of items to be sorted, and a lambda which selects a property on each item on which to sort by.


The lambda itself specifies the type of the item (which is known because we have a strongly typed collection) and the type of the sorting property. This can be inferred if the lambda is present at compile time.


Creating a Lambda on the fly


Given a property name string, we want to somehow concoct a lambda which can be given to the OrderBy function.


Here is a function which does that – analysis to follow:

public static Func<TSource, object> GetPropertySelectorLambda<TSource>(this TSource _this, string propertyName)
{
// Given that propertyName = "Foo", we want the lambda expression * _ => _.Foo *

// This gives us the * _ => * part
var parameterExpression = Expression.Parameter(typeof(TSource), "_");

// This gives us the * .Foo * part - see the interfaces and inheritance blog post
var propertyInfo = typeof(TSource).GetProperty(propertyName, true);

// This gives us the * _.Foo * part
var unboxedAccessorExpression = Expression.Property(parameterExpression, propertyInfo);

// Guid, int and other value types can't strictly be return types in expressions
// Why? Because 'object' is a reference type
// So we convert value types to Object
var accessorExpression = (unboxedAccessorExpression.Type.IsValueType
? (Expression)Expression.Convert(unboxedAccessorExpression, typeof(object))
: unboxedAccessorExpression);

// Build the lambda to the strong Func type and compile it down to a Func
return Expression.Lambda<Func<TSource, object>>(accessorExpression, parameterExpression).Compile();
}

The Expression wrangling above is typical of functions which generate lambdas on the fly – it’s not as elegant as a truly functional language, but it works even though it’s a bit unwieldy.


The NullReferenceException that wasn’t!


The eagle-eyed reader will notice that the only use of ‘_this’ is to make the function an extension method – so that it becomes accessible in a reasonably fluent manner. 


What we’re really interested is in the type of ‘_this’. The value of ‘_this’ is never used. This means that ‘_this’ can be null – or even more mysteriously, it can refer to something that can never exist!


So typically, as in an MVC controller, this method may be used thus:

// GET: /T/
public virtual ActionResult Index(string sort_column = "Timestamp")
{
var orders = Service.Get(null, default(TOrder).GetPropertySelectorLambda(sort_column));
return View(orders);
}

TOrder could be an interface, so we use default(TOrder) instead of new(TOrder) as the anchor for the extension method. Now, since TOrder is defined as a reference type, default(TOrder) can never be anything but null, but the extension method still works very nicely!


Cool!

No comments:

Post a Comment