9

I need to execute two operations inside a NHibernate's unit of work: a transactional one (an entity saving) and a not-transactional one.

Since the not-transactional operation cannot be rollbacked, if I save the entity before executing the not-transactional operation (and finally committing the transaction) I still get a transactional behaviour:

  • The operations will be committed only if the two sub-operations execute successfully;
  • If the entity save fails, the not-transactional operation won't be executed;
  • If the not-transactional fails, the entity save will be rollbacked.

The problem: with a code like the following, NHibernate won't execute the actual sql insert until the call to transaction.Commit() (which calls session.Flush() internally):

        using (var transaction = session.BeginTransaction())
        {
            session.Save(entity);
            NotTransactionalOperationBasedOn(entity);

            transaction.Commit(); // the actual sql insert will be executed here
        }

With a code like this, if the Sql Insert fails it's too late: NotTransactionalOperation has been executed. To execute the actual SQL insert before executing NotTransactionalOperation I have to explicity call session.Flush() right before the session.Save():

        using (var transaction = session.BeginTransaction())
        {
            session.Save(entity);
            session.Flush(); // the actual sql insert will be executed here

            NotTransactionalOperationBasedOn(entity);

            transaction.Commit(); 
        }

The code works, but... is it considered a best practice to call session.Flush() before committing the transaction? If not, are there better ways to achieve the same result?

Notoriousxl
  • 1,540
  • 1
  • 16
  • 27

2 Answers2

3

Flushing means that NHibernate will make sure that all changes are persisted to the DB. That is, it will make sure that all necessary SQL statements are executed. When the transaction fails, and is thus rollbacked, all those changes will be reverted. So, I see no problem in doing that (you must keep in mind however, that your entity might not be in a valid state, since the transaction has failed).

But ... I don't see the problem actually: When the non-transactional procedure fails, what's the problem ? Since the procedure is non-transactional, I guess it has no influence on the information that is held by the database ? Or, what does this code do ?

If it is non-transactional, why can't you do it outside your unit-of-work ?

When the save fails, I guess you'll make sure that not only the transaction is rolled-back, but the exception will be thrown upon the stack as well, until it encounters an error-handler, which probably means that your non-transactional code will not be executed as well:

using( var transaction = session.BeginTransaction() ) 
{
      repository.Save (entity); 

      transaction.Commit();   // error, exception is thrown.
}

NonTransactionalCode (entity); // this line will not be executed, since the exception will be thrown up the stack until a suitable catch block is encountered.
Frederik Gheysels
  • 56,135
  • 11
  • 101
  • 154
  • 2
    I know it's a bit unusual, but the NonTransactionalCode(entity) represents an entity saving to a remote hardware device, and a db entry represents a successfull write to that hardware; so I can't have a db entry with a unsuccessfull remote saving, and vice-versa. :) (errors are still handled by a exception logging mechanism) My doubts came from the fact that the transaction.Commit calls Flush() internally, and I ended up calling Flush() twice. – Notoriousxl Mar 28 '11 at 22:00
  • 1
    @Notoriousxl: don't worry, the second Flush is an (almost) noop. – Diego Mijelshon Mar 28 '11 at 23:36
  • 2
    And what do you do if the commit fails, even if the flush has been successful? – Gerke Geurts Mar 29 '11 at 14:46
1

It's unusual, but if you want to do something outside the unit of work that depends on something done inside, it's perfectly valid. However, the question then becomes, why is the operation performed outside the unit of work if it is dependent on something inside? If it's fully order-dependent, then you should consider a flush AND commit before performing the non-transactional operation. If the non-trans operation determines whether the entire thing will commit (for instance, maybe it throws an exception), then it should be part of the unit of work.

The non-trans option, further, should probably not have to depend on the data being in the DB, unless it depends on data-layer code like triggers (though I like triggers, they should generally be avoided for several reasons, not the least of which is the tendency to have convoluted save operations like this). You should generally keep your business logic in your application layer except as the final option. If the object has been saved to a session, then the data is available wherever that session is, and that session should be as widely-scoped as necessary (or, consider a shared "parent" session with children that have access to the parent at all times).

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • Executing both operations in a pseudo-transactional context is a prerequisite of the application, see my response to Frederik... the db entry is something like a meta representation of a successfull NotTransactionalOperation... :) – Notoriousxl Mar 28 '11 at 22:07