donderdag 9 maart 2006

NHibernate: where's the productivity ?

I've been playing around with NHibernate the past few days, and I've encountered a rather strange bug...

I wanted to perform a rather simple HQL query. I wanted to know the total value of the Orders that were placed by a Customer.
So, I thought that this simple HQL query would do the job:

IQuery q = theSession.CreateQuery("select sum(ol.Price * ol.NumberOfItems)
from OrderLine ol");

However, this didn't work...
This query did work however:

IQuery q = theSession.CreateQuery("select sum(ol.Price) from OrderLine ol")

It appeared that the query (the one with the multiplication in the sum function) that was sent to the database looked like this:

select sum(orderline0_.NumberOfItems*orderline0_.ItemPrice) as x0_0_
from tblOrderLine orderline0_

while NHibernate was then trying to access a result-column that was called x1_0_
This will offcourse not work, since the field has been given the alias x0_0_

After some searching, I've found out that this was a bug that also existed in Hibernate, but has been fixed in Hibernate 3.0.

So, I had to look for an alternative.
The idea came up that I could retrieve all the orderlines that belong to a specific Customer, and then, do the calculation myself.
So, I came up with this HQL query:

IQuery q = theSession.CreateQuery ("from OrderLine ol " +
" inner join Order inner join Customer " +
" where Customer.Id = :custId");

This didn't work as well... An exception was thrown saying that the BY keyword was expected after a GROUP or ORDER. However, I didn't use any group by or order by statements ? Then, I've noticed that I've a class that's called Order, so maybe I had to escape the classname.
However, I couldn't find how to do that (if it is possible), so I've rewritten the query to something like this:

IQuery q = theSession.CreateQuery ("select ol " +
" from OrderLine ol, Order o, Customer c" +
" where ol.Owner = o and o.Owner = c " +
" and c.Id = :custId");

This didn't work as well...
The exception now said:
Could not resolve property:Id of Customer
I found this very strange, since this class has an Id property.
After some research, I found out that the Id did not appear in my hibernate mapping file indeed.
This is, because the Id is used as the 'id' in my mapping file, and, since the Id property is read-only in my class, I use a field access method to set this field. This is specified in my hbm mapping file like this:

<class name="NamespaceName.Customer, NamespaceName" table="tblCustomer">
<id name="id" access="field" column="Id"... >

So, what should I do next ? I didn't want to make the Id property of my Customer clas s writable.

I tried to add the Id property to my Customer mapping file, without touching the 'id' element.
So, I've added the Id property to my mapping file, but, since I've introduced the same database column twice, I had to use some attributes:

<class name="NamespaceName.Customer, NamespaceName" table="tblCustomer">
<id name="id" access="field" column="Id"... >
<property name="Id" update="false" insert="false">

Now I tried to rerun my query. However, retrieving a Customer was already a problem. I received an exception that said that the property value of the Id property could not be set via reflection. Damn.
I really don't want that somebody can set the Id of a Customer via the property...

Luckely, .NET 2.0 came to the rescue. In .NET 2.0, you can define different access-modifiers on your property getter and setter. So, my Customer class looked like this :

public class Customer
private int id;

public int Id
get { return id; }

I've changed this to

public class Customer
private int id;

public int Id
get { return id; }
private set { id = value; }

And this finally did the trick...

I've spent something like 2 hours in getting something simple like this to work in NHibernate. With a self-written Data Access Layer, this would have taken me something about 10 minutes I guess, to implement this functionality.
Conclusion: there's a lot of work to be done on NHibernate before I will really call it 'productive'. However, it is a promising project though.

For more information about my quest, I refer to a topic I've opened regarding this issue on the NHibernate support forum.

3 opmerkingen:

Anoniem zei

Not germane to the part of your code you really care about, but regarding your setter issue. NHibernate has supported access strategies for quite some time.

For your situation, since you have a private int id and a public int Id get, you can define Id as follows:

id name="Id" column="GovEntityId" type="Int32" unsaved-value="0" access="field.camelcase"

The key is Access="field-strategy.naming-strategy".

You could also use access="nosetter.camelcase". It would use the property for get and the field for set.

Frederik Gheysels zei

Yes indeed, that's a much better solution which I'm using at this time. ;) thx.

Anyway, the original problem I have is still not solved. I just checked it with NHibernate 1.2.0 alpha.

A while ago, I've created an issue for this on the JIRA list, but I think no-one noticed it...

Frederik Gheysels zei

Today, I've received an update of the bug I've listed a while ago.

This bug should be fixed in NHibernate 1.2.0 beta2.