Print

Sorting entities with the EntitySorter

This article describes the EntitySorter<T> class. It's a nifty little thing that allows the presentation layer to instruct the service layer how collections should be returned.

About a year ago I was, just like everybody else, trying to figure out how to fit Entity Framework and LINQ to SQL into architectural perspective. Especially within the context of ASP.NET applications. One of the things I figured out pretty quickly was the following: The LINQ to SQL DataContext and Entity Framework ObjectContext classes should not outlive a call to the service layer. There are several opinions about this, but for me this rule is very important. The presentation layer shouldn't know anything about connections and transactions. Next to that, the service layer should have full control over how and when there is communication with the database. Leaking the context classes out of the service layer is an architectural smell.

Consequence of this architectural rule is that the service layer should not return objects that implement IQueryable. Neither should you return entities with lazy loading capacities, because this would allow the presentation layer to make extra calls to the database, without the service layer knowing this (and it needs for the context to stay alive).

The approach I take is rather radical. I make sure that DataContext and ObjectContext classes are disposed by the layer that creates them (as you should do with all IDisposable objects) and return Data Transfer Objects often.

This subject deserves a whole post on it's own, but this architectural rule has some consequences on your design. When you're not allowed to return IQueryable objects from the service layer, you are faced with some challenges. One of those challenges is sorting.

Many user interfaces allow the user to sort results. While the presentation layer can sort a collection that's returned from the service layer, this could cause a severe performance problem, when working with paged result sets.

To solve this problem, the presentation layer should be able to instruct the service layer how to sort a requested set of results. A nice way to achieve this is by using a construct that's similar to Fowler's Query Object.

To achieve this, let's define an interface that allows sorting of collections:

public interface IEntitySorter<TEntity>
{
    IOrderedQueryable<TEntity> Sort(IQueryable<TEntity> collection);
}

Implementations of this interface can be supplied by the presentation layer to methods in the service layer to allow sorting. Below is an example of the use of this 'sort object' in the service layer:

public static Person[] GetAllPersons(IEntitySorter<Person> sorter)
{
Condition.Requires(sorter, "sorter").IsNotNull();

using (var db = ContextFactory.CreateContext())
{
IOrderedQueryable<Person> sortedList =
sorter.Sort(db.Persons);

return sortedList.ToArray();
}
}

Note that this interface processes an IQueryable and returns an IOrderedQueryable. Because IQueryable uses expression trees, frameworks like LINQ to SQL and Entity Framework can parse it. This allows sorting to be executed in the database. The place where this is (usually) done best.

Now let's think about how the presentation layer code should look. This layer shouldn't have to define new implementations of this interface for each and every call to the service layer. We want to have some sort of facade that allows us to create new instances easily. I'm thinking about code like this:

var sorter = EntitySorter<Person>.OrderBy(p => p.Id);
var persons = PersonServices.GetAllPersons(sorter);

And I'd like to be able to sort in descending order:

var sorter = EntitySorter<Person>.OrderByDescending(p => p.Id); 

And I'd like to be able to sort on multiple things, like this:

var sorter = EntitySorter<Person>
    .OrderBy(p => p.Name)
    .ThenBy(p => p.Id);

And it should be easy to create a new instance, based on the name of a property, simply because much of ASP.NET's infrastructure is based on strings. Controls like GridView supply string based property names. Therefore, the API should allow the following syntax:

var sorter = EntitySorter<Person>.OrderBy("Name");

And it must be possible to sort on properties in related objects, like this:

var sorter1 = EntitySorter<Person>.OrderBy(p => p.Address.City);
var sorter2 = EntitySorter<Person>.OrderBy("Address.City");

And while we're add it, it would be cool if we could use LINQ syntax to define a new sorter:

IEntitySorter<Person> sorter =
    from person in EntitySorter<Person>.AsQueryable()
    orderby person.Name descending, person.Id
    select person;

I think this is a nice API that would work. Now let's build it!

internal enum SortDirection
{
Ascending,
Descending
}

public static class EntitySorter<T>
{
public static IEntitySorter<T> AsQueryable()
{
return new EmptyEntitySorter();
}

public static IEntitySorter<T> OrderBy<TKey>(
Expression<Func<T, TKey>> keySelector)
{
return new OrderBySorter<T, TKey>(keySelector,
SortDirection.Ascending);
}

public static IEntitySorter<T> OrderByDescending<TKey>(
Expression<Func<T, TKey>> keySelector)
{
return new OrderBySorter<T, TKey>(keySelector,
SortDirection.Descending);
}

public static IEntitySorter<T> OrderBy(string propertyName)
{
var builder = new EntitySorterBuilder<T>(propertyName);

builder.Direction = SortDirection.Ascending;

return builder.BuildOrderByEntitySorter();
}

public static IEntitySorter<T> OrderByDescending(
string propertyName)
{
var builder = new EntitySorterBuilder<T>(propertyName);

builder.Direction = SortDirection.Descending;

return builder.BuildOrderByEntitySorter();
}

private sealed class EmptyEntitySorter : IEntitySorter<T>
{
public IOrderedQueryable<T> Sort(
IQueryable<T> collection)
{
string exceptionMessage = "OrderBy should be called.";

throw new InvalidOperationException(exceptionMessage);
}
}
}

The snippet above shows the implementation of the EntitySorter<T> facade. As you might have noticed, the class only defines the OrderBy and OrderByDescending methods. ThenBy and ThenByDescending aren't specified here. It makes sense when you think about this, because you always chain the ThenBy call after an OrderBy call, thus ThenBy should be implemented as instance method, or as we'll see shortly, as extension method.

The next snippet shows the extension methods:

public static class EntitySorterExtensions
{
public static IEntitySorter<T> OrderBy<T, TKey>(
this IEntitySorter<T> sorter,
Expression<Func<T, TKey>> keySelector)
{
return EntitySorter<T>.OrderBy(keySelector);
}

public static IEntitySorter<T> OrderByDescending<T, TKey>(
this IEntitySorter<T> sorter,
Expression<Func<T, TKey>> keySelector)
{
return EntitySorter<T>.OrderByDescending(keySelector);
}

public static IEntitySorter<T> ThenBy<T, TKey>(
this IEntitySorter<T> sorter,
Expression<Func<T, TKey>> keySelector)
{
return new ThenBySorter<T, TKey>(sorter,
keySelector, SortDirection.Ascending);
}

public static IEntitySorter<T> ThenByDescending<T, TKey>(
this IEntitySorter<T> sorter,
Expression<Func<T, TKey>> keySelector)
{
return new ThenBySorter<T, TKey>(sorter,
keySelector, SortDirection.Descending);
}

public static IEntitySorter<T> ThenBy<T>(
this IEntitySorter<T> sorter, string propertyName)
{
var builder = new EntitySorterBuilder<T>(propertyName);

builder.Direction = SortDirection.Ascending;

return builder.BuildThenByEntitySorter(sorter);
}

public static IEntitySorter<T> ThenByDescending<T>(
this IEntitySorter<T> sorter, string propertyName)
{
var builder = new EntitySorterBuilder<T>(propertyName);

builder.Direction = SortDirection.Descending;

return builder.BuildThenByEntitySorter(sorter);
}
}

Again no rocket science. Some things to note is that also the OrderBy and OrderByDescending (that we saw in the EntitySorter<T> class) are defined here. Specifying them as extension methods allows us to write LINQ queries. You can see that the implementation simply calls back to the EntitySorter<T>.OrderBy method.

The classes returned from these methods are internal implementations named OrderBySorter<T, TKey> and ThenBySorter<T, TKey>. The implementations are rather straightforward. On creation, a key selector (a lambda) and a sorting direction are supplied. Their Sort method transforms the supplied collection to a ordered collection. Here are the implementations:

internal class OrderBySorter<T, TKey> : IEntitySorter<T>
{
private readonly Expression<Func<T, TKey>> keySelector;
private readonly SortDirection direction;

public OrderBySorter(Expression<Func<T, TKey>> selector,
SortDirection direction)
{
this.keySelector = selector;
this.direction = direction;
}

public IOrderedQueryable<T> Sort(IQueryable<T> col)
{
if (this.direction == SortDirection.Ascending)
{
return Queryable.OrderBy(col, this.keySelector);
}
else
{
return Queryable.OrderByDescending(col,
this.keySelector);
}
}
}

internal sealed class ThenBySorter<T, TKey> : IEntitySorter<T>
{
private readonly IEntitySorter<T> baseSorter;
private readonly Expression<Func<T, TKey>> keySelector;
private readonly SortDirection direction;

public ThenBySorter(IEntitySorter<T> baseSorter,
Expression<Func<T, TKey>> selector, SortDirection direction)
{
this.baseSorter = baseSorter;
this.keySelector = selector;
this.direction = direction;
}

public IOrderedQueryable<T> Sort(IQueryable<T> col)
{
var sorted = this.baseSorter.Sort(col);

if (this.direction == SortDirection.Ascending)
{
return Queryable.ThenBy(sorted, this.keySelector);
}
else
{
return Queryable.ThenByDescending(sorted,
this.keySelector);
}
}
}

As you can see the two classes simply use the .NET framework's Queryable class to sort the supplied collection. It couldn't be easier.

Until now, the code was pretty straightforward. However, the API allows sorting based on the name of the property. I extracted this complicated logic to another class: the EntitySorterBuilder<T>. As seen above, the usage of the EntitySorterBuilder<T> is used as follows:

var builder = new EntitySorterBuilder<T>(propertyName);

builder.Direction = SortDirection.Descending;

IEntitySorter<T> sorter = builder.BuildOrderByEntitySorter();

The builder creates OrderBySorter<T, TKey> and ThenBySorter<T, TKey> implementations, using some heavy reflection. Here's the code:

internal class EntitySorterBuilder<T>
{
private readonly Type keyType;
private readonly LambdaExpression keySelector;

public EntitySorterBuilder(string propertyName)
{
List<MethodInfo> propertyAccessors =
GetPropertyAccessors(propertyName);

this.keyType = propertyAccessors.Last().ReturnType;

ILambdaBuilder builder = CreateLambdaBuilder(keyType);

this.keySelector =
builder.BuildLambda(propertyAccessors);
}

private interface ILambdaBuilder
{
LambdaExpression BuildLambda(
IEnumerable<MethodInfo> propertyAccessors);
}

public SortDirection Direction { get; set; }

public IEntitySorter<T> BuildOrderByEntitySorter()
{
Type[] typeArgs = new[] { typeof(T), this.keyType };

Type sortType =
typeof(OrderBySorter<,>).MakeGenericType(typeArgs);

return (IEntitySorter<T>)Activator.CreateInstance(sortType,
this.keySelector, this.Direction);
}

public IEntitySorter<T> BuildThenByEntitySorter(
IEntitySorter<T> baseSorter)
{
Type[] typeArgs = new[] { typeof(T), this.keyType };

Type sortType =
typeof(ThenBySorter<,>).MakeGenericType(typeArgs);

return (IEntitySorter<T>)Activator.CreateInstance(sortType,
baseSorter, this.keySelector, this.Direction);
}

private static ILambdaBuilder CreateLambdaBuilder(Type keyType)
{
Type[] typeArgs = new[] { typeof(T), keyType };

Type builderType =
typeof(LambdaBuilder<>).MakeGenericType(typeArgs);

return (ILambdaBuilder)Activator.CreateInstance(builderType);
}

private static List<MethodInfo> GetPropertyAccessors(
string propertyName)
{
try
{
return GetPropertyAccessorsFromChain(propertyName);
}
catch (InvalidOperationException ex)
{
string message = propertyName +
" could not be parsed. " + ex.Message;

// We throw a more expressive exception at this level.
throw new ArgumentException(message, "propertyName");
}
}

private static List<MethodInfo> GetPropertyAccessorsFromChain(
string propertyNameChain)
{
var propertyAccessors = new List<MethodInfo>();

var declaringType = typeof(T);

foreach (string name in propertyNameChain.Split('.'))
{
var accessor = GetPropertyAccessor(declaringType, name);

propertyAccessors.Add(accessor);

declaringType = accessor.ReturnType;
}

return propertyAccessors;
}

private static MethodInfo GetPropertyAccessor(Type declaringType,
string propertyName)
{
var prop = GetPropertyByName(declaringType, propertyName);

return GetPropertyGetter(prop);
}

private static PropertyInfo GetPropertyByName(Type declaringType,
string propertyName)
{
BindingFlags flags = BindingFlags.IgnoreCase |
BindingFlags.Instance | BindingFlags.Public;

var prop = declaringType.GetProperty(propertyName, flags);

if (prop == null)
{
string exceptionMessage = string.Format(
"{0} does not contain a property named '{1}'.",
declaringType, propertyName);

throw new InvalidOperationException(exceptionMessage);
}

return prop;
}

private static MethodInfo GetPropertyGetter(PropertyInfo property)
{
var propertyAccessor = property.GetGetMethod();

if (propertyAccessor == null)
{
string exceptionMessage = string.Format(
"The property '{1}' does not contain a getter.",
property.Name);

throw new InvalidOperationException(exceptionMessage);
}

return propertyAccessor;
}

private sealed class LambdaBuilder<TKey> : ILambdaBuilder
{
public LambdaExpression BuildLambda(
IEnumerable<MethodInfo> propertyAccessors)
{
ParameterExpression parameterExpression =
Expression.Parameter(typeof(T), "entity");

Expression propertyExpression = BuildPropertyExpression(
propertyAccessors, parameterExpression);

return Expression.Lambda<Func<T, TKey>>(
propertyExpression, new[] { parameterExpression });
}

private static Expression BuildPropertyExpression(
IEnumerable<MethodInfo> propertyAccessors,
ParameterExpression parameterExpression)
{
Expression propertyExpression = null;

foreach (var propertyAccessor in propertyAccessors)
{
var innerExpression =
propertyExpression ?? parameterExpression;

propertyExpression = Expression.Property(
innerExpression, propertyAccessor);
}

return propertyExpression;
}
}
}

