Aug
22
2009
Bidirectional One-to-many with Orphan Delete
Here’s the full write-up on my issue from part 5. Since we’re saying all orphan children should be deleted, it seems logical that our DB schema won’t allow orphaned children, meaning Child.Parent_id should have a NOT NULL constraint. However, when I add this constraint and try to do a cascading orphan delete, NHibernate yells at me because I’ve set the Child.Parent to null / nothing.
If I remove this constraint (not good DB practice in my opinion), we see that the orphan is deleted without ever updating Child.Parent_id to NULL, so it won’t violate any database constraints, as far as I can tell.
Here’s the code in VB:
Imports FluentNHibernate.Mapping Imports NUnit.Framework Public Class Parent Inherits Entity Private m_Children As ICollection(Of Child) = New HashSet(Of Child) Public Overridable Property Children() As ICollection(Of Child) Get Return m_Children End Get Protected Set(ByVal value As ICollection(Of Child)) m_Children = value End Set End Property End Class Public Class Child Inherits Entity Private m_Parent As Parent Public Overridable Property Parent() As Parent Get Return m_Parent End Get Set(ByVal value As Parent) m_Parent = value End Set End Property End Class Public Class ParentMapping Inherits ClassMap(Of Parent) Public Sub New() Id(Function(x As Parent) x.ID).GeneratedBy.GuidComb() HasMany(Function(x As Parent) x.Children) _ .AsSet() _ .WithForeignKeyConstraintName("ParentChildren") _ .Cascade.AllDeleteOrphan() _ .Inverse() End Sub End Class Public Class ChildMapping Inherits ClassMap(Of Child) Public Sub New() Id(Function(x As Child) x.ID).GeneratedBy.GuidComb() References(Function(x As Child) x.Parent) _ .Cascade.All() _ .WithForeignKey("ChildParent") _ .Not.Nullable() End Sub End Class <TestFixture()> _ Public Class ParentMappingTests Inherits BaseFixture <Test()> _ Public Sub CanCascadeSaveFromParentToChild() Dim ID As Guid Dim P As Parent Dim C As Child Using Scope As New SQLiteDatabaseScope(Of ParentMapping) Using Session = Scope.OpenSession Using Tran = Session.BeginTransaction P = New Parent 'Add a child of the parent C = New Child With {.Parent = P} P.Children.Add(C) ID = Session.Save(P) Tran.Commit() End Using Session.Clear() Using Tran = Session.BeginTransaction P = Session.Get(Of Parent)(ID) Assert.IsNotNull(P) Assert.AreEqual(ID, P.ID) Assert.AreEqual(1, P.Children.Count) Assert.AreNotSame(C, P.Children(0)) Assert.AreEqual(C, P.Children(0)) Assert.AreSame(P.Children(0).Parent, P) Tran.Commit() End Using End Using End Using End Sub <Test()> _ Public Sub CanDeleteOrphanFromParentToChildren() Dim ID As Guid Dim P As Parent Dim C As Child Using Scope As New SQLiteDatabaseScope(Of ParentMapping) Using Session = Scope.OpenSession Using Tran = Session.BeginTransaction P = New Parent 'Add a child of the parent C = New Child With {.Parent = P} P.Children.Add(C) ID = Session.Save(P) Tran.Commit() End Using Session.Clear() Using Tran = Session.BeginTransaction P = Session.Get(Of Parent)(ID) Assert.IsNotNull(P) Assert.AreEqual(ID, P.ID) Assert.AreEqual(1, P.Children.Count) Assert.AreNotSame(C, P.Children(0)) Assert.AreEqual(C, P.Children(0)) Assert.AreSame(P.Children(0).Parent, P) Tran.Commit() End Using Session.Clear() 'Orphan the child C = P.Children(0) P.Children.Remove(C) C.Parent = Nothing Using Tran = Session.BeginTransaction 'Orhpaned child should be deleted Session.SaveOrUpdate(P) Tran.Commit() End Using Session.Clear() Using Tran = Session.BeginTransaction P = Session.Get(Of Parent)(ID) Assert.AreEqual(0, P.Children.Count) Tran.Commit() End Using End Using End Using End Sub End Class
Here’s the code in C#:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using NHibernate; using FluentNHibernate.Mapping; using NUnit.Framework; namespace NStackExample.Data.Tests { public class Parent : Entity { private ICollection<Child> m_Children = new HashSet<Child>(); public virtual ICollection<Child> Children { get { return m_Children; } protected set { m_Children = value; } } } public class Child : Entity { private Parent m_Parent; public virtual Parent Parent { get { return m_Parent; } set { m_Parent = value; } } } public class ParentMapping : ClassMap<Parent> { public ParentMapping() { Id((Parent x) => x.ID).GeneratedBy.GuidComb(); HasMany((Parent x) => x.Children) .AsSet() .WithForeignKeyConstraintName("ParentChildren") .Cascade.AllDeleteOrphan() .Inverse(); } } public class ChildMapping : ClassMap<Child> { public ChildMapping() { Id((Child x) => x.ID).GeneratedBy.GuidComb(); References((Child x) => x.Parent) .Cascade.All() .WithForeignKey("ChildParent") .Not.Nullable(); } } [TestFixture()] public class ParentMappingTests { [Test()] public void CanCascadeSaveFromParentToChild() { Guid ID; Parent P; Child C; using (SQLiteDatabaseScope<ParentMapping> Scope = new SQLiteDatabaseScope<ParentMapping>()) { using (ISession Session = Scope.OpenSession()) { using (ITransaction Tran = Session.BeginTransaction()) { P = new Parent(); //Add a child of the parent C = new Child { Parent = P }; P.Children.Add(C); ID = (Guid) Session.Save(P); Tran.Commit(); } Session.Clear(); using (ITransaction Tran = Session.BeginTransaction()) { P = Session.Get<Parent>(ID); Assert.IsNotNull(P); Assert.AreEqual(ID, P.ID); Assert.AreEqual(1, P.Children.Count); Assert.AreNotSame(C, P.Children.First()); Assert.AreEqual(C.ID , P.Children.First().ID ); Assert.AreSame(P.Children.First().Parent, P); Tran.Commit(); } } } } [Test()] public void CanDeleteOrphanFromParentToChildren() { Guid ID; Parent P; Child C; using (SQLiteDatabaseScope<ParentMapping> Scope = new SQLiteDatabaseScope<ParentMapping>()) { using (ISession Session = Scope.OpenSession()) { using (ITransaction Tran = Session.BeginTransaction()) { P = new Parent(); //Add a child of the parent C = new Child { Parent = P }; P.Children.Add(C); ID = (Guid) Session.Save(P); Tran.Commit(); } Session.Clear(); using (ITransaction Tran = Session.BeginTransaction()) { P = Session.Get<Parent>(ID); Assert.IsNotNull(P); Assert.AreEqual(ID, P.ID); Assert.AreEqual(1, P.Children.Count); Assert.AreNotSame(C, P.Children.First()); Assert.AreEqual(C.ID, P.Children.First().ID ); Assert.AreSame(P.Children.First().Parent, P); Tran.Commit(); } Session.Clear(); //Orphan the child C = P.Children.First(); P.Children.Remove(C); C.Parent = null; using (ITransaction Tran = Session.BeginTransaction()) { //Orhpaned child should be deleted Session.SaveOrUpdate(P); Tran.Commit(); } Session.Clear(); using (ITransaction Tran = Session.BeginTransaction()) { P = Session.Get<Parent>(ID); Assert.AreEqual(0, P.Children.Count); Tran.Commit(); } } } } } }
blog comments powered by Disqus