2012-08-29

ECO atomic operations

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
{
readonly IEcoServiceProvider EcoServiceProvider;
readonly IUndoService UndoService;
readonly IUnitOfWorkValidator UnitOfWorkValidator;
bool IsActive;
string UndoBlockName;

public AtomicOperation(IUnitOfWork unitOfWork, IUnitOfWorkValidator unitOfWorkValidator)
{
if (unitOfWork == null)
throw new ArgumentNullException("UnitOfWork");
if (unitOfWorkValidator == null)
throw new ArgumentNullException("UnitOfWorkValidator");

this.UndoBlockName = Guid.NewGuid().ToString();
this.EcoServiceProvider = unitOfWork.ObjectSpace;
this.UndoService = EcoServiceProvider.Resolve<IUndoService>();
this.UnitOfWorkValidator = unitOfWorkValidator;
this.UndoService.StartUndoBlock(this.UndoBlockName);
this.IsActive = true;
}

public bool Commit(List<ValidationError> validationErrors, IEcoObject rootObjectToValidate, PathStack currentPath = null)
{
if (validationErrors == null)
throw new ArgumentNullException("ValidationErrors");
if (currentPath == null)
throw new ArgumentNullException("CurrentPath");

int index = UndoService.UndoList.IndexOf(UndoBlockName);
if (index == -1)
throw new InvalidOperationException("Undo block not found");
EnsureIsLatestAtomicOperation();

IUndoBlock undoBlock
= UndoService.UndoList[index];
if (rootObjectToValidate != null)
UnitOfWorkValidator.EnsureObjectIsValidated(rootObjectToValidate);

var newErrors
= new List<ValidationError>();
newErrors.AddRange(UnitOfWorkValidator.Validate(
this, currentPath));
validationErrors.AddRange(newErrors);

if (index > 0)
{
string nameOfBlockAbove = UndoService.UndoList[index - 1].Name;
UndoService.UndoList.MergeBlocks(nameOfBlockAbove, UndoBlockName);
}
RemoveUndoBlocks();

IsActive
= false;
return newErrors.Count == 0;
}

public void RollBack()
{
if (!IsActive)
throw new InvalidOperationException("AtomicOperation has already been committed or rolled back");

IUndoBlock undoBlock
= UndoService.UndoList[UndoBlockName];
if (undoBlock == null)
throw new InvalidOperationException("Undo block not found");

UndoService.UndoBlock(UndoBlockName);
RemoveUndoBlocks();
IsActive
= false;
}

void IDisposable.Dispose()
{
GC.SuppressFinalize(
this);
if (IsActive)
RollBack();
}

~AtomicOperation()
{
throw new InvalidOperationException("AtomicOperation was not disposed");
}

public IEnumerable<Eco.ObjectRepresentation.IObject> GetModifiedObjects()
{
var undoBlock
= UndoService.UndoList[UndoBlockName];
Eco.ObjectRepresentation.IObjectList result
= undoBlock.GetChangedObjects();
Contract.Assume(result
!= null);
return result;
}

void RemoveUndoBlocks()
{
if (UndoService.UndoList.IndexOf(UndoBlockName) > -1)
UndoService.UndoList.RemoveBlock(UndoBlockName);
if (UndoService.RedoList.IndexOf(UndoBlockName) > -1)
UndoService.RedoList.RemoveBlock(UndoBlockName);
}

void EnsureIsLatestAtomicOperation()
{
int undoBlockIndex = UndoService.UndoList.IndexOf(UndoBlockName);
if (undoBlockIndex != -1 && undoBlockIndex != UndoService.UndoList.Count - 1)
throw new InvalidOperationException("AtomicOperation contains child atomic operations");
}
}
}

No comments: