I’ve just created a simple class which I thought I’d share.
Account1.Balance += 10;
Account2.Balance -= 10;
In this case we might expect the second line to throw an exception if the adjustment is not permitted, but the first line has already executed. Obviously this isn’t a problem because we simply wouldn’t update the database, but sometimes you want an operation to occur in memory as an atomic operation; like this
using (var atomicOperation = new AtomicOperation(EcoSpace))
{
Account1.Balance += 10;
Account2.Balance -= 10;
atomicOperation.Commit();
}
And so that is exactly what I wrote. The class uses the UndoService to create/merge/remove undo blocks so it is even possible to nest atomic operations.
public class AtomicOperation : IDisposable
{
IUndoService UndoService;
bool IsResolved;
string UndoBlockName;
public AtomicOperation(IUnitOfWork unitOfWork)
{
Contract.Requires(unitOfWork != null);
Contract.Requires(unitOfWork.ObjectSpace != null);
UndoBlockName = Guid.NewGuid().ToString();
UndoService = unitOfWork.ObjectSpace.GetEcoService<IUndoService>();
UndoService.StartUndoBlock(UndoBlockName);
}
public void Commit()
{
if (IsResolved)
throw new InvalidOperationException("AtomicOperation has already been committed or rolled back");
IsResolved = true;
int index = UndoService.UndoList.IndexOf(UndoBlockName);
if (index > 0)
{
string nameOfBlockAbove = UndoService.UndoList[index - 1].Name;
UndoService.UndoList.MergeBlocks(nameOfBlockAbove, UndoBlockName);
}
else
{
if (index > 0)
UndoService.UndoList.RemoveBlock(UndoBlockName);
if (UndoService.RedoList.IndexOf(UndoBlockName) > 0)
UndoService.RedoList.RemoveBlock(UndoBlockName);
}
}
public void Rollback()
{
if (IsResolved)
throw new InvalidOperationException("AtomicOperation has already been committed or rolled back");
IsResolved = true;
if (UndoService.UndoList.IndexOf(UndoBlockName) > -1)
UndoService.UndoBlock(UndoBlockName);
if (UndoService.RedoList.IndexOf(UndoBlockName) > -1)
UndoService.RedoList.RemoveBlock(UndoBlockName);
}
void IDisposable.Dispose()
{
GC.SuppressFinalize(this);
if (!IsResolved)
Rollback();
}
~AtomicOperation()
{
throw new InvalidOperationException("AtomicOperation was not disposed, try using()");
}
}