I'm sure that other developers implement something similar during their delete process, but I haven't run across anything formally defined. This keeps coming up, and after helping several FluentNHibernate & NHibernate users out by giving them direction I decided to do a writeup. I'm going to try and state the pattern on here for your reference and feedback. Disclaimer: I'm not saying it's perfect, or that there's not something better, I'm just saying it works for what we need, and we haven't found anything better yet.
Intent:
Called before an NHibernate Session.Delete(). Disassociates the entity from the domain model, removing all references to the entity, thereby allowing NHibernate to generate the appropriate UPDATE/DELETE statements in the database, in the order required, so there's no Foreign Key Violations. This pattern works with inheritance.
Implementation:
1: public interface IDereferenceable
2: {3: void Dereference();
4: } 5: 6: public abstract class PersistedBase : IDereferenceable
7: {8: protected bool IsDereferencing;
9: 10: public void Dereference()
11: { 12: Dereference(IsDereferencing); 13: }14: protected virtual void Dereference(bool dereferencing)
15: {16: if (dereferencing)
17: { return; }
18: IsDereferencing = true;
19: 20: //Dereference Children
21: foreach(Child child in Children.ToList())
22: { 23: RemoveChildCore(child); 24: } 25: 26: //Dereference Parents
27: if(parent != null)
28: {29: parent.RemoveChild(this);
30: parent = null;
31: } 32: } 33: 34: private ICollection<Child> children;
35: public IEnumerable<Child> Children{ get { return children; } }
36: public void RemoveChild(Child child)
37: {38: if(!IsDereferencing)
39: { 40: RemoveChildCore(child); 41: } 42: } 43: 44: protected void RemoveChildCore(Child child)
45: { 46: children.Remove(child);47: IDereferenceable deref = child as IDereferenceable;
48: if(deref ! = null)
49: { 50: deref.Dereference(); 51: } 52: } 53: } 54: 55: public class Concrete : PersistedBase
56: {57: protected override void Dereference(bool dereferencing)
58: {59: if (dereferencing)
60: { return; }
61: IsDereferencing = true;
62: 63: //Dereference Children
64: foreach(StepChild stepChild in StepChildren.ToList())
65: { 66: RemoveStepChildCore(stepChild); 67: } 68: 69: //Dereference Parents
70: if(stepParent != null)
71: { 72: stepParent.RemoveChild(this);
73: stepParent = null;
74: } 75: 76: base.Dereference(false);
77: } 78: 79: private ICollection<StepChild> stepChildren;
80: public IEnumerable<StepChild> StepChildren{ get { return stepChildren; } }
81: public void RemoveStepChild(StepChild stepChild)
82: {83: if(!IsDereferencing)
84: { 85: RemoveStepChildCore(stepChild); 86: } 87: } 88: 89: protected void RemoveStepChildCore(StepChild stepChild)
90: { 91: stepChildren.Remove(stepChild);92: IDereferenceable deref = stepChild as IDereferenceable;
93: if(deref ! = null)
94: { 95: deref.Dereference(); 96: } 97: } 98: }Let's go down the list:
- Our abstract class/interface. This is the base class that handles basic dereference association and implements the IDereferenceable interface.
- IsDereferencing - This is similar to the IsDisposing/Disposed flag on IDisposable objects. It's a way to ensure that we only Dereference an object once. This is only set within the protected Dereference method if it's passed in a !dereferencing parameter.
- Dereference() - Public no-parameter. This is what is called by by the code that's about to delete the object. It's also called by other classes whenever they want to dereference this instance as a child reference. Passing in the current IsDereferencing flag prevents us from getting into a cyclic call if it's called several times by many parent classes.
- Dereference(bool dereferencing) A.) We early escape if our dereferencing parameter is true, so we don't perform any dereference logic more than once B.) Dereference children first. Children are typically going to be defined as collections, but there are always exceptions. C.) We enumerate over a copy of the collection. Reason? We're going to get a collection cannot be modified during enumeration exception otherwise. D.) We call the private RemoveChildCore(child), which does not do a dereferencing check (otherwise nothing would happen) E.) We check first to see if Parent is null, this is primarily here for situations where a Parent is allowed to be null, or during testing where you may not popular all the parents of an entity F.) If parent is not null, then we call the public RemoveChild(this) method on the parent so we're no longer referenced in a parent collection. Important to note, that RemoveChild() on the parent does perform a !IsDereferencing check on the parent, just like we have for the child. After all, the Child's Dereference() could have been called by the Parent's Dereference(), so we want to respect this. G.) We null out the parent
- Our collection is exposed out as a read only collection, which tells the user that we do not allow adding/removing directly on the collection. Good practice and safety precaution if you have additional association logic, which you would put into the AddChild(Child child) method (not implemented here for the sake of simplifying the example).
- The public RemoveChild(Child child) checks if we're dereferencing, if we are, then we skip actually removing the child. This is there so that when children are being dereferenced, and call parent.RemoveChild(this), they do not enter a cyclic loop. If not dereferencing, call Core dereference.
- RemoveChildCore(Child child) has no checks, and is private, so it should be tightly controlled by the class. This will remove the item from the private collection, cast the child as a dereferenceable, and then if the child is dereferenceable, it will proceed to call Dereference on it. NOTE: that child.Dereference() should only be called if the child dies with the parent, so if the parent is dereferenced, the child should be as well. in some cases, this is not the case, and there might only be an incidental association between them, such as is the case with a ManyToMany() association. In this case, dereferencing the child should be avoided (perhaps though you need to keep the two lists synchronized in the MTM example, so you'd call child.RemoveParents(this) in place of the child.Dereference() )
- The Concrete implementation of the base class. Here we have a similar setup as the base class, except we've got additional information that needs to be dereferenced (another collection and parent, defined as StepChildren and StepParent)
- Dereference(bool dereferencing). Our setup here is the same, and can be referenced from 4 A-G. However, it adds a new element at the end: H.) We call our base.Dereference(false) to continue up the dereference stack. Passing it false is important, since we know we're in the middle of dereferencing and should override the dereferencing check. If we passed the IsDereferencing field, the base Dereference would not fire, meaning that any references specific to the base class would still hold a reference to our class. As we're overriding, polymorphism dictates that our bottom level class (most concrete) will have Dereference called on it first. So it Dereferences from most specific to most generic (Covarience).
Usage:
1: using(var session = sessionFactory.OpenSession())
2: using(var transaction = session.BeginTransaction())
3: { 4: var concrete = session.Get(id); 5: concrete.Dereference(); 6: session.Delete(concrete); 7: transaction.CommittTransaction(); 8: }So there you go, a way to manage the disassociations with NHibernate (or another ORM) and your object model. Hopefully this helps someone out, and if it doesn't, feel free to leave a comment if you've got any critiques or questions. Always open to feedback! (Also wrote all of the code in notepad, so if anything's off let me know and I'll correct it)