tags:

views:

494

answers:

3

Is there any way to get NHibernate to let me store multiple ChildObjects in the ChildObjectTable but refer them back to different ParentObjects? Or do I have to create a separate ChildObject class/table for each ParentObject type?

I've boiled this down to the following, I'm trying to map these objects:

public class ParentObjectA
{
    public virtual Int64 Id { get; private set; }

    public virtual IDictionary<int, ChildObject> Children { get; private set; }
}

public class ParentObjectB
{
    public virtual Int64 Id { get; private set; }

    public virtual IDictionary<int, ChildObject> Children { get; private set; }
}

public class ChildObject
{
    public virtual Int64 Id { get; private set; }   
}

into the following table structure:

ParentObjectTableA
  Id bigint

ParentObjectTableB
  Id bigint

ChildObjectTable
  Id bigint
  ParentId bigint
  ParentQualifier varchar(50)
A: 

Perhaps you could create an abstract base class for your parent classes? That way, a given child object could refer back to the abstract type for its parent.

For example, you can have a class called ParentBase, which ParentClassA and ParentClassB extend. Your child object will have a reference back to ParentBase.

This is all possible with NHibernate's various inheritance models.

Ryan Duffield
That's true. I'm actually using this technique elsewhere where but didn't even think to apply it here because I was trying to avoid a common base class. Will this work with interfaces as well?
Chris Stavropoulos
I am unsure as to how NHibernate deals with interfaces in a mapping scenario.
Ryan Duffield
A: 

The following maps seem to work from an object perspective. I'm not overly keen on the idea of using crosstables, but I think this might be cleaner than trying to deal with inheritance. I'm going to take a look at that option as well and I'll post my findings here for the sake of completion. Comments welcome.

Mapping of ParentObjectA

<map name="Children" table="ParentAxChildObject">
    <key column="ParentId" />
    <index column="ChildObjectNumber" type="Int32" />
    <many-to-many class="ChildObject" />
</map>

Mapping of ParentObjectB

<map name="Children" table="ParentBxChildObject">
    <key column="ParentId" />
    <index column="ChildObjectNumber" type="Int32" />
    <many-to-many class="ChildObject" />
</map>

These generate the following cross tables:

ParentAxChildObject
    [ParentId] [bigint] NOT NULL,
    [elt] [bigint] NOT NULL,
    [ChildObjectNumber] [int] NOT NULL,

ParentBxChildObject
    [ParentId] [bigint] NOT NULL,
    [elt] [bigint] NOT NULL,
    [ChildObjectNumber] [int] NOT NULL,
Chris Stavropoulos
It should be noted that the table attribute in the map does not appear to be documented in the NHibernate documentation. I guessed at it after discovering that it was trying to build a crosstable out of the 'name' attribute by default.
Chris Stavropoulos
That creates one consolidated `Children` collection? Neat. Very interesting approach that I haven't seen before.
Ryan Duffield
A: 

I've got the inheritance version working as well with an interface as the base and wanted to post it here for completion.

public interface IParent
{
    IList<Child> children { get; set; }
    void AddChild(Child child);
    Int64 Id { get; set; }
    Child GetChild();
}

public class Parent : IParent
{
    public virtual IList<Child> children { get; set; }

    public Parent()
    {
        children = new List<Child>();
    }

    public virtual void AddChild(Child child)
    {
        children.Add( new Child() );
    }

    public virtual Int64 Id { get; set; }

    public virtual Child GetChild()
    {
        return children.First();
    }
}

public class Parent2 : IParent
{
    public virtual IList<Child> children { get; set; }

    public Parent2()
    {
        children = new List<Child>();
    }

    public virtual void AddChild(Child child)
    {
        children.Add(new Child());
    }

    public virtual Int64 Id { get; set; }

    public virtual Child GetChild()
    {
        return children.First();
    }
}


public class Child
{
    public virtual Int64 Id { get; private set; }
}

These are mapped with the following:

<class name="IParent" table="IParents">
 <id name="Id" unsaved-value="0">
  <column name="Id" sql-type="bigint" />
  <generator class="hilo" />
 </id>

 <bag name="children" cascade="all">
  <key column="ParentId" />
  <one-to-many class="Child" />
 </bag>

 <joined-subclass name="Parent" table="Parents" >
  <key column="ParentId" />
 </joined-subclass>

 <joined-subclass name="Parent2" table="Parents2" >
  <key column="ParentId" />
 </joined-subclass>
</class>

<class name="Child" table="Children">
 <id name="Id" unsaved-value="0">
  <column name="Id" sql-type="bigint" />
  <generator class="hilo" />
 </id>
</class>

This in turn creates the following tables:

IParents
    Id bigint

Parents
    ParentId bigint

Parents2
    ParentId bigint

Children
    Id bigint
    ParentId bigint

The big 'gotcha' to be aware of here is that the Child object refers directly to the Id in the IParents table only. And each instance of the Parent or Parent2 object is bound to a joined call between IParents and either Parents or Parents2 depending on what derived object type you're actually working with.

I haven't tested this with a class with multiple interfaces yet but I need to test that as well.

The thing I don't like about the inheritance model is that I need to have a common interface/base class. I'm not sure why I'm opposed to that offhand, it just seems clunky.

I think I'm going to roll with the crosstable method for now and revisit this later if I have to.

Chris Stavropoulos