2

Using 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:

  1. when "barActive" is true, "bar" is set to a Bar instance.
  2. 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).

naasking
  • 2,514
  • 1
  • 27
  • 32
  • 1
    a `many-to-one` mapping doesn't have an option for all-delete-orphan in cascade. Only `all|none|save-update|delete`. See here http://www.nhforge.org/doc/nh/en/index.html#mapping-declaration-manytoone – Vadim Mar 10 '11 at 21:56
  • Then why is the mapping accepted by NHibernate without an error? In any case, do you have a way to accomplish what I'm looking for? – naasking Mar 11 '11 at 02:38
  • 1
    as far as I can tell there is no way for NHibernate to automatically delete an orphan for a `many-to-one` relationship as there is no check for that in the code. It will cascade the other 4 as mentioned though. There is this jira ticket http://216.121.112.228/browse/NH-1262 which I think is related, but you can open up another one specifically for your issue. – Vadim Mar 11 '11 at 16:12

1 Answers1

0

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).

Community
  • 1
  • 1