views:

53

answers:

4

Hi,

I'm trying to figure what's the correct way to map the following parent child relationship. I have a parent class which contains child objects. However, the parent also has a pointer to an instance of one of the children (the PrimaryChild)

Class Parent

  Public Property Id As Integer?

  Public Property PrimaryChild As Child

  Public Property Children As IList(Of Child)

End Class

Public Class Child

    Public Property Id As Integer?

    Public MyParent As Parent

End Class

Usage is something like

Dim ch As New Child
Dim par as New Parent

ch.MyParent = par
par.Children.Add(ch)

par.PrimaryChild = ch

Session.SaveOrUpdate(par)

However, when I do this, the PrimaryChild is shown as being a null or transient value. I have set cascade="all" on the Children collection.

Any ideas what I'm doing wrong?

Update 1

Added Mappings

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Parent" table="Parents">
    <id name="Id" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="ID" />
    </id>

    <set access="nosetter.camelcase-underscore" cascade="all-delete-orphan" inverse="true" name="Children" mutable="true">
      <key>
        <column name="ParentID" />
      </key>
      <one-to-many class="Child" />
    </set>
    <many-to-one cascade="save-update" class="Child" name="PrimaryChild">
      <column name="PrimaryChildID" not-null="true" />
    </many-to-one>
    <many-to-one cascade="save-update" class="Child" name="SecondaryChild">
      <column name="SecondaryChildID" not-null="true" />
    </many-to-one>
  </class>
</hibernate-mapping>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Child" table="Child">
    <id name="Id" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="ID" />
    </id>
    <many-to-one class="Parent" name="Parent">
      <column name="ParentID" not-null="true" />
    </many-to-one>
  </class>
</hibernate-mapping>
A: 

What mappings do you have so far?

PrimaryChild is a one-to-one, Children is a one-to-many but you could manage the relationship in many different ways. Where are your Foreign Keys?

if you put the FK on the Child, then you want both one-to-one and one-to-many mappings with inverse=true set on both.

As an aside, this:

ch.MyParent = par
par.Children.Add(ch)

is a massive OO encapsulation fail. Parent shouldn't expose an IList, as other objects can manipulate it. The parent class should control all manipulations of itself. Make it an IEnumerable and use an AddChild method that does the above two lines.

Andrew Bullock
Yes, I'm already using IEnumerable, just wanted to simplify for this question. Thanks for pointing it out anyway.I'll update my original post with the mappings
James
A: 

I've performed this task with tools like the NHibernate LINQ library.

public class Parent {
  public Parent() {
    Children = new List<Child>();
  }
  public virtual IList<Child> Children { get; set; }
  public virtual Child PrimaryChild {
      get {
        return Children.FirstOrDefault(x => x.IsPrimary);
    }
  }
}

If you've already loaded the children, then PrimaryChild is an in-memory operation. If you request PrimaryChild first, then this will be it own database fetch operation. Works nicely, IMO.

And you should look at Andrew Bullock's response. Exposing an IList opens the door to bad domain design. You should only expose the enumeration.

Jarrett Meyer
@James Jarrett makes a useful point here, but this is only one side of a possible optimisation. It depends how you are going to be accessing your object graph. This would be best if you've eager loaded, otherwise you could create a `Select N+1` problem
Andrew Bullock
Thanks for the reply. I don't think I can use this method because there are actually 2 properties (PrimaryChild, SecondaryChild) which may point to the same child instance so I can't make use of something like IsPrimary
James
A: 

If your relation is bidirectional (IE parent references children, and child references parent), and if the foreign key is on Child, you need to set the attribute

inverse="true"

in the declaration of the collection. Otherwise cascade won't work nicely :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="Parent">
    <id name="Id" />

    <bag name="Children" cascade="all" inverse="true">
      <key column="ID_PARENT" />
      <one-to-many class="Child"/>
    </bag>
  </class>

  <class name="Children">
    <id name="Id" />

    <many-to-one name="Parent" column="ID_PARENT" class="Parent" not-null="true" />
  </class>
</hibernate-mapping>
mathieu
A: 

Your tables are looking like this:

Table Parent
(
  ...
  PrimaryChild_FK NOT NULL
)

Table Child
(
  ...
  Paren_FK NOT NULL
)

Can you tell me in which order the data should be inserted? You can neither insert Parent nor Child, since both need the other to set the foreign key. (NHibernate inserts one of them and set the FK to null, to update it later. But the database complains.)

Remove the not null constraint from the set. NHibernate is not smart enough to find a working insert order if you just remove one of them. (AFAIK, not null constraints in the mapping files are actually only used to create the database schema from).

And as already mentioned by mathieu, make the set inverse and use the same foreign key for the child-parent and the parent-children relations.

Stefan Steinegger
Hi Stefan, thanks for responding.Parent should be inserted first, then all children, then parent should be updated to set the PrimaryChild_FK column to the identity of the correct child record.To make this work does PrimaryChild_FK have to be NULL? In which case what should the Cascade setting be for the PrimaryChild?
James
So remove the not-null constraint from the primaryChild_FK. You may also need to remove some more not-null constraints. As I said: NH is not smart enough to change the order of inserts based on not-null constraints. I would set the PrimaryChild to cascade all.
Stefan Steinegger