views:

217

answers:

2

I'm using nHibernate to map an object very similar to .NET's System.Web.SiteMapNode. In order to keep my object similar to this .NET object I would like to have it contain a ParentNode, PreviousSibling, NextSibling, and ChildNodes complex properties.

The table looks somewhat like this and is open to be changed:

  • ID (int)
  • Title (string)
  • Description (string)
  • Key (string)
  • ParentNodeId (int)
  • OrdinalPosition (int)
  • ReadOnly (bool)
  • Url (string)

I may have some other properties that are not needed to mimic the .NET SiteMapNode object (like an isExternal bool), but I think those are inconsequential to this question.

My current mapping looks like this:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="AthletesCafe.Core.Domain.System.SiteMap" assembly="AthletesCafe.Core">
<class name="SiteMapNode" table="SiteMapNode" lazy="true" >

<id name="ID" type="Int32" unsaved-value="0">
  <column name="ID" not-null="true" unique="true" index="PK_SiteMapNode"/>
  <generator class="identity" />
</id>

<property name="Title" column="Title" type="String" length="255" not-null="true" />
<property name="Description" column="Description" type="String" not-null="false" />

<property name="Url" column="Description" type="String" not-null="true"  />

<property name="SiteMapKey" column="SiteMapKey" type="String" not-null="true" length="255"  />

<property name="OrdinalPosition" column="OrdinalPosition" type="Int32" not-null="true" />

<property name="ReadOnly" column="ReadOnly" not-null="true" type="System.Boolean" />

<property name="IsExternal" column="IsExternal" not-null="true" type="System.Boolean" />

<many-to-one name="ParentNode" column="ParentNodeId" class="AthletesCafe.Core.Domain.System.SiteMap.SiteMapNode, AthletesCafe.Core" 
             access="field.pascalcase-underscore" not-null="false" />
<many-to-one name="PreviousNode" column="ParentNodeId" class="EatMyTrainer.Core.Domain.SiteMap.SiteMapNode, EatMyTrainer.Core" not-null="false" /></hibernate-mapping>

The ParentNode mapping is easy as it should be just a simple many-to-one mapping. This is the code I have for it (untested, but I believe it to be correct):

<many-to-one name="ParentNode" column="ParentNodeId" class="AthletesCafe.Core.Domain.System.SiteMap.SiteMapNode, AthletesCafe.Core" 
             access="field.pascalcase-underscore" not-null="false" />

The mapping for the child nodes should just be a simple bag which will bring back all SiteMapNode objects that have the ParentNodeId equal to the current ID. I haven't written this bag yet, but I believe it to be not such a big deal.

The issue that I cannot seem to resolve is how to do the Next/Previous Sibling properties. This objects can be derived from the following formula for each node:

  • PreviousSibling: Has the same ParentNode (ParentNodeId) as the current object and its OrdinalPosition should be one less than the current object's OrdinalPosition.
  • NextSibling: Has the same ParentNode (ParentNodeId) as the current object and its OrdinalPosition should be one more than the current object's OrdinalPosition.

I think this is achievable through the formual attribute on a many-to-one mapping. Is this possible? I haven't found a good example of how this works.

A: 

I don't think what you're asking for is strictly possible (although I would be very interested to see the solution if it is). There would be a relatively simple workaround, but NHibernate does not support bidirectional one-to-many mappings with indexed collections on the many end.

The only thing that comes to mind is a bit ugly: have the parent object keep its own index map (keyed off the OrdinalPosition) to each child object. On the child do something like:

public SiteMapNode NextSibling()
{
    return this.Parent.NextSibling(this);
}
Stuart Childs
A: 

I believe Stuart is correct in this situation. It is impossible to do for the many-to-one mapping. If NHibernate provided a way to do where clausing on this mapping then I may have a chance.

Another possible solution although inefficient is to create a bag that uses a field setter. The public property that would be Next/Previous setting would still return an object reference (as opposed to an enumerable). In the getter it would just reference the first position of the enumerable in the field. Lazy loading would be ideal because NHibernate wouldn't be able to load this object in one get with the initial load of the object. You would have a penalty for accessing this object every time.

I guess both solutions have a similar penalty.

Mike G