Usin开发者_高级运维g NHibernate v3.0. I have a class similar to this:
class Foo
{
bool barActive;
Bar bar;
}
The Bar instance is managed entirely internally to Foo:
- when "barActive" is true, "bar" is set to a Bar instance.
- when "barActive" is set to false, the "bar" field is set to null.
Foo.bar is mapped like so:
<many-to-one name="bar" column="BarId" cascade="all-delete-orphan" unique="true" />
However, when "bar" is set to null, it does not delete the Bar record in the database. Bar is an inherited class that is used elsewhere as well, so I can't just make this field a component.
I would have expected the "unique" constraint + "delete-orphan" to handle this. Am I missing something, or can NHibernate not handle this transparently? If it can't, it seems my only option is to raise an event so a higher-level scope can call ISession.Delete(bar).
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).
精彩评论