A standalone Unit of Work component for Entity Framework Core 2.0 and ASP.NET Core 2.0 which handles transactions automatically (in ASP.NET request scope) by committing (and rolling back in case of an error) them at the end of HTTP request. Supports multiple DbContexts.
This repository contains several C# projects:
- UnitOfWorkCore - core library with Unit of Work pattern implementation
- UnitOfWorkCore.AspNetCore - UnitOfWorkCore configuration (transaction filters and isolation level attributes) for ASP.NET Core 2.0
- Sample projects:
- UnitOfWorkCore.Samples.ReleasesDatabase - sample database migration scripts (console app using DbUp)
- UnitOfWorkCore.Samples.IssuesDatabase - sample database migration scripts (console app using DbUp)
- UnitOfWorkCore.Samples.SingleContextApi - sample ASP.NET Core 2.0 project using UnitOfWorkCore with only one DbContext
- UnitOfWorkCore.Samples.MultiContextApi - sample ASP.NET Core 2.0 project using UnitOfWorkCore with two DbContexts
UnitOfWorkCore component is distributed as two separate NuGet packages
- UnitOfWorkCore - for use in projects that don't depend on ASP.NET Core (e.g. services layer in your solution)
- UnitOfWorkCore.AspNetCore - for use in ASP.NET Core projects with REST API
To install UnitOfWorkCore
and UnitOfWorkCore.AspNetCore
in your project(s), use the Visual Studio's built-in NuGet GUI (Manage NuGet packages option in project's context menu) or run the following commands in Package Manager Console:
Install-Package UnitOfWorkCore
Install-Package UnitOfWorkCore.AspNetCore
Register your DbContext in ConfigureServices
method in Startup.cs
services.AddDbContext<ReleasesDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ReleasesDb")));
Register UnitOfWorkCore for your DbContext in services
services.AddUnitOfWork<ReleasesDbContext>();
Add UnitOfWorkTransactionFilter
to global filters
services.AddMvcCore(options =>
{
options.Filters.Add(typeof(UnitOfWorkTransactionFilter));
});
Inject IUnitOfWork
into your services where you need to access DbSets or save database changes (like EF's DbContext.SaveChanges) to obtain inserted entity's id etc.
public ReleasesService(IUnitOfWork uow)
{
this._uow = uow;
}
Use the IUnitOfWork
in your service
//add entity to the database
_uow.Set<ReleaseEntity>().Add(entity);
//save changes to obtain the entity id
_uow.SaveChanges();
PRO TIP: If you don't want to write _uow.Set<MyEntity>() with the exact name of the entity's class because you never remember it, or would like Intellisense to help you with selecting the right entities set, you can either:
A) Use helper extension methods:
public static DbSet<ReleaseEntity> Releases(this IUnitOfWork uow)
{
return uow.Set<ReleaseEntity>();
}
//and then:
_uow.Releases().Add(entity);
B) Utilize decorator pattern described in the next section with Multiple DbContexts usage (this is a more elegant solution for multiple DbContexts, but it also works for a single one)
Register your DbContexts in ConfigureServices
method in Startup.cs
services.AddDbContext<ReleasesDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ReleasesDb")));
services.AddDbContext<IssuesDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("IssuesDb")));
Register multiple UoWs, providing a unique key (string) for each of them
services.AddUnitOfWorkPool(optionsBuilder =>
{
optionsBuilder.AddUnitOfWork<ReleasesDbContext>("Releases");
optionsBuilder.AddUnitOfWork<IssuesDbContext>("Issues");
});
Add UnitOfWorkPoolTransactionFilter
(please notice the Pool word) to global filters
services.AddMvcCore(options =>
{
options.Filters.Add(typeof(UnitOfWorkPoolTransactionFilter));
});
Inject IUnitOfWorkPool
into your services and retrieve IUnitOfWork
for a specific DbContext by its key
private readonly IUnitOfWork _releasesUow;
private readonly IUnitOfWork _issuesUow;
public MyService(IUnitOfWorkPool uowPool)
{
_releasesUow = uowPool.Get("Releases");
_issuesUow = uowPool.Get("Issues");
}
PRO TIP: Injecting IUnitOfWorkPool
into your services and retrieving the required IUnitOfWork
by key may be a bit tedious and unelegant - it would be much easier if you had a separate interface for each of your UoWs, e.g IReleasesUoW and IIssuesUoW, which you could inject instead of the whole IUnitOfWorkPool
. You can easily do this by using the decorator pattern that will also allow you to extend UoW with additional methods and properties (e.g. DbSet properties for easier access to data with Intellisense, methods for bulk insert, delete etc.) See the sample code in UnitOfWorkCore.Samples.MultiContextApi project.
The general steps for the adapter are:
- Create an interface that inherits from
IUnitOfWork<TDbContext>
and add your own methods and properties
public interface IReleasesUoW : IUnitOfWork<ReleasesDbContext>
{
DbSet<ReleaseEntity> Releases { get; }
}
- Implement your custom interface, inject
IUnitOfWorkPool
and use theIUnitOfWork
retrieved by key in your methods
public class ReleasesUoW : IReleasesUoW
{
private readonly IUnitOfWork _uow;
public DbSet<ReleaseEntity> Releases => _uow.Set<ReleaseEntity>();
public ReleasesUoW(IUnitOfWorkPool uowPool)
{
_uow = uowPool.Get("Releases");
}
//IUnitOfWork<ReleasesDbContext> methods
public void CommitTransaction()
{
_uow.CommitTransaction();
}
//(...)
}
- Register your custom UoW in
Startup.ConfigureServices
and inject it into your other classes
//register in DI container
services.AddScoped<IReleasesUoW, ReleasesUoW>();
//use as a dependency in other classes instead of IUnitOfWorkPool
public MyService(IReleasesUoW uow) {}