Getting Started with Adobe After Effects - Part 6: Motion Blur


Upload Image Close it
Select File

Weblog of a workaholic
Browse by Tags · View All
.NET General 53
C# 47
LINQ 16
Dependency injection 12
Validation Application Block 10
Enterprise Library 10
ASP.NET 9
LINQ to SQL 8
Entity Framework 8
CuttingEdge.Conditions 8

Archive · View All
August 2009 7
September 2009 6
March 2007 5
October 2006 3
May 2009 3
July 2008 3
November 2007 3
November 2006 2
January 2009 2
August 2008 2

Sorting entities with the EntitySorter

Oct 25 2009 12:00AM by Steven   

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!


Republished from .NET Junkie [7 clicks].  Read the original version here [1 clicks].

Steven
395 · 0% · 101
0
Liked
 
0
Lifesaver
 
0
Refreshed
 
0
Learned
 
0
Incorrect



Submit

Your Comment


Sign Up or Login to post a comment.

    Copyright © Rivera Informatic Private Ltd Contact us      Privacy Policy      Terms of use      Report Abuse      Advertising      [ZULU1097]