Linq Order By When You Have Property Name

Without reflection, we go blindly on our way, creating more unintended consequences, and failing to achieve anything useful.
–Margaret J. Wheatley

Ordering By a Column Name

Quick tip today in case anyone runs into this.  Frequently you have some strongly typed object and you want to order by some property on that object.  No problem — Linq’s IEnumerable.OrderBy() to the rescue.  But what about when you don’t have a strongly typed object at runtime and you only have the property’s name?

In a little project I’m working on at the moment, this came up. In this project, I’m parsing SQL queries (a subset of SQL, anyway) and translating these queries into web service requests for Autotask. All of the Autotask web service’s entities are children of a base class simply called Entity. Entities have ids in common, but little else. So the situation is that I’m going to get a query of the form “SELECT * FROM Account ORDER BY AccoutName” (i.e. just a string) and I’m going to have to pull out of the API a series of strongly typed objects and figure out how to sort them by “AccountName” at runtime. Tricky part is that I don’t know at compile time what object type I’ll be getting back, much less which property on that type I’ll be using to sort. So something like entities.OrderBy(e => e.AccountName) is obviously right out.

So what we need is a way of mapping the string to a property and then matching that property to a strongly typed value on the object that can be used for ordering.

private static IEnumerable OrderBy(IEnumerable entities, string propertyName)
{
    if (!entities.Any() || string.IsNullOrEmpty(propertyName))
        return entities;

    var propertyInfo = entities.First().GetType().GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
    return entities.OrderBy(e => propertyInfo.GetValue(e, null));
}

This method first checks a couple of preconditions: actual value supplied for the property name (obviously) and that any entities exist for sorting. This last one might seem a little strange, but it makes sense when you think about it. The reason it makes sense, if you’ll recall my post on type variance, is that the type of the enumerable is generic and strictly a compile time designation. As such, this method is going to be compiled as IEnumerable rather than IEnumerable or any other derivative.

Now, if you did this:

private static IEnumerable OrderBy(IEnumerable entities, string propertyName)
{
    if (!entities.Any() || string.IsNullOrEmpty(propertyName))
        return entities;

    var propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
    return entities.OrderBy(e => propertyInfo.GetValue(e, null));
}

…you would have a problem. Since T is going to be compiled as Entity, you’re going to be looking for properties of the derived class using the type information associated with the base class, which will fail, causing the returned propertyInfo to be null and then a null reference exception on the next line. Since we have no way of knowing at compile time what sort of entity we’re going to have, we have to check at run time. And, in order to do that, we need an actual instance of an entity. If we just have an empty enumerable, this is strictly unknowable.

My solution here is a private static method because I have no use for it (yet) in any other scope or class. But, if you were so inclined you could create an extension method pretty easily:

public static IEnumerable OrderBy(this IEnumerable entities, string propertyName)
{
    if (!entities.Any() || string.IsNullOrEmpty(propertyName))
        return entities;

    var propertyInfo = entities.First().GetType().GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
    return entities.OrderBy(e => propertyInfo.GetValue(e, null));
}

If you were going to do this, I’d suggest making this method a tad more robust, however as you might get a variety of interesting edge cases thrown at it.