Bobby Diaz did some more work recently, and has documented it here. Interesting reading...
Following several good patches, I have added Bobby Diaz to the Rhino Tools project, which currently includes the Linq for NHibernate implementation as well. I expect that the new stuff will be up in the repository shortly.
Just to remind you, I am still looking for more volunteers to help with the implementation, so if you are into futuristic technologies, just dive right into the code and the implementation details. And be sure to send me (or Bobby) a patch or two.
Bobby Diaz has implemented orderring and paging support for Linq to NHibernate, so this works:
(from c in nwnd.Customers select c.CustomerID)
.Skip(10).Take(10).ToList();
As well as this:
where c.Country == "Belgium"
orderby c.Country descending, c.City descending
select c.City;
Thanks again, Bobby, and the new code is on SVN.
Jeremy has a great post summarizing the MVP summit, and he include:
- Web service call is (a remote remote call - extranet/internet)
- Non relational databases
- Hierarchical data stores
- Relational databases
Now consider the following query:
where c.Name == "Ayende" select c).First();
- If the web service expose a GetCustomerByName(), this would be a good candidate, if not, the implementation would need to call GetAllCusomters() and filter it in memory.
- For non relational databases, I am aware of object databases, flat files, temporal and hierarchical (covered seperatedly) - each of which has its own tradeoffs. I am not familiar with object databases to say what the tradeoffs are here, but for a flat file, it is going to be a linear scan. The query is not even a valid one for a temporal database (unless there is an implict CurrentTime).
- For hierarchical data stores, this query would need to iterate all the customers, and compare their name to the query.
- Relational database would think that this is too easy, and quit.
And this is just about the simplest query possible. I can't guess what will happen if I happened to want a join between customer and orders.
I get the feeling that I am missing something here, because it sure isn't heterogeneous to me.
I got a couple of great patches yesterday and today, Bobby Diaz sent a patch to add support for Count() and DataContext like behavior. Jon Stelly went and cleaned up my code, turning it from a sleep-deprived hack into a respectable project. You can see his full list here. Thanks to their efforts, this now works:
I changed the implementation of NHibernteLinqQuery to use the visitor pattern, which is the natural way that I think about walking through an AST. It is still very primitive one, but it does the job that we need right now.
The new code is in the repository:
Bobby and Jon, thanks again. Everyone else, you are invited to the party as well.
Jeff Brown commented on my Linq Options post:
Linq is not just anonymous delegates. (I should be clear that I am mostly thinking about the abilities of Expression rather than the Langague Integrated Query here). It means that I can start doing some really stuff. For what it worth, there is such a thing as the ExecutionScope for linq, but I am not sure what it is supposed to do, as far as I can see, it is the entire lexical scope for the expression.
Here is a trivial example that shows what you can do with it. Assume that I have this work item (and saved action):
[Serializable]
public class WorkItem
{
string name;
string action;
string on;
public WorkItem(string name, string action, string on)
{
this.name = name;
this.action = action;
this.on = on;
}
public WorkItem() {}
public void DoAction()
{
Console.WriteLine(name +" "+action+" " +on);
}
}
[Serializable]
private class SavedAction
{
public object target;
public string method;
}
And I have this code:
public delegate void Act();
static void Main(string[] args)
{
WorkItem wi = new WorkItem("Ayende,", "write", "blog post");
Save("Temp.action",() => wi.DoAction());
Act act = Load("Temp.action");
act();
}
What is going on here? I am saving the labmda into a file in the Save(), then load and execute it in the next two statement. Sadly, Linq's Expression<T> are not serializable, which I consider a huge minus, but for this example, I worked around it a bit. Here is the code for the Load, which isn't really interesting:
private static Act Load(string file)
{
SavedAction action;
BinaryFormatter bf = new BinaryFormatter();
using (Stream s = File.OpenRead(file))
action = (SavedAction)bf.Deserialize(s);
return (Act)Delegate.CreateDelegate(typeof(Act), action.target,
action.target.GetType().GetMethod(action.method));
}
The Save() is where the real magic begins, I compile the expression, extract the target, extract the method that was about to call, and save it, for later processing in the load.
private static void Save(Expression<Act> actionToSave)
{
Act act = actionToSave.Compile();
ExecutionScope scope = (ExecutionScope)act.Target;
SavedAction action = new SavedAction();
MethodCallExpression l = (MethodCallExpression)actionToSave.Body;
action.target = Expression.Lambda(l.Object).Compile().DynamicInvoke();
action.method = l.Method.Name;
BinaryFormatter bf = new BinaryFormatter();
using(Stream s = File.Create("Temp.action"))
bf.Serialize(s, action);
}
I am very excited about these capabilities. Yes, I can do it today, but the inteface I would have to expose is wholly unatural, while Linq provide for much nicer alternative.
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.
The code that I am going to show is working code. I have it passing tests on the March 2007 CTP, against NHibernate 1.2 CR1.
Select entity and filter:
var query = (
from user in session.Linq<User>()
where user.Name == "ayende"
select user
).ToList();
Project a property:
var query = (
from user in session.Linq<User>()
select user.Name
).ToList();
Project anonymous type:
var query = (
from user in session.Linq<User>()
select new { user.Name, user.RegisteredAt }
).ToList();
Where do you get that?
You can get the source from Subversion:
You can also download it from the download page.
Caveats:
- It is by no means a complete Linq implementation. The 101 examples for Linq are a good test metric to go against, in my opinion, and I am only hitting 13 of them right now (probably more, but I have tests for 13).
- I read the C# 3.0 spec when it was out in PDC '06, and last night I got the March CTP and started working on an implementation. This is literally my baby steps with Linq.
- I am not going to invest a lot of time in this. It is still far into the future, and I mostly did it to make people stop wishing for it.
Important - patches:
I would appriciate it if you will send bug reports with patches to fix them.
Have fun, and don't make too much out of it.
As usual, just a set of random impressions:
- Anonymous types are annoyingly limited. You can't define arrays of anonymous types, for instnace, which can open up more options for intent revealing code.
- Linq seems to be heavily based around the idea of Expression, a lot of the stuff that is going on there revolves around Expression and operation on Expressions.
- Here is a new way to assert that a collection contains all the items that it should:
Assert.AreEqual(3, list.Intersect(new string[]{"ayende","rahien","bar"}).ToList().Count); - I am annoyed that this doesn't work, though:
Assert.AreEqual(3, list.Intersect({"ayende","rahien","bar"}).ToList().Count);Shouldn't this be handled by the collection initializer?
Insomniac dreams. I am awake since about 2:30 AM (it is 07:30AM now), which meant that I had a chance to read my feeds, catch on my emails, do some OSS work, release a new beta and post too much :-). Just thought that I should explain why you see so many posts all of a sudden.
Anyway, the reason for this post is asking how the ADO.Net Entity Framework will handle dynamic queries. I am talking about something like this example. I just read John's post about the Entity Framework, and the question just came up in my mind.
