views:

469

answers:

2

I am trying to use NHibernate with legacy entities that are not mapped with NHibernate. On occaisson this means that I need to manually flush NHibernate data to the database so that I don't receive foreign key exceptions when I try to connect the legacy entities with NHibernate-mapped entities.

A problem occurs when this takes place within a transaction that then needs to be rolled back. The data flushed from NHibernate does not rollback.

Is there anything I can do about this?

+1  A: 

When using transactions with NHibernate try to avoid using the Session.Flush() and instead use the transaction.Commit() which it calls the session.flush() internally.

If during the Commit() an error occurs and the transaction needs to be rolled back this can be addressed like this.

public static void CommitChanges()
{
    ITransaction transaction = Session.BeginTransaction();

    try
    {
     transaction.Commit();
    }
    catch (HibernateException ex)
    {
     transaction.Rollback();
     //close and dispose session here
     throw ex;
    }
    finally
    {
     transaction.Dispose();
    }
}

Now, if a manual call to flush() or a call to commit() goes through successfully there isn't a way to roll back the transaction using NHibernate mechanisms. Especially when calling the transaction.Commit() command the AdoTransaction created by NHibernate is then disposed right after the Commit() finishes so you cannot access it in order to roll back.

The code sample above allows you to catch errors that happen during commit and then roll back the transaction that has started already.

Now instead of calling the transaction.Commit() in the sample above you call the session.Flush() in my tests no data are saved in the Database as the transaction is never commited.

I have no idea how your code looks like but if you are calling in a pattern, as the above the code sample shows, the transaction.commit() instead of the Session.Flush() it should give you a way to achieve what you want.

tolism7
Your catch(...) section doesn't add anything functional, as the Dispose() already does a Rollback (if not commited).
taoufik
@taoufik I am affraid that your comment is invalid. The Dispose() command of the ITransaction interface as implemented by the AdoTransaction object in the NHibernate code is responsible only for freeing managed and unmanaged resources. Furthermore a quick look into NHibernate source code proved the fact that the Dispose() does NOT call the ITransaction.RollBack() or the underlying IDbTransaction object's RollBack() method... Therefore the catch(...) section is entirely functional and necessary to the above code sample.
tolism7
@tolism7 The following posting shows a code snippet (from Reflector) which proves that the Dispose does call Rollback: http://stackoverflow.com/questions/641660/will-a-using-statement-rollback-a-database-transaction-if-an-error-occurs
taoufik
+1  A: 

For the sake of formatting I allow myself to update tolism7's answer here.

  1. use using and forget about transaction.Dispose() - the transaction will automatically be Dispose'd of at the end of the using block.
  2. throw - don't throw ex because it means throwing away your stacktrace (see this post where it states "When the .NET Framework executes this statement: throw ex; it throws away all the stack information above the current function.")

.

public void CommitChanges()
{
    using (var transaction = Session.BeginTransaction())  // <-- open scope
        try
        {
            // do something
            transaction.Commit();
        }
        catch (HibernateException)
        {
            transaction.Rollback();
            _session.Close();
            _session.Dispose();

            throw;  // <-- this way the stacktrace stays intact!
        }
}

A VB.NET version of this piece of code can be found here.

Oliver