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.