Introducing the NHibernate Query Generator

Okay, it looks like I can track down fairly easily what books I read, when I read Working Effectively with Legacy Code, I wrote Rhino Mocks. Now I am reading Applying Domain-Driven Design and Patterns, and I wrote the NHibernate Query Generator.

So, what is this all about?

NHibernate has two major API for querying objects. The first one is called the query API, and uses HQL, a language similar to SQL but with OOP aspirations. The second is called the criteria API, and it is far simpler API, but it is also much easier to use.

Here is a simple example of using the criteria API using my Repository sample from yesterday:

ICollection<Customer> customersInLondon = Repository<Customer>.FindAll(Expression.Eq("City""London"));

Now, those of you that already knows me know that I have a passionate dislike to strings. I wrote a mock objects library because of this issue, so I may need physcological therapy in the future, but I hate strings.

Well, what do I do when there is something that I don't like? I fix it.

In this case, there where two main options, run time proxying and code generation.

Run time proxying is similar to what I do in Rhino Mocks, and it may look like this:

ICollection<Customer> customersInLondon = Repository<Customer>.FindAll(Expression.Eq(Expr.Of<Customer>().Name,"London"));

This has the disadvantages of being both ugly and complex, so I turned to the other route. I build a small application which takes an NHibernate mapping file (hbm.xml) file, and output a strongly typed query wrapper. The above query can now be used like this:

ICollection<Customer> customersInLondon = Repository<Customer>.FindAll(
   Where.Customer.City.Eq("London") );

This is much better, and of course that you get all the other nice things about the expression API (like Between, In, etc).

You can get it here, binaries only. Source would have to wait for a while, my subversion provider has run out of room. Note that the code it generates relies on a class called NamedExpression. The class is included in the binary, so just add it to your project and you are done.

I also added support for queries, since I didn't want to leave the other side of the API high and dry, it means that you can do this:

ICollection<Customer> customersInLondon = Repository<Customer>.FindAll(
    Queries.AllCustomersInCity, new Parameter("city""London"));

Here is a little FAQ

  • What does it generate:
    The tool generate strongly typed wrappers for each of the mapped entities and each of their properties. At the moment, is supports querying against properties, many-to-one, and ids.
  • How to make it work for all the files in a directory?
    Simply use the batch capabilities of the shell, like this:
    for %f in (*.hbm.xml) DO "NHibernate.Query.Generator.exe" %f D:\Queries
    This will run the tool over all the files in the directory, and output them all to the Queries dir.
  • How to make it work with ActiveRecord?
    You need to use the isDebug="true" flag in the configuration, and then point the tool to the resulting mapping files.
  • Can I get is as a custom tool for Visual Studio?
    It shouldn't be hard to do, but I don't have the time at the moment. Feel free to send me a patch for it when I release the code.

Print | posted on Monday, July 31, 2006 8:34 PM

Feedback


Gravatar

#  7/31/2006 9:58 PM Tomas Restrepo

Interesting work; will definitely have to test drive it in the future. I'm currently working completely on the opposite spectrum using HQL expressions almost exclusively intead of the criteria API. The reason for this is being able to keep the queries separate.

In particular, I built a small framework for our application where we have &quot;views&quot; of the data defined as external XML files which contain an HQL query (parameterized, of course) and mapping of the view to a set of columns. From there on most of the work is fully generic as we do some reflection + xml magic to render the customized view of the objects into grid controls. Its fairly flexible and we can easily change the queries without touching the code.


Gravatar

#  7/31/2006 11:21 PM Ayende Rahien

I find that about 80% of the work that I do is very simple queries (and 20% is very _complex_ queries :-) ).
For those simple queries, this API is very good, since it is very explicit .
I am interested in what you mean my views as HQL? Do you mean that you use the projection capabilities?
I.e:
Select item.id, item.name from Item item where item.date = :date

And then pass it to the grids?

Personally, I found that passing a collection of object to the grid works in 90% of the time, and using BindingList of T usually solves the other 9%


Gravatar

#  8/1/2006 1:11 AM Alex

I like the syntax a lot. I have been aiming for something similar in something I am working on too.

However I have a bit of a problem... in your example your Where class is specific to a specific mapping file. Unfortunately my mapping files are used to define units of re-use, a little like assembly references... i.e. one mapping file can reference another.

So I can't think of a way of making Where work that can be extended as each new mapping files is added to a particular solution.

At least not without C#3.0 extension methods).

Why? Inheritance won't work because you can easily have multiple dependencies for a mapping file, which would of course imply the need for multiple inheritence.

I'd be interested in whether you can think of a cool solution to this intriguing problem.


Gravatar

#  8/1/2006 3:11 AM Steve

All you need now is some operator overloading and you'll be in business :)

ICollection[Customer] customersInLondon = Repository[Customer].FindAll(Where.Customer.City == &quot;London&quot; &amp;&amp; Where.Customer.Age &gt; 20);


Gravatar

#  8/1/2006 6:51 AM Ayende Rahien

@Steve,
that is the next stage :-)


Gravatar

#  8/1/2006 6:53 AM Ayende Rahien

@Alex,
I am afraid that I completely don't understand what you mean.
HBM files can't (to my knowledge) include other files.

The generated code is using partial classes and follow the structure of the classes defined in the XML so you can have:
Where.Customer.Id.Eq()
Where.Customer.ValuedCustomer.Id.Eq()
Where.ValudedCustomer.Eq()


Gravatar

#  9/27/2006 8:09 PM Bill Pierce

Cooked up a simple MSBuild bit 'o code for using your lovely little utility.

&amp;lt;ItemGroup&amp;gt;
&amp;lt;HbmFiles Include=&quot;Models\*.hbm.xml&quot; /&amp;gt;
&amp;lt;Compile Include=&quot;Query\*.cs&quot; /&amp;gt;
&amp;lt;/ItemGroup&amp;gt;
&amp;lt;Import Project=&quot;$(MSBuildBinPath)\Microsoft.CSharp.targets&quot; /&amp;gt;
&amp;lt;Target Name=&quot;BeforeBuild&quot;&amp;gt;
&amp;lt;Exec Command=&quot;..\..\tools\NHibernateQueryGenerator\NHibernate.Query.Generator.exe cs %(HbmFiles.FullPath) Query\&quot; /&amp;gt;
&amp;lt;/Target&amp;gt;

Just add that to your .csproj file and you are in business.


Gravatar

# Book Review: Applying Domain-Driven Design and Patterns 2/3/2007 7:48 AM www.ayende.com


Gravatar

# hdmsqxet 3/17/2007 12:16 AM hdmsqxet

hdmsqxet


Gravatar

# jxgcktia - Google Search 3/17/2007 12:16 AM Pingback/TrackBack

jxgcktia - Google Search

Comments have been closed on this topic.