I have a workaround, that will automatically delete the orphan. I believe it should work for NHibernate version 3 and above. It uses an interceptor - basically an object that handles various session-related events. When it detects the update operation on Foo
, it will add an explicit delete for the orphaned Bar
.
using System;
using System.Collections;
using NHibernate;
using NHibernate.Type;
class Interceptor : EmptyInterceptor
{
private ISession _session;
private Bar _barOrphan;
public override void SetSession(ISession session)
{
base.SetSession(session);
_session = session;
}
public override bool OnFlushDirty(object entity, object id, object[] currentStates, object[] previousStates, string[] propertyNames, IType[] types)
{
if (entity.GetType() != typeof(Foo)) return;
for (var i = 0; i < propertyNames.Length; i++)
{
if (!StringComparer.Ordinal.Equals(propertyNames[i], "bar")) continue;
object previousState = previousStates[i];
if (currentStates[i] != previousState)
{
_barOrphan = (Bar) previousState;
}
break;
}
}
public override void PostFlush(ICollection entities)
{
if (_barOrphan == null) return;
_session.Delete(_barOrphan);
_barOrphan = null;
_session.Flush();
}
}
Now, when opening your NHibernate session, you have to use one of the overloads that accepts an interceptor instance as an argument, e.g.
using (ISession session = YourSessionFactoryGoesHere.OpenSession(new Interceptor()))
{
...
}
Please note that this is just a draft, to explain the concept (I hope I did not screw up the code as I was rewriting it ;-). In a real usage scenario, you may have to deal with possible multiple orphans created in one unit of work (event on the same entity, e.g. Foo
could have bar1
and bar2
!), so instead of a single _barOrphan
member you will need a queue of the delete actions to be executed in PostFlush()
. Instead of hard-coding the types of the involved classes and the name of the property bar
, you will want to use generics and a property selector (e.g. PropertySelector.GetPropertyName<Foo>(foo => foo.bar)
, see this link. DB constraint could be a problem, moving the delete operation to Interceptor.PreFlush()
might help, but I did not test it. Don't forget about the performance impact (e.g. OnFlushDirty()
is invoked for each updated entity, so don't make it a bottleneck).