James
James

Reputation: 730

NHibernate mapping child collection and embedded instance of child

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>

Upvotes: 1

Views: 708

Answers (4)

Stefan Steinegger
Stefan Steinegger

Reputation: 64628

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.

Upvotes: 1

mathieu
mathieu

Reputation: 31202

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>

Upvotes: 0

Andrew Bullock
Andrew Bullock

Reputation: 37378

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.

Upvotes: 0

Jarrett Meyer
Jarrett Meyer

Reputation: 19573

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.

Upvotes: 0

Related Questions