Two way one-to-many associations in NHibernate

As nice as the example from my previous post was (person having pets) it exhibits a problem. The problem is related to the fact that by their very nature associations in relational databases are bidirectional, whereas in objects they are unidirectional. Here we hit the mythical impedance mismatch.

Often however we want to have a bidirectional association in our object model. Person may have a set of pets, but then each pet has its owner. Then we have another problem. What if we add a pet to person’s pet collection, but forget to set pet’s owner? We’ll get inconsistencies.

The best way to alleviate that (or the best way I found. Remember that the big warning sign from the previous post still holds true) is to decide on using one side of association for manipulation, and have it modify the other.

public class Pet
{
    private Person _owner;
    public virtual Guid Id { get; protected set; }
    public virtual string Name { get; set; }
 
    public virtual Person Owner
    {
        get { return _owner; }
        set
        {
            if (Equals(_owner, value))
            {
                //there's no need to change anything
                return;
            }
            //sorry old owner, I'm no longer yours
            if (_owner != null)
            {
                _owner.Pets.Remove(this);
            }
            _owner = value;
            if (_owner != null)
            {
                //welcome to my new home
                _owner.Pets.Add(this);
            }
        }
    }
}

Here you see that when we set the Owner for a pet it takes care of removing the pet from the old owner, and adding it to the new one.

Is it all we need? Well, not quite, we forgot about the mapping.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="ConsoleApplication1" assembly="ConsoleApplication1">
  <class name="Pet">
    <id name="Id">
      <generator class="guid.comb" />
    </id>
    <property name="Name"/>
    <many-to-one name="Owner" column="OwnerId" />
  </class>
</hibernate-mapping>

Notice one important thing. In our many-to-one we have set the value of column attribute to be the same as the one-to-many end’s key column attribute (see Person’ mapping in the previous post or below). If we didn’t NHibernate would create two different columns for that and I’m not sure how it would affect the behavior. Probably it’s something you’d want to avoid.

Are we done? Let’s see.

using(var session = factory.OpenSession())
using(var transaction = session.BeginTransaction())
{
    var krzysztof = new Person();
    new Pet {Name = "Rex", Owner = krzysztof};
    session.Save(krzysztof);
    transaction.Commit();
}

When we run that piece of code, with the mapping we have, here’s what will happen.

nh_2_additional_update

NHibernate will issue two INSERTS and one UPDATE (this is exactly the same SQL as code from my previous post would yield. The difference is, we can get rid of the UPDATE now). NHibernate Profiler shows us an alert by the UPDATE saying that its superfluous and if we used inverse=”true” it would go away. So let’s do just that.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="ConsoleApplication1" assembly="ConsoleApplication1">
  <class name="Person">
    <id name="Id">
      <generator class="guid.comb" />
    </id>
    <property name="HasPets" access="readonly"/>
    <set name="Pets" cascade="all" inverse="true" >
      <key column="OwnerId" />
      <one-to-many class="Pet" />
    </set>      
  </class>
</hibernate-mapping>

If we run the application now, the additional UPDATE will go away, as advertised.

nh_2_no_update

Ok, we can INSERT our association into the DB, so let’s now SELECT it back.

using(var session = factory.OpenSession())
using(var transaction = session.BeginTransaction())
{
    var krzysztof1 = session.Get<Person>(id);
    foreach(var pet in krzysztof1.Pets)
        Debug.Assert(pet.Owner == krzysztof1);
 
    transaction.Commit();
}

Surprisingly, if we run this code we’ll get an exception:

NHibernate.LazyInitializationException was unhandled
  Message=”illegal access to loading collection”

The problem is that when creating Pet instances NHibernate sets the Parent property. Our setter tries to add the pet to Person’s Pets collection which is not initialized, hence the exception.

So how do we fix it? Same as the last time. We want to bypass the setter and set the owner directly into the field. We don’t need our setter’s code anyway since at this point NHibernate takes care of maintaining integrity of the association.

So we only need to add appropriate access attribute to our many-to-one mapping and we’re all set.

<many-to-one name="Owner" column="OwnerId" access="nosetter.camelcase-underscore" />

Hope this was helpful.

DNK Tags: ,

Comments

Ben Hyrman says:

Looks very good Krzysztof. One comment. Where possible, it’s best to keep your collection setter private or left out. That forces your consumers to go through your getter. Things get crabby if you load a collection through NH and then replace it with a new collection. Always better to do a .Clear() and then add instead.

Dragan B. says:

I had the same issue with NHibernate.LazyInitializationException using NHibernate. After pulling my hair off for hours, a colleague of mine noticed that mapping for one unrelated field in dependant table was of a wrong type (it stood Int16 instead of Int32), corrected that and it started working.

It is very weird that this error was removed only by this intervention, seams like NHibernate made a wrong guess about what the problem really is. This is not the first case when exceptions returned by NHibernate were pointing to something that looked very complicated and it turned out to be a wrong mapping.

So I advise you and others to double check your mappings again.

Mojito says:

Is this supposed to work with many-to-many relations too? When I try that workaround with many-to-many, NHibernate returns an error.