The Power of Expression Trees with ORMs such as NHibernate and Entity Framework

by Michael Henstock 20. August 2012 10:35
Lets start with a quick, simple example. Lets start with a simple Entity:
public class SimpleEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

List<SimpleEntity>()
    {
        new SimpleEntity() { Id = 1, Name = "Entity 1" },
        new SimpleEntity() { Id = 2, Name = "Entity 2" },
        new SimpleEntity() { Id = 3, Name = "Entity 3" }
    };
entites.Where(e => e.Id == 2);
Which would return an IEnumerable<SimpleEntity> with the second item added in the list.
 
Now, say we didn't know the field we wanted to query on, how could we go about that? Firstly, we need become great friends with System.Linq.Expressions.Expression. This is the base class for all parts of an expression tree which builds up a LINQ query. Let's dive into doing the above query the analyse its parts.
var parameter = Expression.Parameter(typeof(SimpleEntity), "e");
entities.Where(
    Expression.Lambda<Func<SimpleEntity, bool>>(
        Expression.Equal(
            Expression.Property(parameter, "Id"),
            Expression.Constant(2)),
        parameter)
        .Compile());
So, first up we're creating a parameter named "e" that is defined as a type of SimpleEntity. Pretty straight forward, and in the simple LINQ query above this is the e =>
 
Next we have Expression.Lambda<Func<SimpleEntity, bool>> which is us telling the runtime that we're going to build up an expression tree which looks like a function that takes a SimpleEntity as a parameter and returns a bool result.
 
Then we are building up a BinaryExpression where we are doing an equality check for the property Id on parameter "e" and comparing that to the constant value of 2.
 
Then we pass in the parameter to the lambda as well to pass the property "e" up the hierarchy of the expression tree to the area it is used.
 
Finally, as the List Where method requires a Func<SimpleEntity, bool> parameter, rather than the Expression object tree we have built up, we call the Compile() which returns a delegate of the type specified which is then called in the Where method and the returns the single entity. Building up an expression tree and compiling at run time is not very efficient, so you need to look into where and when you would use something like this to build up a delegate and make sure you're not over-engineering something that could be done simpler.
 
The real power of expression trees comes about when we start to look at IQueryable and how ORMs interpret these IQueryable LINQ entities. NHibernate, Entity Framework and their ilk use IQueryable definitions to define SQL queries in Expression tree code form, as per this example:
NHibernate.ISession sessionObject;
var query = sessionObject.Query<SimpleEntity>();
query.Where(e => e.Id == 2);
This would be interpreted by NHibernate when the query is complete and being executed into SQL something like this:
SELECT * FROM SimpleEntity WHERE Id = 2
Now we can take a IQueryable object, build it up with a few via both lambda expressions and expression trees, have NHibernate interpret that into SQL and execute your dynamic expression tree as SQL against your database. This could look something like this:
var query = sessionObject.Query<SimpleEntity>(); 
query = query.Where(e => e.Id == 2);
query = query.Where(
    Expression.Lambda<Func<SimpleEntity, bool>>(
        Expression.Equal(
            Expression.Property(parameter, "Name"),
            Expression.Constant("Entity 2")),
        parameter));
And this would come out like:
SELECT * FROM SimpleEntity WHERE Id = 2 AND Name = 'Entity 2'
This isn't just for filtering either, you can order results, select results, group results etc, all using the power of Expression trees. While these examples are really very basic, it very quickly expands out into powerful (and complicated!) expressions.
 
A final example without much explanation, this is looping through a list of names, and depending on custom markers in the name, filtering a query on each Persons name and these are OR'd to find all the Person entities in the supplied names enumerable:
Expression<Func<Person, bool>> expr = null;
foreach (var name in names)
{
    if (!string.IsNullOrEmpty(name))
    {
        var searchName = name.Replace("%", "");
        Expression<Func<Person, bool>> newPred;
        if (name.EndsWith("%") && !name.StartsWith("%"))
        {
            newPred = i => i.name.StartsWith(searchName);
        }
        else if (name.StartsWith("%") && !name.EndsWith("%"))
        {
            newPred = i => i.name.EndsWith(searchName);
        }
        else
        {
            newPred = i => i.name == searchName;
        }
        expr = expr == null
            ? newPred
            : Expression.Lambda<Func<InvestorView, bool>>(
                  Expression.OrElse(
                      expr.Body,
                      Expression.Invoke(
                          newPred,
                          expr.Parameters.Cast<Expression>())),
                      expr.Parameters);
    }
}
query.Where(expr);

Tags:

C# | NHibernate