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");
}
}
}
Comments