Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>I've been struggling with this for a while now. Am going to have another crack at it.</p> <p>I want to implement transactions in individual service containers - because that makes them self-contained - but then be able to nest a bunch of those service methods within a larger transaction and rollback the whole lot if necessary.</p> <p>Because I'm using <a href="http://www.ayende.com/Wiki/Rhino+Commons.ashx" rel="nofollow noreferrer">Rhino Commons</a> I'm now going to try refactoring using the <a href="https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/commons/Rhino.Commons.NHibernate/With/With.Transaction.cs" rel="nofollow noreferrer"><code>With.Transaction</code></a> method. Basically it allows us to write code as if transactions were nested, though in reality there is only one.</p> <p>For example:</p> <pre><code>private Project CreateProject(string name) { var project = new Project(name); With.Transaction(delegate { UnitOfWork.CurrentSession.Save(project); }); return project; } private Sample CreateSample(Project project, string code) { var sample = new Sample(project, code); With.Transaction(delegate { UnitOfWork.CurrentSession.Save(sample); }); return sample; } private void Test_NoNestedTransaction() { var project = CreateProject("Project 1"); } private void TestNestedTransaction() { using (var tx = UnitOfWork.Current.BeginTransaction()) { try { var project = CreateProject("Project 6"); var sample = CreateSample(project, "SAMPLE006", true); } catch { tx.Rollback(); throw; } tx.Commit(); } } </code></pre> <p>In <code>Test_NoNestedTransaction()</code>, we are creating a project alone, without the context of a larger transaction. In this case, in <code>CreateSample</code> a new transaction will be created and committed, or rolled back if an exception occurs.</p> <p>In <code>Test_NestedTransaction()</code>, we are creating both a sample and a project. If anything goes wrong, we want both to be rolled back. In reality, the code in <code>CreateSample</code> and <code>CreateProject</code> will run just as if there were no transactions at all; it is entirely the outer transaction that decides whether to rollback or commit, and does so based on whether an exception is thrown. Really that's why I'm using a manually created transaction for the outer transaction; so we I have control over whether to commit or rollback, rather than just defaulting to <em>on-exception-rollback-else-commit</em>.</p> <p>You could achieve the same thing without Rhino.Commons by putting a whole lot of this sort of thing through your code:</p> <pre><code>if (!UnitOfWork.Current.IsInActiveTransaction) { tx = UnitOfWork.Current.BeginTransaction(); } _auditRepository.SaveNew(auditEvent); if (tx != null) { tx.Commit(); } </code></pre> <p>... and so on. But <code>With.Transaction</code>, despite the clunkiness of needing to create anonymous delegates, does that quite conveniently.</p> <p>An advantage of this approach over using <code>TransactionScope</code>s (apart from the reliance on MSDTC) is that there ought to be just a single flush to the database in the final outer-transaction commit, regardless of how many methods have been called in-between. In other words, we don't need to write uncommitted data to the database as we go, we're always just writing it to the local NHibernate cache.</p> <p>In short, this solution doesn't offer ultimate control over your transactions, because it doesn't ever use more than one transaction. I guess I can accept that, since nested transactions are by no means universally supported in every DBMS anyway. But now perhaps I can at least write code without worrying about whether we're already in a transaction or not.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload