Implementing Linq for NHibernate: A How To Guide - Part 1
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:
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.
Comments
Looks like there are others trying to create open source linq providers as well...
http://code2code.net/Linq_Mysql/
Linq for NHibernate Updates: Adding a new committer
Pingback from http://oakleafblog.blogspot.com/2007/03/third-party-linq-providers.html
It was only a matter of time before more people started work on extending the LINQ structure to other...
This series of posts is going to be interesting whether you use NHibernate or not as it starts to...
Following Ayende's lead, I am going to continue to explain how we are implementing the Linq provider
The previous posts in this series can be found here: Part 1 - Implementing Linq for NHibernate (by Ayende)
Last month I started a series of posts covering some of the new VB and C# language features that are
In the spirit of LINQ to Amazon now comes LINQ to Flickr . Mohammed Hossam El-Din ( Bashmohandes ) proposes
A while back, I posted on lambda expressions in C# 3.0 . In that post, I concluded by saying the following.
什么是Lambda表达式?Lambda表达式为编写匿名方法提供了更简明的函数式的句法,但结果却在编写LINQ查询表达式时变得极其有用,因为它们提供了一个非常紧凑的而且类安全的方式来编写可以当作参数来传递,在以后作运算的函数。
Whats the point? we can bypass hiberanate and go straigt to the store can we not. Screw hibernate.
David,
Please moderate your language.
The point is getting the nice Linq syntax with all the power in NHibernate.
Great stuff,
Combining LINQ with the power of NHibernate should be awsome. Especially considering MS is dropping the LINQ to Entities from Orcas...
Comment preview