NHibernate Collections: Fighting the Good Fight and Losing

Thursday, June 11 2009 -

I recently had a problem where I needed to represent a collection of Folders, where each Folder could have items or additional folders. I needed to store this representation in our database, however I also needed to be able to easily manipulate individual items within the collection so storing everything as a single entity (say XML) was out of the question.

I created a set of Classes: Folder.cs and FolderItem.cs

I created the below mapping documents :

Folder

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"

       namespace="Domain.Core"

                       default-access="field.camelcase-underscore"

    >

  <class name=" Domain.Core.Folder, Domain" table="folders"  lazy="false" >

    <id name="FolderID" column="ID" type="Int32" unsaved-value="0">

      <generator class="native" />

    </id>

    <property name="ParentID" column="parentID" type="Int32" />

    <property name="FolderName" column="name"   type="string"  />

    <property name="UserGuid" column="userGuid"   type="Guid" />

    <property name="SortID" column ="sortID" type="Int32" />

    <set name="_folderItem" access="field" lazy="false"  table="ItemsInFolder”  inverse="false"  cascade="save-update" >

      <key column="folderID" />

      <one-to-many class="Domain.Core.FolderItem, Domain" />

    </set>

  </class>

</hibernate-mapping>

FolderItem

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"

    default-access="field.camelcase-underscore"

    namespace="Domain.Core"

    >

  <class name="Domain.Core.FoldersItem,  Domain" table="FolderItem" lazy="false"  >

    <id name="ID" column="ID" type="Int32" unsaved-value="0">

      <generator class="native" />

    </id>

    <property name="SortID" column ="sortID" type="Int32" />

    <many-to-one name="Folder" class=" Domain.Core.Folder, Domain" column="folderId"/>

    <many-to-one name="Detail"

                 class="Domain.Core.Detail, Domain"

                 unique="true" 

                 column="detailID" 

                 cascade="none"/>

   </class>

</hibernate-mapping>

 

Essentially I’ve got a Unit of Work pattern setup where the Session is created via an HTTPModule at Begin_Request and closed at End_Request.

Loading the collections worked as expected, but saving changes was another story.

Since the data itself was represented as XML I had to convert on the fly from XML back to the collection. The sticky part is that the XML hierarchy could be changed at will by the end user (e.g. Drag and Drop) and the returned XML could not be relied upon to have the correct Parent->Child IDs. That meant I had to rebuild the relationship by comparing the current collection to the XML and updating the current accordingly.

A folder could be in one folder collection and have to move to another folder collection due to end user changes.

In addition to moving folder to folder, each item had to retain its current sort position. I ran into an interesting issue where, when I mapped a Sort column, there were scenarios where I knew I had N number of items in my collection, but N-1 would come out of NHibernate. The problem was that when adding a new item to the DB I would set the sortID to –1. NHibernate considered these items with duplicate sortID values to be identical and only returned one of them. I of course, missed that small tidbit in the NHibernate docs, and spent a good hour trying to figure out why occasionally things stopped working as expected.

Back to Folders…

When I found a Folder that didn’t have the correct Parent (e.g. it was moved) I updated the Folder in my Folder collection to have the correct parentID (and any other changes) and then updated the other side of the collection, the FolderItem collection so that it was also pointed at the correct record. Check out collections in the NHibernate docs if this doesn’t make sense. Essentially the <one-to-many class="Domain.Core.FolderItem, Domain" /> and the  <many-to-one name="Folder" class=" Domain.Core.Folder, Domain" column="folderId"/> need to be kept in sync. That worked great for one change, but if I had multiple only the 1st would ever persist. NHibernate would insist that no other changes took place.

Frustrated, I tried actually removing the changed item from the collection and forcing a commit(), then adding it back into the collection(s). This still only worked for one item. I suspect that had I created a new ISession, forced a commit() and then tried the update on a second ISession this strategy might have worked, but the potential for major performance issues with that approach ruled it out. After spending what seemed like 3 – 4x the amount of time it would have taken me to roll my own persistence layer I gave up and flattened my hierarchy. My DB structure is now not in 3rd normal form, however I gain far fewer reads and writes and my collection manipulation logic got much easier to deal with.

Now I only have a folder.cs class and a folder.cs mapping that looks something like this:

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"

       namespace="Domain.Core"

                       default-access="field.camelcase-underscore"

    >

  <class name=" Domain.Core.Folder, Domain" table="folders"  lazy="false" >

    <id name="FolderID" column="ID" type="Int32" unsaved-value="0">

      <generator class="native" />

    </id>

    <property name="ParentID" column="parentID" type="Int32" />

    <property name="FolderName" column="name"   type="string"  />

    <property name="UserGuid" column="userGuid"   type="Guid" />

    <property name="SortID" column ="sortID" type="Int32" />

    <many-to-one name="Detail"

                 class="Domain.Core.Detail, Domain"

                 unique="true" 

                 column="detailID" 

                 cascade="none"/>

   </class>

  </class>

</hibernate-mapping>

I constantly see value in NHIbernate, but I continue to struggle to achieve the productivity gains I know are there. Karl over at CodeBetter.com echoes some of my frustrations with NHibenate here. Like he said, NHibernate is solving a seriously complex problem and as such, it is a complex beast. Sometimes, getting things done, out-weighs getting things done the “correct” way. When that happens NHibernate gives you the tools to circumvent its more complex pieces and just do what you need.