Reputation: 730
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
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
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
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
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