Read only collections and properties with NHibernate

warningsign

I’ve been working with NHibernate for the last couple of days, and as I make my way though it, I find out about things, that were not so obvious to me at first, so I decided to post them here, so that someone else can benefit as well.

First thing you learn about NHibernate (well ok – first thing I learned about NHibernate, but most of you probably as well) is that it requires you to mark your properties virtual, have parameterless constructor, and pay special attention to your GetHashCode() and Equals() methods.

With all that in mind (and following many tutorial that are out there) you may start crunching your entity classes to look like this:

public class Person
{
    public virtual Guid Id { get; set; }
    public virtual ISet<Pet> Pets{ get; set; }
    public virtual bool HasPets 
    { 
        get { return Pets.Count > 0; } 
        set { /*do nothing*/}
    }
}

with mapping like this:

<?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"/>
    <set name="Pets" cascade="all" >
      <key column="OwnerId" />
      <one-to-many class="Pet" />
    </set>      
  </class>
</hibernate-mapping>

This gets the job done, but it’s far from being persistence ignorant. You usually don’t want to expose a setter for your collections, so that they can be swapped. Also the set accessor on HasPets property is nothing short of an ugly hack. Although we have no explicit sign of NHibernate in the code, it is anything but persistence ignorant.

You can however make it so.

public class Person
{
    private readonly ISet<Pet> _pets = new HashedSet<Pet>();
 
    public virtual Guid Id { get; protected set; }
 
    public virtual ISet<Pet> Pets { get { return _pets; } }
 
    public virtual bool HasPets
    {
        get { return Pets.Count > 0; }
    }
}

Now it looks like a “normal” class. Will it work with NHibernate now though? – Absolutely*. The trick is to use mapping files appropriately.

<?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" access="nosetter.camelcase-underscore" >
      <key column="OwnerId" />
      <one-to-many class="Pet" />
    </set>      
  </class>
</hibernate-mapping>

 

What’s changed? I added the access attributes to the mappings.

For HasPets I set it to readonly. That way NHibernate will read the property and use its value when doing INSERTs and UPDATEs, but will not include it in SELECTs.

For Pets one-to-many mapping I used value that tells NHibernate to use the getter to read the value of the property, and to write to the field directly, and that field name is _pets (by convention). There are quite a few more options, and you can use them to do some pretty powerful things, like enforcing consistency in two-way *-to-many mappings.

 

* the access=”readonly” is new to NHibernate v2.1

DNK Tags:

Comments

Ben Hyrman says:

Technically, you don’t need your entity properties to be virtual if you specify default-lazy="false" at the top-mapping or specify lazy="false" in the class mapping. However, then you’ll want to make sure that, when you don’t want eager fetching for collections you specify lazy="true" on the many-one and mark that as virtual.

Also, you don’t always need to override hashcode and equals. It’s a good practice to, since that’s where you can enforce domain equality and not just object equality, but otherwise the main time it’s required is when you’re using a composite id (it’ll use the id column for equality checking otherwise). You also need to override if you’re using a set but you don’t if you’re using a bag (set ensures that only one of something can be associated so you want to make sure it knows when two objects are the same thing)