views:

39

answers:

2

We have a fairly big DB (~200 tables) which almost entirely uses composite primary keys and composite foreign keys, using a single "base table" from which every other table inherits part of its primary key:

  • Parent has single column primary key ParentId
  • Child has composite primary key (ParentId, ChildId) and foreign key ParentId
  • Nephew has composite primary key (ParentId, NephewId), foreign key ParentId and foreign key (ParentId, ChildId)

and so on. Up until now we managed this whole shebang with an ORM framework of our own, but we're considering using NHibernate, which I've been assigned to learn (i've downloaded v2.1.2).

The mappings: Child

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Assembly" namespace="Namespace">
<class name="Child" table="Child">
    <composite-id name="IdChild" class="ChildId">
        <key-many-to-one name="Parent" column="ParentId" class="ParentId"></key-many-to-one>
        <key-property name="Id" column="ChildId" type="Int32"></key-property>
    </composite-id>
    <!--simple properties-->
    <set name="Nephews" table="Nephew">
        <key>
            <column name="ParentId"></column>
            <column name="ChildId"></column>
        </key>
        <one-to-many class="Nephew"/>
    </set>
</class>
</hibernate-mapping> 

Nephew

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Assembly" namespace="Namespace">
    <class name="Nephew" table="Nephew">
        <composite-id name="IdNephew" class="NephewId">
            <key-many-to-one name="Parent" column="ParentId" class="Parent"></key-many-to-one>
            <key-property name="Id" column="NephewId" type="Int32"></key-property>
        </composite-id>
        <many-to-one name="Child" class="Child">
            <column name="ParentId"></column>
            <column name="ChildId"></column>
        </many-to-one>
        <!--simple properties-->
    </class>

I can post the classes too if you want, I'll omit them now for brevity (as I'll omit the mapping for Parent, since it doesn't have problems). Every property is virtual, every mapping file is an embedded resource, every composite Id has its own class which overrides Equals and GetHashCode.

The problem is I can't save an instance of Nephew, initialized through a simple new Nephew() and passed on to _session.Save(), because I get a System.IndexOutOfRangeException: Invalid index n for this SqlParameterCollection with Count=n..

The only column which is duplicated in the mapping is the ParentId. Removing the many-to-one mapping in the Nephew, the set mapping in the Child and all related properties everything works fine.

I found several posts reporting this exception, and the most appropriate in my case seems to be this one, which gives me the gut feeling that my current schema is infeasible with NHibernate. Please tell me I'm wrong :-)

NOTES:

  • I'm not using Fluent right now, even though it may be an option, but I preferred learning the basics first;
  • Yes, we realize composite primary keys are a big pain in the ass. This DB has passed through several hands through the years, probably not so skilled ones, but before refactoring it we will count to 10 000
A: 

Unfortunately, you won't be able to map that relationship in its current form, as you have inferred.

However, there's a workaround. Instead of mapping Nephew.Child as a many-to-one, map ChildId as a regular property, and use a query when you need to retrieve the Child.

There's one more chance for a hack. If and only if Nephew.Child is not null and not mutable, you could map the key referencing the Child instead of the parent:

<class name="Nephew" table="Nephew">
  <composite-id name="IdNephew" class="NephewId">
    <key-many-to-one name="Child">
      <column="ParentId">
      <column="ChildId">
    </key-many-to-one>
    <key-property name="Id" column="NephewId"/>
  </composite-id>
</class>
Diego Mijelshon
Thank you. Your first suggestion partly works, but I lose the ability to cascade save the nephews by only calling _session.Save(child). I have to manually create a Child.Add(Nephew) method that adds the nephew to the Nephews collection and sets the simple property ChildId in the Nephew, even with inverse="true" and cascade="all" in the <set> tag. It still is more than nothing
Piddu
If the answer was helpful, please accept it.
Diego Mijelshon
Sorry, I'm still learning to use this site as a "student"
Piddu
That's fine. The FAQ is your friend :-) http://stackoverflow.com/faq
Diego Mijelshon
A: 

I found a better solution:

<many-to-one name="Child" class="Child">
    <formula>ParentId</formula>
    <column name="ChildId"></column>
</many-to-one>

I had already tried that and it gave me an error, but then I noticed this patch, so I downloaded the 3.0.0 alpha2 and it all worked correctly! Thanks to this solution I can map the Nephew.Child property as it was meant to be.

I still need the Child.Add(Nephew) method, though (but I realized that is recommended even in the documentation)

Piddu