Skip to content

Instantly share code, notes, and snippets.

@mdekrey
Created February 16, 2018 02:45
Show Gist options
  • Save mdekrey/ed50034666d14af7702d5e536f5f2a8f to your computer and use it in GitHub Desktop.
Save mdekrey/ed50034666d14af7702d5e536f5f2a8f to your computer and use it in GitHub Desktop.
Unit of Work

This wasn't quite enough to make it into a whole repository, but I may do so eventually.

This is a basic extensible unit of work framework I've used in various projects, including Entity Framework and ADO.Net using Transactions. I hope it helps you!

The idea is that a IUnitOfWorkLifecycleManagement orchestrates the lifecycle of the work target, such as setting up and committing a transaction, or validating and saving changes to an EF context, or even preparing a GraphQL mutation! Adding things to the unit of work is synchronous, so you can prepare for a unit of work's deferred commit by "preparing" work actions. (This is useful for loading from a context to make updates before actually making the changes to commit.)

Hopefully I'll add examples and such later.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Woosti.Work
{
public interface IUnitOfWork : IDisposable
{
Task CommitAsync(CancellationToken cancellationToken);
void Prepare(Func<IServiceProvider, CancellationToken, Task> action);
void Defer(Func<IServiceProvider, Task> action);
void Defer(Func<IServiceProvider, CancellationToken, Task> action);
void AfterCommit(Func<IServiceProvider, Task> action);
void PrepareAndFinalize<T>()
where T : IUnitOfWorkLifecycleManagement;
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Woosti.Work
{
public interface IUnitOfWorkFactory
{
IUnitOfWork CreateUnitOfWork();
}
}
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Woosti.Work
{
public interface IUnitOfWorkLifecycleManagement
{
Task Prepare(CancellationToken cancellationToken);
Task Commit();
Task Rollback();
}
}
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Woosti.Work
{
internal class UnitOfWork : IUnitOfWork
{
struct PortionOfWork
{
public Func<IServiceProvider, CancellationToken, Task> Action;
}
private readonly Queue<PortionOfWork> prework = new Queue<PortionOfWork>();
private readonly List<PortionOfWork> work = new List<PortionOfWork>();
private readonly HashSet<Type> lifecycleManagementTypes = new HashSet<Type>();
private readonly IServiceProvider provider;
private readonly List<Func<IServiceProvider, Task>> afterCommit = new List<Func<IServiceProvider, Task>>();
public UnitOfWork(IServiceProvider provider)
{
this.provider = provider;
}
public void Prepare(Func<IServiceProvider, CancellationToken, Task> action) =>
prework.Enqueue(new PortionOfWork
{
Action = action,
});
public void Defer(Func<IServiceProvider, Task> action) =>
work.Add(new PortionOfWork
{
Action = (sp, _) => action(sp),
});
public void Defer(Func<IServiceProvider, CancellationToken, Task> action) =>
work.Add(new PortionOfWork
{
Action = action,
});
public void AfterCommit(Func<IServiceProvider, Task> action) =>
afterCommit.Add(action);
public void PrepareAndFinalize<T>()
where T : IUnitOfWorkLifecycleManagement
{
lifecycleManagementTypes.Add(typeof(T));
}
public async Task CommitAsync(CancellationToken cancellationToken)
{
using (var scope = provider.CreateScope())
{
var values = lifecycleManagementTypes.Select(type => scope.ServiceProvider.GetRequiredService(type) as IUnitOfWorkLifecycleManagement).ToArray();
try
{
while (prework.Count > 0)
{
var portionOfWork = prework.Dequeue();
await portionOfWork.Action(scope.ServiceProvider, cancellationToken);
}
foreach (var portionOfWork in values)
{
await portionOfWork.Prepare(cancellationToken);
}
foreach (var portionOfWork in work)
{
await portionOfWork.Action(scope.ServiceProvider, cancellationToken);
}
foreach (var portionOfWork in values)
{
await portionOfWork.Commit();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
foreach (var portionOfWork in values)
{
await portionOfWork.Rollback();
}
throw;
}
foreach (var entry in afterCommit)
{
await entry(scope.ServiceProvider);
}
}
}
void IDisposable.Dispose()
{
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Woosti.Work
{
class UnitOfWorkFactory : IUnitOfWorkFactory
{
private readonly IServiceProvider serviceProvider;
public UnitOfWorkFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public IUnitOfWork CreateUnitOfWork()
{
return new UnitOfWork(serviceProvider);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment