Transactions are a common cross-cutting concern, that need to be managed carefully.
You often want a transaction to be shared between multiple repositories, but you don’t want low level details (e.g. what ORM you are using) to bleed out of the persistence layer.
There’s also a lot of boiler plate code, and forgetting to commit the transaction is a common (and easy) mistake to make.
Bring the Func!
public class TransactionController : ITransactionController { private readonly ISession _session; public TransactionController(ISession session) { _session = session; } public void InTransaction(Action action) { using (var transaction = _session.BeginTransaction()) { action(); transaction.Commit(); } } } [TestFixture] public class TransactionControllerTests { private TransactionController _transactionController; private ISession _session; [SetUp] public void SetUp() { _session = MockRepository.GenerateStub<ISession>(); _transactionController = new TransactionController(_session); } [Test] public void Begin_transaction() { StubTransaction(); _transactionController.InTransaction(() => { }); _session.AssertWasCalled(s => s.BeginTransaction()); } [Test] public void Perform_action() { StubTransaction(); var flag = false; _transactionController.InTransaction(() => { flag = true; }); Assert.That(flag, Is.True); } [Test] public void Commit_transaction() { var transaction = StubTransaction(); _transactionController.InTransaction(() => { }); transaction.AssertWasCalled(t => t.Commit()); } [Test] public void Dispose_transaction() { var transaction = StubTransaction(); _transactionController.InTransaction(() => { }); transaction.AssertWasCalled(t => t.Dispose()); } [Test] public void Rollback_on_error() { var transaction = StubTransaction(); try { _transactionController.InTransaction(() => { throw new Exception(); }); } catch (Exception) { } transaction.AssertWasNotCalled(t => t.Commit()); transaction.AssertWasCalled(t => t.Dispose()); } private ITransaction StubTransaction() { var transaction = MockRepository.GenerateStub<ITransaction>(); _session.Stub(s => s.BeginTransaction()).Return(transaction); return transaction; } }
This can also be done using an AOP framework (like PostSharp) but, having tried that, I prefer it to be explicit in the code.