Ayende @ Rahien

It's a girl

NHibernate 2.0 and Linq

Linq for NHibernate is not part of the 2.0 release. Linq support is planned for the 2.1 release. That said, we have been getting a lot of questions about that.

The technical reasons are not really interesting, but suffice to say that to provide good Linq support we also need to modify NHibernate slightly. Those changes happens on the trunk, which is what Linq for NHibernate is following.

However, due to all the questions that we got, I wanted to point out that Daniel Guenter has back ported the current version of Linq to NHibernate to Nhibernate 2.0 and made it available here.

Now, the disclaimer. We are not supporting this. This is an unofficial (but welcome) contribution from the community. It is likely to have bugs (in fact, we know it contains bug in the more complex Linq queries, that is why it is still under active development), and the NHibernate team response is most likely going to be, use the latest, which requires the NHibernate trunk, not the NHibernate 2.0 version.

Use at your own risk, etc.

Comments

Tuna Toksoz
09/11/2008 04:20 AM by
Tuna Toksoz

Contrib Trunk. Linq on NH trunk doesn't have a single passing query test yet.

Rainer Schuster
09/11/2008 09:53 AM by
Rainer Schuster

Grabbed the actual contrib trunk.

  1. delete the refenrece to the nhibernate.dll and add the new one from the TunasExperiment\NHibernate.Linq\lib directory.

  2. modify app-config to your local mashines Sql-Server

  3. Have fun

Frans Bouma
09/11/2008 11:15 AM by
Frans Bouma

Code like this:

protected Expression TransformSelectCall(MethodCallExpression expr)

{

Expression source = this.Visit(expr.Arguments[0]);

LambdaExpression lambda = LinqUtil.StripQuotes(expr.Arguments[1]) as LambdaExpression;

Expression body = lambda.Body;

ParameterExpression parameter = lambda.Parameters[0];

string alias = parameter.Name;

System.Type type = lambda.Body.Type;

System.Type resultType = typeof(IQueryable<>).MakeGenericType(type);

return new SelectExpression(resultType, alias, body, source, null);

}

will never succeed. The problem is that parameter -> source tracking has to take place. You can't simply re-use the parameter name, as a parameter can be named equally in every new lambda in the query. You therefore need indirection alias objects: objects you use as an alias which refer to the set you want to refer to.

This particularly flaps in your face with queries with for example SelectMany calls joining Join results etc.where also where clauses are referring to the left/right side of the SelectMany calls.

Writing a linq provider is no picknick. After 9 months full time development we still have edge cases not working here and there, however they tend to fall into the more bizarre category with a lot of nested froms used with defaultIfEmpty on joined sets and mulitple hops (e.a.b.c.Foo==bla) combined with where clauses which have to be moved around in the query (but not always).

My advice to you: continue with the old batch, it had more potential.

Ayende Rahien
09/11/2008 02:02 PM by
Ayende Rahien

Krzysztof,

When it is ready

Jan Limpens
09/11/2008 03:20 PM by
Jan Limpens

what is "the old batch"?

Tuna Toksoz
09/11/2008 04:54 PM by
Tuna Toksoz

Frans, the problem is not only expression tree-sql parsing but also NH codebase.

NH codebase is just huge! Expression tree-sql also hard.

I take this as personal since this piece of code was written by me, and I don't say it will owrk perfect. It is at its early baby steps.

You may easily see what will work and what not, but i can't see. Please be encouraging, not discouraging.

Fabio Maulo
09/12/2008 01:54 AM by
Fabio Maulo

Strange... very strange....

The port of NHLQ of NH.Contrib was ported outside NH.Contrib and is "unofficial" ?

Why "unofficial" and why out side NH.Contrib ?

No info about this in NHForge ?

Bah?!??!?

Frans Bouma
09/12/2008 08:51 AM by
Frans Bouma

ARGGG... Tuna, I wrote a very lengthy reply and when I clicked submit I got:

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:

[InvalidOperationException: Sorry, but we cannot accept this comment.]

Subtext.Web.UI.Controls.PostComment.LastDitchValidation() +148

Subtext.Web.UI.Controls.PostComment.btnSubmit_Click(Object sender, EventArgs e) +65

System.Web.UI.WebControls.Button.OnClick(EventArgs e) +111

System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +110

System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +10

System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +13

System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +175

System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1565

clicking 'back' revealed an empty form :X.

Very short recap: I didn't want to discourage you, I gave you advice even (the alias objects). Please read my blog about articles writing linq to llblgen pro to read about some details of how to write such a provider.

The reason I posted my post above was that I made the exact same mistake. You'll realize that it's wrong because parameters are only unique inside a lambda. If you use extension methods instead of 'from' or 'where', you'll realize that you can use the same parameter name over and over.

It's a lot of work, so do realize that you'll run into a lot of problems to overcome. Try to convert the tree in multiple passes into elements you can match in a visitor. E.g. method call to Where into WhereExpression (class you write yourself) etc. During multiple passes, the tree then reduces into elements which are mergeable into a single query. I don't think NH core code has to change at all for this, unless it hasn't projection to custom type code.

My provider is about 1.2MB of C# code, so it's a lot of code to cover almost everything. And even then I still ran into problems like this one:

http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=14181

which took me 2 weeks to fix (about 5 days full time work on and off). it's therefore key to first understand what to do to reduce subtrees into an expression you can match and recognize immediately. Use a visualizer to visualize the trees in a treeview with extra info so you can easily see how the tree reduced with each step, something like this:

http://weblogs.asp.net/fbouma/archive/2007/10/30/developing-linq-to-llblgen-pro-part-8.aspx

It's a modified debugger visualizer which is inside the C# examples.

Take one step at a time, and try to understand first what's going on, what's the reason the tree looks like that and not differently. This time, forget TDD altogether at first, TDD won't bring you a good provider in this case, as refactoring a linq provider is simply very very difficult because altering code for some expression can make it fail in another situation you haven't anticipated in a test, even if you have thousands of tests.

Tuna Toksoz
09/12/2008 04:26 PM by
Tuna Toksoz

Frans, I read your blog and enjoy them a lot. I took your first one offensive, maybe just because I were sensitive yesterday. I'll(and the team) follow your advice, but right now I am trying to have something work with NH(it is a simple query with where f.SerialNumber=="someserial". This is not related with Linq directly, but related with NHibernate, I am trying to understand it.

Thanks for your comments, they mean a lot for me.

And for tdd case, you are right but i think that it is best to have queries test as we did in Contrib Linq. The simple expression reducers are just to warm up.

Frans Bouma
09/12/2008 05:11 PM by
Frans Bouma

Sure, tests are needed, to see if what you wrote even works and you were right about what you thought should work ;). We use them too. They're also a lock on the door when you do change something, but shouldn't give that much confidence in this case, as everything works so much together.

Ideally, one would use different visitors / handlers for different subtrees, e.g. a tree inside a projection is a different scope and therefore different visitors could be used. This leads to more isolation of the problems which arise, however it also runs the risk of duplicated code: often a handler might look the same as in other scopes. In practise this thus leads to a single handler for a single pass. Problem is that a handler for a given expression type E is therefore used in different scopes so changing it affects ALL these scopes.

Due to no documentation at all and no standard whatsoever for expression trees (so be prepared for running your code with VB.NET as that compiler creates different trees) it's unknown in which scopes an expression could pop up and therefore in which scopes a change has any effect or SHOULD have any effect or not. I learned in the past few months that that was the main cause for a lot of the bugs we ran into. Unfortunately, it's hard to solve that problem other than simply try/error rinse repeat....

Humberto C Marchezi
09/15/2008 12:26 AM by
Humberto C Marchezi

Hi,

Do you plan to support EntityRef and EntitySet in future NH releases ?

I know I can use this in my current NH version but it will actually ignore it but the reason I ask this is because proxies can be a headache sometimes when I have to work with casting involving objects that are part of class hierarchies or when I have to inspect objects in Visual Studio debug mode.

Ayende Rahien
09/15/2008 12:36 AM by
Ayende Rahien

EntityRef and EntitySet ?

Why are they interesting?

Mike
10/19/2008 09:48 PM by
Mike

with the current nh.linq contrib are subqueries like this possible? I cant seem to get them to work

from c in Customers

select new

{

c.Name,

Purchases =

    from p in Purchases

    where p.CustomerID == c.ID && p.Price > 1000

    select new { p.Description, p.Price }

}

Fred
10/22/2008 03:45 PM by
Fred

....Why using Nhibernate to emulate MS ORM (or whatever they call it)

I can't see why Nhibernate would require Linq support...

It could probably help out some people who do not really require an ORM.

When will you supply fetching strategy in Linq ?

When will you supply multiple returns with specific fetching strategy ?

Attaching / Detaching ?

HSQL / ICriteria / IQuery are far more powerfull.

I understand when you are using LLBL that queries would benefit from linq support, but nhibernate...

At least, the most interesting features is still available :

Linq to object on query results if required.

the same way as automated mapping it's a waste of time.

How intelligent and powerfull, no tool can understand business requirements from data model.

Except northwind or petshops....

Comments have been closed on this topic.