The EntitySorterBuilder<T> does a few interesting things. First of all it creates a list of property accessors (getter methods), based on the property names. For instance, it creates a list of two getter methods when the following string is supplied: "Address.City". Next it generates a lambda expression (the keySelector) based on the list of property accessors, in the following form: 'e => e.[Property1].[Property2]'). After that, it creates a new OrderBySorter<T, TKey> or ThenBySorter<T, TKey> with the generated lambda expression as constructor argument and returns it.

I hope this EntitySorter<T> comes in handy to some of you. I has already served me well over the last year.

I just created an CodePlex project that contains the code shown above. The code on CodePlex has additional error checking, (xml) comments and more descriptive variable names. So don't copy-paste the code from this blog; just browse directly to the source code, by clicking here.

The project also contains an EntityFilter<T> class (here the code) that allows filtering of collections. Just like the EntitySorter<T> it allows creation using LINQ queries, like this:

IEntityFilter<Person> entityFilter =
from person in EntityFilter<Person>.AsQueryable()
where person.Name.StartsWith("a")
where person.Id < 100
select person;

Happy sorting and filtering!

- .NET General, C#, Entity Framework, LINQ, LINQ to SQL - five comments / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

five comments:

Very interesting, but... nothing on CodePlex. Do you have an ETA for that? Thanks. Jim
Jim (URL) - 11 02 10 - 22:54

Jim, the code is on CodePlex, just follow the link: http://servicelayerhelpers.codeplex.com/.. or browse directly to the EntityFilter code: http://servicelayerhelpers.codeplex.com/.. and the EntitySorter code: http://servicelayerhelpers.codeplex.com/.. However, don't expect any downloadable DLL that you can add to your project. It's a BIY (build it yourself) :-)
Steven (URL) - 12 02 10 - 09:55

Thank you for this great library! One question regarding the IEntityFilter example above: how would one be able to pass (serialize) the entityFilter object (entirely or its relevant property) to the Service Layer?
Tim B. - 05 01 13 - 02:22

@Tim, the implementation here and on CodePlex doesn't support serialization. It can be added though. One tip: do not try to serialize expression trees because, 1. this is pain and 2. you'll open up your application for injection attacks. Instead make sure that the keySelectors (Expression keySelector) are serialized as property-chained strings (such as "Address.City descending"). The code already supports deserializing property-chains to expression tree. Here you can read how to serialize them to property-chains: http://www.cuttingedge.it/blogs/steven/p.. Good luck.
Steven (URL) - 05 01 13 - 14:25

Hi Steven

Great idea, and great execution!
I've successfully implemented this in my framework at work, and I must say it works like a charm.

I found a bug though in the EntitySorter.
If you use the (string PropertyName) method of the EntitySorter, and that property is part of a base class, then you will get an Exception during the building of the LambdaExpression.

This is because the method accessor type (= base class) does not equal the TEntity type of the sorter (= class).

The fix is rather straightforward though: instead of using MethodInfo property accessors, you need to use PropertyInfo properties in the EntitySorterBuilder

Here's a gist of the refactored code: https://gist.github.com/amoerie/5583216

Have a nice day
Alex (URL) - 15 05 13 - 13:09


No trackbacks:

Trackback link:

Please enable javascript to generate a trackback url


  
Remember personal info?

/

Before sending a comment, you have to answer correctly a simple question everyone knows the answer to. This completely baffles automated spam bots.
 

  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.