CSharp · Console
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using OperationsList = System.Collections.Generic.LinkedList<OperationType>;
using OperationsResult = System.Collections.Generic.Dictionary<string, object?>;
var runner = new TransactionRunner();
runner
.Add("Op 1", () => { Console.WriteLine(1); return 2; })
.Add("Op 2", (res) => { Console.WriteLine(res.GetResult<int>("Op 1")); })
.Add("Rollback",
() => { throw new Exception(); },
() => Console.WriteLine("Exception was thrown. Rolling back...")
).Execute();
public class OperationType
{
public required string OperationName { get; set; }
public required Delegate Operation { get; set; }
public Delegate? Rollback { get; set; }
}
public enum RunnerState
{
RolledBack = -1,
Initialized = 0,
Commited = 1
};
public class TransactionRunner
{
private readonly OperationsList _operations = new OperationsList();
internal readonly OperationsResult _results = new OperationsResult();
private RunnerState _state = RunnerState.Initialized;
public RunnerState State => _state;
public TransactionRunner Add(string name, Delegate operation, Delegate? rollback = null)
{
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
AppendOperation(op);
return this;
}
public TransactionRunner Add<TResult>(string name, Func<TResult> operation, Delegate? rollback = null)
{
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
AppendOperation(op);
return this;
}
public TransactionRunner Add(string name, Func<OperationsResult, Delegate> operation, Delegate? rollback = null)
{
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
AppendOperation(op);
return this;
}
public TransactionRunner Add(string name, Action<OperationsResult> operation, Delegate? rollback = null)
{
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
AppendOperation(op);
return this;
}
public TransactionRunner Add<TResult>(string name, Func<OperationsResult, Func<TResult>> operation,
Delegate? rollback = null)
{
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
AppendOperation(op);
return this;
}
private void AppendOperation(OperationType operation)
{
_operations.AddLast(operation);
}
public ExecutionType<T> Returns<T>(string operationName)
{
return new ExecutionType<T>(this, operationName);
}
public void Execute()
{
new ExecutionType(this).Execute();
}
public Task ExecuteAsync()
{
return new ExecutionType(this).ExecuteAsync();
}
internal void Executor()
{
if (_state is not RunnerState.Initialized)
{
throw new ApplicationException("Runner can't be called more than once");
}
var currentNode = _operations.First;
while (currentNode is not null)
{
var operation = currentNode.Value;
try
{
if (operation.Operation is Func<OperationsResult> or Action<OperationsResult>)
{
_results[operation.OperationName] = operation.Operation.DynamicInvoke(_results) ?? null;
}
else
{
_results[operation.OperationName] = operation.Operation.DynamicInvoke() ?? null;
}
currentNode = currentNode.Next;
}
catch (Exception e)
{
Rollback(currentNode);
throw new Exception($"Transaction have failed on operation \"{operation.OperationName}\" and was rolled back",
e);
}
}
}
public async Task AsyncExecutor()
{
if (_state is not RunnerState.Initialized)
{
throw new ApplicationException("Runner can't be called more than once");
}
var currentNode = _operations.First;
while (currentNode is not null)
{
var operation = currentNode.Value;
try
{
if (operation.Operation is Func<OperationsResult> or Action<OperationsResult>)
{
_results[operation.OperationName] = operation.Operation.DynamicInvoke(_results) ?? null;
}
else if (operation.Operation.Method.ReturnType.IsSubclassOf(typeof(Task)))
{
dynamic invoke = operation.Operation.DynamicInvoke();
if (invoke is not null)
{
_results[operation.OperationName] = await invoke ?? null;
}
}
else
{
_results[operation.OperationName] = operation.Operation.DynamicInvoke() ?? null;
}
currentNode = currentNode.Next;
}
catch (Exception e)
{
await RollbackAsync(currentNode);
throw new Exception($"Transaction have failed on operation \"{operation.OperationName}\" and was rolled back",
e);
}
}
}
private void Rollback(LinkedListNode<OperationType>? currentNode)
{
while (currentNode?.Previous is not null)
{
var rollback = currentNode.Value.Rollback;
if (rollback is Func<OperationsResult> or Action<OperationsResult>)
{
rollback.DynamicInvoke(_results);
}
else
{
rollback?.DynamicInvoke();
}
currentNode = currentNode.Previous;
}
_state = RunnerState.RolledBack;
}
private async Task RollbackAsync(LinkedListNode<OperationType>? currentNode)
{
while (currentNode is not null)
{
var rollback = currentNode.Value.Rollback;
if (rollback is Func<OperationsResult> or Action<OperationsResult>)
{
rollback.DynamicInvoke(_results);
}
else if (rollback is Func<Task>)
{
dynamic invoke = rollback.DynamicInvoke();
if (invoke is null) continue;
await invoke;
}
else if (rollback is not null)
{
rollback.DynamicInvoke();
}
currentNode = currentNode.Previous;
}
_state = RunnerState.RolledBack;
}
}
public class ExecutionType
{
private readonly TransactionRunner _parent;
public ExecutionType(TransactionRunner parent)
{
_parent = parent;
}
public void Execute() => _parent.Executor();
public async Task ExecuteAsync() => await _parent.AsyncExecutor();
}
public class ExecutionType<T>
{
private readonly TransactionRunner _parent;
private readonly string _returnKey;
public ExecutionType(TransactionRunner parent, string returnKey)
{
_parent = parent;
_returnKey = returnKey;
}
public T? Execute()
{
_parent.Executor();
return (T?)_parent._results[_returnKey];
}
public async Task<T?> ExecuteAsync()
{
await _parent.AsyncExecutor();
return (T?)_parent._results[_returnKey];
}
}
public static class DictionaryExtensions
{
public static object? GetResult(this Dictionary<string, object?> instance, string name)
{
var value = instance[name];
if (value is not null)
{
return value;
}
return null;
}
public static T? GetResult<T>(this Dictionary<string, object?> instance, string name)
{
try
{
return (T?)GetResult(instance, name);
}
catch
{
throw new ApplicationException("Resulting value is not available");
}
}
}