Implementing Linq for NHibernate: A How To Guide - Part 1

time to read 26 min | 5065 words

There is an appaling lack of documentation about how to implement Linq providers. The best resource that I could find is Fabrice's post about Linq To Amazon. The Linq In Action may provide additional information, but from what I have seen it is about using linq, not build a provider yourself. Since I would really like people to pitch in and help with the implementation of Linq for NHibernate (not that this is a hint or anything), I decided to document what I found out while building Linq for NHibernate.

I strongly suggest that you would get the VPC Image and use that to explore what is possible. The code, to remind you, is at:

svn co https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/experiments/NHibernate.Linq/

My goal at the moment is feature parity with the 101 examples that Microsoft have published.

I am not going to explain in detail how Linq works, you can go to other sites to find that out, but here is a short introduction. Linq is an extention to the compiler that turns certain keywords into method calls. The interesting part it that the compiler can give you the AST (Abstract Syntax Tree) for the query instead of executable code. This is important, because then you are free to take actions based on the AST (for instance, make a database call).

The whole concept revolves around two main ideas, IQueryable<T> and Expression which are tightly linked together. The compiler will output an Expression tree that will be passed to the IQueryable<T> implementation. Of course, there need to be an IQueryable<T> implementation, and that is where extention methods come into place. I implemented to core functionalty by adding an extention method to ISession, like this:

public static class LinqForNHibernate

{

    public static IQueryable<T> Linq<T>(this ISession session)

    {

        return new NHibernateLinqQuery<T>(session);

    }

}

Now I can execute this query, and NHiberanteLinqQuery get to intercept the expression tree:

var query = (from user in session.Linq<User>() select user).ToList();

The rest of this post is going to focus mostly on the NHibernateLinqQuery implementation. I have choosen to base the Linq implementation on the criteria API. This make the task a lot simpler, since I can let mature API handle a lot of the underlying query generation. The criteria API does not exposes 100% of the functionality offered by NHibernate, but it offers most of it, and saves me the need to handle query generation myself. Where I would need features in the criteria API that do not currently exists, I can add them.

Here are the fields on NHibernateLinqQuery:

ISession session;

ICriteria rootCriteria;

IDictionary<ExpressionType, Action<System.Linq.Expressions.Expression>> actions;

Stack<IList<NHibernate.Expression.ICriterion>> criterionStack = new Stack<IList<NHibernate.Expression.ICriterion>>();

IList<TResult> result;

The session and rootCriteria are probably obvious, but actions require an explanation. Each Expression has an ExpressionType, and the action dictionary contains the matching methods that can handle them. Basically, each ExpressionType is handled by a method Process[ExpressionType] on the NHibernateLinqQuery. Here is an example of the method that handles ExpressionType.Lambda:

public void ProcessLambda(Expression expr)

{

    LambdaExpression lambda = (LambdaExpression)expr;

    ProcessExpression(lambda.Body);

}

Where ProcessExpression is implemented as:

public void ProcessExpression(Expression expr)

{

    actions[expr.NodeType](expr);

}

Basically a visitor pattern with the actions dictionary serving as the dispatcher.

The criterionStack contains all the current predicates about the query. It is a stack of a list of ICriterion, and the idea is that I can insert a new list to the stack, have it process some of the expression, and then pop the list and use the processed items. Let us see this in code, we have the CurrentCritetions, which all the Process[ExpressionType] will handle, which is simply:

public IList<NHibernate.Expression.ICriterion> CurrentCriterions

{

       get { return criterionStack.Peek(); }

}

Once we have both of those, we can then use it for complex expression, like handling [user.Name == "ayende" || user.Name = "rahien"]:

public void ProcessOrElse(Expression expr)

{

       BinaryExpression and = (BinaryExpression) expr;

       criterionStack.Push(new List<NHibernate.Expression.ICriterion>());

       ProcessExpression(and.Left);

       ProcessExpression(and.Right);

       IList<NHibernate.Expression.ICriterion> ors = criterionStack.Pop();

 

       NHibernate.Expression.Disjunction disjunction = new NHibernate.Expression.Disjunction();

       foreach (var crit in ors)

       {

              disjunction.Add(crit);

       }

       CurrentCriterions.Add(disjunction);

}

We push a new list to the stack, process the right and left expressions of the or, pop the current critetion list, and then we combine them into a disjunction, which we push into the original criterion list. This way we don't have to worry about complex expression, they are mostly handled by themselves.

Now that I talked about how I am parsing the expression tree, let us talk about how the query is handled. Again, this isn't documented that I have seen, so I am mainly talking about what I discovered. The first thing that happens is that the IQueryable<T>.Expression property is called. Basically it is asked to give what sort of an expression should handle this query. I choose to handle the query in the same IQueryable<T> implementation, so I am returning this reference:

public System.Linq.Expressions.Expression Expression

{

       get { return System.Linq.Expressions.Expression.Constant(this); }

}

Then, the CreateQuery<TElement> method is called. it is important to understand the difference in the <T>'s here. The query itself is a generic type, NHibernateLinqQuery<TResult>, where TResult is the entity that we are querying. The result of the query may be different than TResult, because we may use projection to get only some of the values, or select a child item values.

Therefor, we need to return a new IQueryable<TElement>, which is why I am creating a new instance of the same class, passing it the current state of the objects, and continue to parse the expression tree.

As you can see, I am only handling the Select and Where methods at the moment. My naming convention at the moment is "HandleXyzCall" is to handle a query method, while "ProcessXyz" it to process an expression type.

public IQueryable<TElement> CreateQuery<TElement>(System.Linq.Expressions.Expression expression)

{

       MethodCallExpression call = (MethodCallExpression) expression;

       switch (call.Method.Name)

       {

              case "Where":

                     HandleWhereCall(call);

                     break;

              case "Select":

                     HandleSelectCall(call);

                     break;

       }

 

       foreach (var crit in CurrentCriterions)

       {

              rootCriteria.Add(crit);

       }

       return new NHibernateLinqQuery<TElement>(session, rootCriteria);

}

The HandleWhereCall is very simplistic, it just process all the arguments passed to the where clause:

private void HandleWhereCall(MethodCallExpression call)

{

       foreach (Expression expr in call.Arguments)

       {

              ProcessExpression(expr);

       }

}

The select method is a bit more complex, since it need to handle projections and other various interesting features. I am not going to show it here, because it is over 50 lines of codes and it is very state machine like. Not really interesting.