Thursday, April 2, 2009

Adding transaction autocomplete support to NCommon's UnitOfWorkScope class

Something I miss from NCommon's UnitOfWorkScope class is the ability to start an auto-complete capable UoWS. That's specially true on such cases on which you are trying to read some value from a repository without writing anything back into it.

With AutoComplete I mean being able to start a UoW scope which will automatically commit changes made if no uncatched exception is thrown nor a rollback operation is performed, inside it.

Adding such thing is pretty easy and that's why today I'm going to show you how to add a new "AutoComplete" option to UoWS so you can write code like this not caring about calling scope.Commit() at all:


using (new UnitOfWorkScope(UnitOfWorkScopeTransactionOptions.AutoComplete))
{
...
}


Let's start adding a new "AutoComplete" option to UnitOfWorkScopeTransactionOptions enumeration:


[Flags]
public enum UnitOfWorkScopeTransactionOptions
{
...

/// <summary>
/// Specifies that UoW scope will be commited automatically at Dispose(), if it has not been rolled back previously.
/// </summary>
AutoComplete = (1<<4)
}


Now we should modify UnitOfWorkScope class to handle this new option.

Step one, adding a new private field which will indicate whether this is an autocomplete enabled UoWS or not:


public class UnitOfWorkScope : IDisposable {
...
private bool _autocomplete = false;
...
}


Step two, modifying class' constructor:


public UnitOfWorkScope(IsolationLevel isolationLevel, UnitOfWorkScopeTransactionOptions transactionOptions)
{
_disposed = false;

if ((transactionOptions & UnitOfWorkScopeTransactionOptions.AutoComplete) != 0)
_autocomplete = true;

_currentTransaction = UnitOfWorkScopeTransaction.GetTransactionForScope(this, isolationLevel,
transactionOptions);
RegisterScope(this);
}


Step three, modifying Dispose() method to make it commit transaction if autocomplete is desired.


private void Dispose(bool disposing)
{
if (!disposing) return;
if (_disposed) return;

if (_currentTransaction != null)
{
if (_autocomplete == true)
_currentTransaction.Commit(this);
else
_currentTransaction.Rollback(this);

_currentTransaction = null;
}
UnRegisterScope(this);
_disposed = true;
}


Ok, now we need a unit test to ensure this is working as expected. So, let's add a new test at NCommon.Tests.UnitOfWorkScopeTests fixture.


[Test]
public void Disposing_AutoCommit_Scope_Does_Not_Calls_Rollback_On_Transaction()
{
var mockLocator = MockRepository.GenerateStub<IServiceLocator>();
var mockUOWFactory = MockRepository.GenerateMock<IUnitOfWorkFactory>();
var mockUOW = MockRepository.GenerateMock<IUnitOfWork>();
var mockTransaction = MockRepository.GenerateMock<ITransaction>();

mockLocator.Stub(x => x.GetInstance<IUnitOfWorkFactory>()).Return(mockUOWFactory);
mockUOWFactory.Expect(x => x.Create()).IgnoreArguments().Return(mockUOW);
mockUOW.Expect(x => x.BeginTransaction(IsolationLevel.ReadCommitted)).Return(mockTransaction);
mockUOW.Expect(x => x.Dispose());

mockTransaction.Expect(x => x.Commit());
mockTransaction.Expect(x => x.Dispose());

ServiceLocator.SetLocatorProvider(() => mockLocator);

using (new UnitOfWorkScope(UnitOfWorkScopeTransactionOptions.AutoComplete))
{
Assert.That(UnitOfWorkScope.HasStarted);
}

Assert.That(!UnitOfWorkScope.HasStarted);
mockUOWFactory.VerifyAllExpectations();
mockUOW.VerifyAllExpectations();
mockTransaction.VerifyAllExpectations();
}



Compile, tests, and.. that.. that.. that's all folks ;)

PD: Just one final note, this and a few more changes I've made to this library are available at my own subversion repository.

Right now I am making a few changes to this library, but once I feel my code is stable enough, and that I wont be making any major changes, I will try to submit all my additions to NCommon's maintainer proposing him merging my code back into his trunk.

Greets.

No comments:

Post a Comment