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
