Reputation: 1550
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 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?
Upvotes: 9
Views: 11485
Reputation: 71591
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).
Upvotes: 1
Reputation: 56984
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.
Upvotes: 3