NHibernate is lazy, just live with it

time to read 7 min | 1301 words

At a client site, I found the following in all the mapping files:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false">
    <class name="Machine" table="Machines" lazy="false">
        <id name="Id">
            <generator class="identity"/>
        </id>
        <property name="Name"/>
        <set name="Parts" table="Parts" lazy="false">
            <key column="BlogId"/>
            <one-to-many class="Part"/>
        </set>
    </class>
</hibernate-mapping>

I think that you can figure out by now what is wrong. It is too bad that the <blink/> tag has gone out of use, because that would be an accurate reflection of how I responded when I saw that. After I finished frothing at the mouth, I explained that you never ever do things like, because it leads to pain, mayhem and tears.

The lead dev heard me out, then explained that their situation wasn’t a standard web application, their server had some business functionality, but it served mainly as a way for clients to come in and get some data. The architecture looks like this:

image

As you can imagine, NHibernate is sitting in the Application tier, but a lot of the work is done on the smart client application.

The lead dev explained that in their scenario, they needed to send the full entity to the smart client app, so they didn’t want lazy loading. Their scenario also precluded the need to do lazy loading over the network, so all was good, and they made the decision to use lazy=”false” intentionally. Their model was pretty good about not having deeply connected object graphs, too.

I hit Google and searched for the relevant posts:

* Googling that made me very nervous.

I include the search terms as a reminder to myself that post titles are important, because that is how I search for them after the fact. And I got some really strong (and strange) looks when I did that.

After we discussed that we put it aside and moved to other topics. Until we came to the part where we profiled the system.

We immediately found some trouble spots that we needed to resolve. One of them was getting a list of machines that required fixing.

The problem was that the code looked something like this:

public IEnumerable<Machine> GetMachinesRequiringFixes()
{
    return (from machine in session.Linq<Machine>()
           where machine.Status == Statuses.Broken
           select machine).Take(10);
}

Looks easy, right?

Except that this seemingly innocent piece of code was responsible for 400 queries.

What went wrong? How could loading 10 machines result in that many queries?

Well, let us look what NHibernate did, shall we?

select top 10 * from Machines where Status = 'Broken'

This gives NHibernate ten machines instances, except that when NHibernate investigate the mapping for a Machine, it realizes that it needs to load the Parts collection, since it was marked lazy=”false”, so NHibernate will execute ten queries for loading each Machine’s Parts.

select * from Parts where MachineId = 40
select * from Parts where MachineId = 41
select * from Parts where MachineId = 42
select * from Parts where MachineId = 43
select * from Parts where MachineId = 44
select * from Parts where MachineId = 45
select * from Parts where MachineId = 46
select * from Parts where MachineId = 47
select * from Parts where MachineId = 48
select * from Parts where MachineId = 49

But each Part also has an association for a MaintenanceHistory, which was also marked lazy=”false”, so NHibernate had to load those as well. (I’ll spare you that one :-) ).

From the client perspective, Machine,Parts & Maintenance History where all part of the same entity, and they wanted to be able to be able return that to the user in a single call. (More on that fallacy in my next post).

The problem is that when you set lazy=”false”, you literally ties NHibernate’s hands. Now, you could start playing with the fetch mode option, to change that to join or subselect, but the problem is that this works when you have only one collection association (in the entire graph), because otherwise you run into sever Cartesian products issues.

Now, NHibernate has the ability to handle just that scenario, but by setting lazy=”false”, you prevent NHibernate from having the chance to actually utilize them.

There is a good reason why lazy is set to true by default, and while I am sure that there is some limited number of scenarios where lazy=”false” is the appropriate choice, it isn’t for your scenario.