├── docker-compose.override.yml ├── sql-server-container ├── entrypoint.sh ├── initialize-database.sql ├── setup-database.sh └── Dockerfile ├── example-with-events ├── Uow.SqliteWithEvents.Example │ ├── EntityEvent.cs │ ├── Entity.cs │ ├── UnitOfWork │ │ ├── ICreateUnitOfWork.cs │ │ ├── IEventPublisher.cs │ │ ├── ITransactionalEventPublisher.cs │ │ ├── ITransactionalEventPublisherFactory.cs │ │ ├── IGetConnection.cs │ │ ├── IUnitOfWork.cs │ │ ├── UnitOfWork.cs │ │ └── UnitOfWorkContext.cs │ ├── Uow.SqliteWithEvents.Example.csproj │ └── EntityRepository.cs └── Uow.SqliteWithEvents.Tests │ ├── Infrastructure │ ├── RepositoryTest.cs │ ├── TestDatabaseContext.cs │ └── DatabaseFixture.cs │ ├── Fakes │ └── FakeEventPublisher.cs │ ├── EntityRepositoryTest.cs │ ├── Uow.SqliteWithEvents.Tests.csproj │ └── UnitOfWorkTest.cs ├── basic-example ├── Uow.Sqlite.Example │ ├── Application │ │ ├── ICreateUnitOfWork.cs │ │ ├── Entity.cs │ │ ├── IEntityRepository.cs │ │ ├── IUnitOfWork.cs │ │ └── Controller.cs │ ├── Storage │ │ ├── IGetConnection.cs │ │ ├── EntityRepository.cs │ │ ├── UnitOfWork.cs │ │ └── UnitOfWorkContext.cs │ └── Uow.Sqlite.Example.csproj └── Uow.Sqlite.Tests │ ├── Infrastructure │ ├── RepositoryTest.cs │ ├── DatabaseFixture.cs │ └── TestDatabaseContext.cs │ ├── Uow.Sqlite.Tests.csproj │ ├── EntityRepositoryTest.cs │ └── ControllerTest.cs ├── mssql-example ├── Uow.Mssql.Database │ ├── UnitOfWork │ │ ├── ICreateUnitOfWork.cs │ │ ├── IGetConnection.cs │ │ ├── IUnitOfWork.cs │ │ ├── UnitOfWork.cs │ │ └── UnitOfWorkContext.cs │ ├── Entity.cs │ ├── SqlSettings.cs │ ├── Uow.Mssql.Database.csproj │ └── EntityRepository.cs ├── Uow.Mssql.Tests │ ├── Infrastructure │ │ ├── DatabaseCollection.cs │ │ ├── RepositoryTest.cs │ │ ├── DatabaseFixture.cs │ │ └── TestDatabaseContext.cs │ ├── EntityRepositoryTest.cs │ ├── Uow.Mssql.Tests.csproj │ └── UnitOfWorkTest.cs └── Uow.Mssql.Migrator │ ├── SqlSettings.cs │ ├── appsettings.json │ ├── Migrations │ └── Migration001_CreateEntityTable.cs │ ├── Uow.Mssql.Migrator.csproj │ ├── MigrationLogger.cs │ └── Program.cs ├── postgresql-example ├── Uow.Postgresql.Database │ ├── Entity.cs │ ├── UnitOfWork │ │ ├── ICreateUnitOfWork.cs │ │ ├── IGetUnitOfWork.cs │ │ ├── IUnitOfWork.cs │ │ ├── UnitOfWork.cs │ │ └── UnitOfWorkContext.cs │ ├── SqlSettings.cs │ ├── Uow.Postgresql.Database.csproj │ └── EntityRepository.cs ├── Uow.Postgresql.Tests │ ├── Infrastructure │ │ ├── DatabaseCollection.cs │ │ ├── RepositoryTest.cs │ │ ├── DatabaseFixture.cs │ │ └── TestDatabaseContext.cs │ ├── EntityRepositoryTest.cs │ ├── Uow.Postgresql.Tests.csproj │ └── UnitOfWorkTest.cs └── Uow.Postgresql.Migrator │ ├── SqlSettings.cs │ ├── appsettings.json │ ├── Migrations │ └── Migration001_CreateEntityTable.cs │ ├── Uow.Postgresql.Migrator.csproj │ ├── MigrationLogger.cs │ └── Program.cs ├── entity-framework-example ├── Uow.EntityFramework.Example │ ├── Storage │ │ ├── IGetDbContext.cs │ │ ├── ExampleDbContext.cs │ │ ├── EntityRepository.cs │ │ ├── UnitOfWork.cs │ │ └── UnitOfWorkContext.cs │ ├── Application │ │ ├── ICreateUnitOfWork.cs │ │ ├── Entity.cs │ │ ├── IEntityRepository.cs │ │ ├── IUnitOfWork.cs │ │ └── Controller.cs │ ├── Migrations │ │ ├── migrations.md │ │ ├── 20230318093204_CreateEntityTable.cs │ │ ├── ExampleDbContextModelSnapshot.cs │ │ └── 20230318093204_CreateEntityTable.Designer.cs │ ├── Uow.EntityFramework.Example.csproj │ └── MigrationToolHacks.cs ├── Uow.EntityFramework.Tests │ ├── Infrastructure │ │ ├── DatabaseCollection.cs │ │ ├── RepositoryTest.cs │ │ ├── DatabaseFixture.cs │ │ └── TestDatabaseContext.cs │ ├── EntityRepositoryTest.cs │ ├── UnitOfWorkTest.cs │ ├── Uow.EntityFramework.Tests.csproj │ └── ControllerTest.cs └── Uow.EntityFramework.Migrator │ ├── SqlSettings.cs │ ├── appsettings.json │ ├── Migrations │ ├── Migration001_CreateEntityTable.cs │ └── migrations.md │ ├── Uow.EntityFramework.Migrator.csproj │ ├── MigrationLogger.cs │ └── Program.cs ├── .dockerignore ├── docker-compose.dcproj ├── .editorconfig ├── docker-compose.yml ├── LICENSE ├── .gitignore ├── README.md └── UnitOfWork.sln /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | -------------------------------------------------------------------------------- /sql-server-container/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 'running database setup' 4 | ./setup-database.sh & ./opt/mssql/bin/sqlservr 5 | -------------------------------------------------------------------------------- /sql-server-container/initialize-database.sql: -------------------------------------------------------------------------------- 1 | IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = 'mydatabasedb') 2 | BEGIN 3 | CREATE DATABASE mydatabasedb; 4 | END 5 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/EntityEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.SqliteWithEvents.Example; 2 | 3 | public class EntityEvent 4 | { 5 | public int Id { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Application/ICreateUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Sqlite.Example.Application; 2 | 3 | public interface ICreateUnitOfWork 4 | { 5 | IUnitOfWork Create(); 6 | } 7 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/UnitOfWork/ICreateUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Mssql.Database.UnitOfWork; 2 | 3 | public interface ICreateUnitOfWork 4 | { 5 | IUnitOfWork Create(); 6 | } 7 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/Entity.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Postgresql.Database; 2 | 3 | public class Entity 4 | { 5 | public int Id { get; set; } 6 | public int Value { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/UnitOfWork/ICreateUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Postgresql.Database.UnitOfWork; 2 | 3 | public interface ICreateUnitOfWork 4 | { 5 | IUnitOfWork Create(); 6 | } 7 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Application/Entity.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Sqlite.Example.Application; 2 | 3 | public class Entity 4 | { 5 | public int? Id { get; set; } 6 | public int Value { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/Entity.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Mssql.Database 2 | { 3 | public class Entity 4 | { 5 | public int Id { get; set; } 6 | public int Value { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Storage/IGetConnection.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace Uow.Sqlite.Example.Storage; 4 | 5 | public interface IGetConnection 6 | { 7 | IDbConnection GetConnection(); 8 | } 9 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Storage/IGetDbContext.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.EntityFramework.Example.Storage; 2 | 3 | public interface IGetDbContext 4 | { 5 | ExampleDbContext GetDbContext(); 6 | } 7 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/Entity.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.SqliteWithEvents.Database; 2 | 3 | public class Entity 4 | { 5 | public int? Id { get; set; } 6 | public int Value { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/UnitOfWork/ICreateUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.SqliteWithEvents.Example.UnitOfWork; 2 | 3 | public interface ICreateUnitOfWork 4 | { 5 | IUnitOfWork Create(); 6 | } 7 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Application/ICreateUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.EntityFramework.Example.Application; 2 | 3 | public interface ICreateUnitOfWork 4 | { 5 | IUnitOfWork Create(); 6 | } 7 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Application/IEntityRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Sqlite.Example.Application; 2 | 3 | public interface IEntityRepository 4 | { 5 | int Create(int value); 6 | Entity? GetOrDefault(int id); 7 | } 8 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/UnitOfWork/IEventPublisher.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.SqliteWithEvents.Example.UnitOfWork; 2 | 3 | public interface IEventPublisher 4 | { 5 | void PublishEvent(EntityEvent @event); 6 | } 7 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/UnitOfWork/IGetUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace Uow.Postgresql.Database.UnitOfWork; 4 | 5 | public interface IGetConnection 6 | { 7 | IDbConnection GetConnection(); 8 | } 9 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Application/Entity.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.EntityFramework.Example.Application; 2 | 3 | public class Entity 4 | { 5 | public int? Id { get; set; } 6 | public int Value { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/UnitOfWork/ITransactionalEventPublisher.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.SqliteWithEvents.Example.UnitOfWork; 2 | 3 | public interface ITransactionalEventPublisher : IEventPublisher 4 | { 5 | void Commit(); 6 | } 7 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Application/IEntityRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.EntityFramework.Example.Application; 2 | 3 | public interface IEntityRepository 4 | { 5 | int Create(int value); 6 | Entity? GetOrDefault(int id); 7 | } 8 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/UnitOfWork/IGetConnection.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace Uow.Mssql.Database.UnitOfWork; 4 | 5 | public interface IGetConnection 6 | { 7 | (IDbConnection connection, IDbTransaction transaction) GetConnection(); 8 | } 9 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/UnitOfWork/ITransactionalEventPublisherFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.SqliteWithEvents.Example.UnitOfWork; 2 | 3 | public interface ITransactionalEventPublisherFactory 4 | { 5 | ITransactionalEventPublisher Create(); 6 | } 7 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Tests/Infrastructure/DatabaseCollection.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Uow.Mssql.Tests.Infrastructure; 4 | 5 | [CollectionDefinition("DatabaseTest")] 6 | public class DatabaseCollection : ICollectionFixture 7 | { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Application/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uow.Sqlite.Example.Application; 4 | 5 | public interface IUnitOfWork : IDisposable 6 | { 7 | void RollBack(); 8 | void Commit(); 9 | bool IsDisposed { get; } 10 | } 11 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Tests/Infrastructure/DatabaseCollection.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Uow.Postgresql.Tests.Infrastructure; 4 | 5 | [CollectionDefinition("DatabaseTest")] 6 | public class DatabaseCollection : ICollectionFixture 7 | { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/UnitOfWork/IGetConnection.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace Uow.SqliteWithEvents.Example.UnitOfWork; 4 | 5 | public interface IGetConnection 6 | { 7 | IDbConnection GetConnection(); 8 | IEventPublisher GetEventPublisher(); 9 | } 10 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/UnitOfWork/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uow.SqliteWithEvents.Example.UnitOfWork; 4 | 5 | public interface IUnitOfWork : IDisposable 6 | { 7 | void RollBack(); 8 | void Commit(); 9 | bool IsDisposed { get; } 10 | } 11 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Application/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Uow.EntityFramework.Example.Application; 4 | 5 | public interface IUnitOfWork : IDisposable 6 | { 7 | void RollBack(); 8 | void Commit(); 9 | bool IsDisposed { get; } 10 | } 11 | -------------------------------------------------------------------------------- /sql-server-container/setup-database.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo 'Waiting for sql server to start (60s)' 3 | sleep 60s 4 | 5 | echo 'Initializing database' 6 | ./opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Password1! -d master -i initialize-database.sql 7 | 8 | echo 'Finished initializing database' 9 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Tests/Infrastructure/DatabaseCollection.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Uow.EntityFramework.Tests.Infrastructure; 4 | 5 | [CollectionDefinition("DatabaseTest")] 6 | public class DatabaseCollection : ICollectionFixture 7 | { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/SqlSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Mssql.Database; 2 | 3 | public class SqlSettings 4 | { 5 | public string ConnectionString { get; set; } 6 | 7 | public SqlSettings(string connectionString) 8 | { 9 | ConnectionString = connectionString; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Migrator/SqlSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Mssql.Migrator; 2 | 3 | public class SqlSettings 4 | { 5 | public string ConnectionString { get; set; } 6 | 7 | public SqlSettings(string connectionString) 8 | { 9 | ConnectionString = connectionString; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/UnitOfWork/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Uow.Mssql.Database.UnitOfWork; 5 | 6 | public interface IUnitOfWork : IDisposable 7 | { 8 | Task RollBackAsync(); 9 | Task CommitAsync(); 10 | bool IsDisposed { get; } 11 | } 12 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/SqlSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Postgresql.Database; 2 | 3 | public class SqlSettings 4 | { 5 | public string ConnectionString { get; set; } 6 | 7 | public SqlSettings(string connectionString) 8 | { 9 | ConnectionString = connectionString; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Migrator/SqlSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Postgresql.Migrator; 2 | 3 | public class SqlSettings 4 | { 5 | public string ConnectionString { get; set; } 6 | 7 | public SqlSettings(string connectionString) 8 | { 9 | ConnectionString = connectionString; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/UnitOfWork/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Uow.Postgresql.Database.UnitOfWork; 5 | 6 | public interface IUnitOfWork : IDisposable 7 | { 8 | Task RollBackAsync(); 9 | Task CommitAsync(); 10 | bool IsDisposed { get; } 11 | } 12 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Migrator/SqlSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.EntityFramework.Migrator; 2 | 3 | public class SqlSettings 4 | { 5 | public string ConnectionString { get; set; } 6 | 7 | public SqlSettings(string connectionString) 8 | { 9 | ConnectionString = connectionString; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Migrations/migrations.md: -------------------------------------------------------------------------------- 1 | How to create these Migrations: 2 | 3 | 1) Make your changes to the code (to the DBContext) 4 | 5 | 2) Call `dotnet ef` to generate SQL from the EF Migrations. 6 | 7 | dotnet ef migrations add NameOfTheNewMigration 8 | 9 | 3) No go and follow the instructions in the *Uow.EntityFramework.Migrator* project. 10 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Migrator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SqlSettings": { 3 | "ConnectionString": "server=localhost,11433;database=mydatabasedb;user id=sa;password=Password1!" 4 | }, 5 | "Serilog": { 6 | "MinimumLevel": { 7 | "Default": "Information", 8 | "Override": { 9 | "Microsoft": "Warning", 10 | "System": "Warning" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Migrator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SqlSettings": { 3 | "ConnectionString": "server=localhost,11433;database=mydatabasedb;user id=sa;password=Password1!" 4 | }, 5 | "Serilog": { 6 | "MinimumLevel": { 7 | "Default": "Information", 8 | "Override": { 9 | "Microsoft": "Warning", 10 | "System": "Warning" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Migrator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SqlSettings": { 3 | "ConnectionString": "host=localhost;port=15432;database=mypostgresqldb;user id=postgresqluser;password=postgresqluserpassword" 4 | }, 5 | "Serilog": { 6 | "MinimumLevel": { 7 | "Default": "Information", 8 | "Override": { 9 | "Microsoft": "Warning", 10 | "System": "Warning" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Storage/ExampleDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Uow.EntityFramework.Example.Application; 3 | 4 | namespace Uow.EntityFramework.Example.Storage; 5 | 6 | public class ExampleDbContext : DbContext 7 | { 8 | public ExampleDbContext(DbContextOptions options) : base(options) 9 | { 10 | } 11 | 12 | public DbSet Entities => Set(); 13 | } 14 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Uow.Sqlite.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/Uow.Mssql.Database.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/Uow.Postgresql.Database.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/Uow.SqliteWithEvents.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docker-compose.dcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.1 5 | Linux 6 | d2c0e2b3-e223-41bf-8b23-6b1487f598fa 7 | 8 | 9 | 10 | docker-compose.yml 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Tests/Infrastructure/RepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Uow.Sqlite.Example.Application; 3 | 4 | namespace Uow.Sqlite.Tests.Infrastructure; 5 | 6 | public class RepositoryTest : IDisposable 7 | { 8 | protected readonly DatabaseFixture Fixture = new(); 9 | private readonly IUnitOfWork _unitOfWork; 10 | 11 | public RepositoryTest() 12 | { 13 | _unitOfWork = Fixture.CreateUnitOfWork.Create(); 14 | } 15 | 16 | public void Dispose() 17 | { 18 | _unitOfWork.RollBack(); 19 | _unitOfWork?.Dispose(); 20 | Fixture?.Dispose(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Migrator/Migrations/Migration001_CreateEntityTable.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations; 2 | 3 | namespace Uow.Mssql.Migrator.Migrations; 4 | 5 | [Migration(1, "CreateEntityTable")] 6 | public class Migration001_CreateEntityTable : Migration 7 | { 8 | protected override void Up() 9 | { 10 | Execute(@" 11 | CREATE TABLE [dbo].[Entity]( 12 | [Id] [int] IDENTITY PRIMARY KEY, 13 | [Value] [int] NOT NULL, 14 | );"); 15 | } 16 | 17 | protected override void Down() 18 | { 19 | Execute(@"DROP TABLE [dbo].[Entity];"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Migrator/Migrations/Migration001_CreateEntityTable.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations; 2 | 3 | namespace Uow.Postgresql.Migrator.Migrations; 4 | 5 | [Migration(1, "CreateEntityTable")] 6 | public class Migration001_CreateEntityTable : Migration 7 | { 8 | protected override void Up() 9 | { 10 | Execute(@" 11 | CREATE TABLE public.Entity( 12 | Id serial primary key, 13 | Value integer not null 14 | );"); 15 | } 16 | 17 | protected override void Down() 18 | { 19 | Execute(@"DROP TABLE public.Entity;"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Tests/Infrastructure/RepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Uow.SqliteWithEvents.Example.UnitOfWork; 3 | 4 | namespace Uow.SqliteWithEvents.Tests.Infrastructure; 5 | 6 | public class RepositoryTest : IDisposable 7 | { 8 | protected readonly DatabaseFixture Fixture = new(); 9 | private readonly IUnitOfWork _unitOfWork; 10 | 11 | public RepositoryTest() 12 | { 13 | _unitOfWork = Fixture.CreateUnitOfWork.Create(); 14 | } 15 | 16 | public void Dispose() 17 | { 18 | _unitOfWork.RollBack(); 19 | _unitOfWork?.Dispose(); 20 | Fixture?.Dispose(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Migrator/Migrations/Migration001_CreateEntityTable.cs: -------------------------------------------------------------------------------- 1 | using SimpleMigrations; 2 | 3 | namespace Uow.EntityFramework.Migrator.Migrations; 4 | 5 | [Migration(1, "CreateEntityTable")] 6 | public class Migration001_CreateEntityTable : Migration 7 | { 8 | protected override void Up() 9 | { 10 | Execute(@" 11 | CREATE TABLE [Entities] ( 12 | [Id] int NOT NULL IDENTITY, 13 | [Value] int NOT NULL, 14 | CONSTRAINT [PK_Entities] PRIMARY KEY ([Id]) 15 | ); 16 | "); 17 | } 18 | 19 | protected override void Down() 20 | { 21 | Execute(@" 22 | DROP TABLE [Entities]; 23 | "); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sql-server-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/mssql/server:latest 2 | 3 | USER root 4 | RUN apt-get update && apt-get install -y dos2unix 5 | USER mssql 6 | 7 | ENV ACCEPT_EULA=Y 8 | ENV SA_PASSWORD=Password1! 9 | 10 | COPY initialize-database.sql initialize-database.sql 11 | COPY setup-database.sh setup-database.sh 12 | COPY entrypoint.sh entrypoint.sh 13 | 14 | USER root 15 | # convert crlf -> lf 16 | RUN dos2unix ./initialize-database.sql && dos2unix ./setup-database.sh && dos2unix ./entrypoint.sh 17 | # remove dos2unix and cleanup apt-get files 18 | RUN apt-get --purge remove -y dos2unix && rm -rf /var/lib/apt/lists/ 19 | USER mssql 20 | 21 | CMD /bin/bash ./entrypoint.sh 22 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Tests/Infrastructure/RepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Uow.Mssql.Database.UnitOfWork; 3 | using Xunit; 4 | 5 | namespace Uow.Mssql.Tests.Infrastructure; 6 | 7 | [Collection("DatabaseTest")] 8 | public class RepositoryTest : IDisposable 9 | { 10 | protected readonly DatabaseFixture Fixture; 11 | private readonly IUnitOfWork _unitOfWork; 12 | 13 | public RepositoryTest(DatabaseFixture fixture) 14 | { 15 | Fixture = fixture; 16 | _unitOfWork = fixture.CreateUnitOfWork(); 17 | } 18 | 19 | public void Dispose() 20 | { 21 | _unitOfWork.RollBackAsync().GetAwaiter().GetResult(); 22 | _unitOfWork?.Dispose(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Tests/Infrastructure/RepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Uow.EntityFramework.Example.Application; 3 | using Xunit; 4 | 5 | namespace Uow.EntityFramework.Tests.Infrastructure; 6 | 7 | [Collection("DatabaseTest")] 8 | public class RepositoryTest : IDisposable 9 | { 10 | protected readonly DatabaseFixture Fixture; 11 | private readonly IUnitOfWork _unitOfWork; 12 | 13 | public RepositoryTest(DatabaseFixture fixture) 14 | { 15 | Fixture = fixture; 16 | _unitOfWork = fixture.CreateUnitOfWork.Create(); 17 | } 18 | 19 | public void Dispose() 20 | { 21 | _unitOfWork.RollBack(); 22 | _unitOfWork?.Dispose(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Tests/Infrastructure/RepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Uow.Postgresql.Database.UnitOfWork; 3 | using Xunit; 4 | 5 | namespace Uow.Postgresql.Tests.Infrastructure; 6 | 7 | [Collection("DatabaseTest")] 8 | public class RepositoryTest : IDisposable 9 | { 10 | protected readonly DatabaseFixture Fixture; 11 | private readonly IUnitOfWork _unitOfWork; 12 | 13 | public RepositoryTest(DatabaseFixture fixture) 14 | { 15 | Fixture = fixture; 16 | _unitOfWork = fixture.CreateUnitOfWork(); 17 | } 18 | 19 | public void Dispose() 20 | { 21 | _unitOfWork.RollBackAsync().GetAwaiter().GetResult(); 22 | _unitOfWork?.Dispose(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Migrator/Migrations/migrations.md: -------------------------------------------------------------------------------- 1 | How to create these from EF Core Migrations: 2 | 3 | 1) In *Uow.EntityFramework.Example* 4 | 5 | \entity-framework-example\Uow.EntityFramework.Example\ 6 | 7 | Call `dotnet ef` to generate SQL from the EF Migrations. 8 | 9 | dotnet ef migrations script PreviousMigration NewMigration # Generate Up 10 | dotnet ef migrations script NewMigration PreviousMigration # Generate Down 11 | 12 | 2) Create a new migration in this project. 13 | Copy in the SQL from the previous step. 14 | But discard any changes to `[__EFMigrationsHistory]`. This project doesn't use EF Migrations in the database, only to generate SQL for *Simple.Migrations*. 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = crlf 9 | 10 | [*.{config,xml,js,json,html,css,sql,csproj,props,yml,proto}] 11 | indent_size = 2 12 | 13 | [*.cs] 14 | end_of_line = crlf 15 | # use var everywhere 16 | csharp_style_var_for_built_in_types = true:suggestion 17 | csharp_style_var_when_type_is_apparent = true:suggestion 18 | csharp_style_var_elsewhere = true:suggestion 19 | # use file_scoped namespaces 20 | csharp_style_namespace_declarations = file_scoped 21 | # Organize usings 22 | dotnet_sort_system_directives_first = true 23 | # Expression-level preferences 24 | dotnet_style_prefer_conditional_expression_over_return = false 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | sql-server: 5 | build: ./sql-server-container 6 | user: root 7 | expose: 8 | - 1433 9 | ports: 10 | - 11433:1433 11 | volumes: 12 | - sqlserverdata:/var/opt/mssql/data 13 | 14 | postgres: 15 | image: postgres 16 | expose: 17 | - 5432 18 | ports: 19 | - "15432:5432" 20 | volumes: 21 | - pgdata:/var/lib/postgresql/data:rw 22 | environment: 23 | POSTGRES_DB: mypostgresqldb 24 | POSTGRES_USER: postgresqluser 25 | POSTGRES_PASSWORD: postgresqluserpassword 26 | PGDATA: /var/lib/postgresql/data 27 | 28 | volumes: 29 | sqlserverdata: 30 | pgdata: 31 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Storage/EntityRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using Uow.Sqlite.Example.Application; 3 | 4 | namespace Uow.Sqlite.Example.Storage; 5 | 6 | public class EntityRepository : IEntityRepository 7 | { 8 | private readonly IGetConnection _getConnection; 9 | 10 | public EntityRepository(IGetConnection getConnection) 11 | { 12 | _getConnection = getConnection; 13 | } 14 | 15 | public int Create(int value) 16 | { 17 | return _getConnection.GetConnection().QuerySingle( 18 | @" 19 | insert into Entity (Value) values (@value); 20 | select last_insert_rowid(); 21 | ", new { value }); 22 | } 23 | 24 | public Entity? GetOrDefault(int id) 25 | { 26 | return _getConnection.GetConnection().QuerySingleOrDefault( 27 | @" 28 | select * from Entity where Id = @id 29 | ", new { id }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Tests/Infrastructure/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Uow.Sqlite.Example.Application; 3 | using Uow.Sqlite.Example.Storage; 4 | 5 | namespace Uow.Sqlite.Tests.Infrastructure; 6 | 7 | public class DatabaseFixture : IDisposable 8 | { 9 | private readonly TestDatabaseContext _testDatabaseContext; 10 | 11 | public ICreateUnitOfWork CreateUnitOfWork; 12 | public IGetConnection GetConnection { get; } 13 | 14 | public DatabaseFixture() 15 | { 16 | _testDatabaseContext = new TestDatabaseContext(); 17 | var connection = _testDatabaseContext.Connection; 18 | 19 | var unitOfWorkContext = new UnitOfWorkContext(connection); 20 | CreateUnitOfWork = unitOfWorkContext; 21 | GetConnection = unitOfWorkContext; 22 | } 23 | 24 | public void Dispose() 25 | { 26 | _testDatabaseContext.Dispose(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Tests/Infrastructure/TestDatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SQLite; 3 | using Dapper; 4 | 5 | namespace Uow.Sqlite.Tests.Infrastructure; 6 | 7 | public class TestDatabaseContext : IDisposable 8 | { 9 | public TestDatabaseContext() 10 | { 11 | Connection = InitializeTestDatabase(); 12 | } 13 | 14 | public SQLiteConnection Connection { get; private set; } 15 | 16 | public void Dispose() 17 | { 18 | Connection?.Dispose(); 19 | } 20 | 21 | private static SQLiteConnection InitializeTestDatabase() 22 | { 23 | var connection = new SQLiteConnection("Data Source=:memory:"); 24 | connection.Open(); 25 | _ = connection.Execute( 26 | @" 27 | create table Entity ( 28 | Id integer primary key, 29 | Value integer not null 30 | );"); 31 | return connection; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Tests/Infrastructure/TestDatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SQLite; 3 | using Dapper; 4 | 5 | namespace Uow.SqliteWithEvents.Tests.Infrastructure; 6 | 7 | public class TestDatabaseContext : IDisposable 8 | { 9 | public TestDatabaseContext() 10 | { 11 | Connection = InitializeTestDatabase(); 12 | } 13 | 14 | public SQLiteConnection Connection { get; private set; } 15 | 16 | public void Dispose() 17 | { 18 | Connection?.Dispose(); 19 | } 20 | 21 | private static SQLiteConnection InitializeTestDatabase() 22 | { 23 | var connection = new SQLiteConnection("Data Source=:memory:"); 24 | connection.Open(); 25 | _ = connection.Execute( 26 | @" 27 | create table Entity ( 28 | Id integer primary key, 29 | Value integer not null 30 | );"); 31 | return connection; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Uow.EntityFramework.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Tests/EntityRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Shouldly; 3 | using Uow.Mssql.Database; 4 | using Uow.Mssql.Tests.Infrastructure; 5 | using Xunit; 6 | 7 | namespace Uow.Mssql.Tests; 8 | 9 | /** 10 | * This is example of test where Uow isn't important and were it's more expressive to hide it behind the scenes 11 | */ 12 | public class EntityRepositoryTest : RepositoryTest 13 | { 14 | private readonly EntityRepository _repository; 15 | 16 | public EntityRepositoryTest(DatabaseFixture fixture) : base(fixture) 17 | { 18 | _repository = new EntityRepository(fixture.GetConnection); 19 | } 20 | 21 | [Fact] 22 | public async Task Should_create_entity() 23 | { 24 | var value = 456; 25 | 26 | var id = await _repository.Create(value); 27 | 28 | var result = await _repository.GetOrDefault(id); 29 | result.Id.ShouldBe(id); 30 | result.Value.ShouldBe(value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Tests/EntityRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Shouldly; 3 | using Uow.Postgresql.Database; 4 | using Uow.Postgresql.Tests.Infrastructure; 5 | using Xunit; 6 | 7 | namespace Uow.Postgresql.Tests; 8 | 9 | /** 10 | * This is example of test where Uow isn't important and were it's more expressive to hide it behind the scenes 11 | */ 12 | public class EntityRepositoryTest : RepositoryTest 13 | { 14 | private readonly EntityRepository _repository; 15 | 16 | public EntityRepositoryTest(DatabaseFixture fixture) : base(fixture) 17 | { 18 | _repository = new EntityRepository(fixture.GetConnection); 19 | } 20 | 21 | [Fact] 22 | public async Task Should_create_entity() 23 | { 24 | var value = 456; 25 | 26 | var id = await _repository.Create(value); 27 | 28 | var result = await _repository.GetOrDefault(id); 29 | result.Id.ShouldBe(id); 30 | result.Value.ShouldBe(value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Storage/EntityRepository.cs: -------------------------------------------------------------------------------- 1 | using Uow.EntityFramework.Example.Application; 2 | 3 | namespace Uow.EntityFramework.Example.Storage; 4 | 5 | public class EntityRepository : IEntityRepository 6 | { 7 | private readonly IGetDbContext _getDbContext; 8 | 9 | public EntityRepository(IGetDbContext getDbContext) 10 | { 11 | _getDbContext = getDbContext; 12 | } 13 | 14 | public int Create(int value) 15 | { 16 | var entity = new Entity { Value = value }; 17 | 18 | var context = _getDbContext.GetDbContext(); 19 | 20 | _ = context.Add(entity); 21 | _ = context.SaveChanges(); 22 | 23 | return entity.Id!.Value; 24 | } 25 | 26 | public Entity? GetOrDefault(int id) 27 | { 28 | var context = _getDbContext.GetDbContext(); 29 | 30 | if (context.Entities.Find(id) is Entity entity) 31 | { 32 | return entity; 33 | } 34 | 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Storage/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.SQLite; 4 | using Uow.Sqlite.Example.Application; 5 | 6 | namespace Uow.Sqlite.Example.Storage; 7 | 8 | public class UnitOfWork : IUnitOfWork, IDisposable 9 | { 10 | private readonly SQLiteConnection _connection; 11 | private readonly SQLiteTransaction _transaction; 12 | 13 | public IDbConnection Connection => _connection; 14 | 15 | public bool IsDisposed { get; private set; } = false; 16 | 17 | public UnitOfWork(SQLiteConnection openConnection) 18 | { 19 | _connection = openConnection; 20 | _transaction = _connection.BeginTransaction(); 21 | } 22 | 23 | public void RollBack() 24 | { 25 | _transaction.Rollback(); 26 | } 27 | 28 | public void Commit() 29 | { 30 | _transaction.Commit(); 31 | } 32 | 33 | public void Dispose() 34 | { 35 | _transaction?.Dispose(); 36 | 37 | IsDisposed = true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Tests/EntityRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Uow.EntityFramework.Example.Storage; 3 | using Uow.EntityFramework.Tests.Infrastructure; 4 | using Xunit; 5 | 6 | namespace Uow.EntityFramework.Tests; 7 | 8 | /** 9 | * This is example of test where Uow isn't important and were it's more expressive to hide it behind the scenes 10 | */ 11 | public class EntityRepositoryTest : RepositoryTest 12 | { 13 | private readonly EntityRepository _repository; 14 | 15 | public EntityRepositoryTest(DatabaseFixture fixture) : base(fixture) 16 | { 17 | _repository = new EntityRepository(fixture.GetDbContext); 18 | } 19 | 20 | [Fact] 21 | public void Should_create_entity() 22 | { 23 | var value = 456; 24 | 25 | var id = _repository.Create(value); 26 | 27 | var result = _repository.GetOrDefault(id); 28 | _ = result.ShouldNotBeNull(); 29 | result.Id.ShouldBe(id); 30 | result.Value.ShouldBe(value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Tests/Fakes/FakeEventPublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Uow.SqliteWithEvents.Example; 3 | using Uow.SqliteWithEvents.Example.UnitOfWork; 4 | 5 | namespace Uow.SqliteWithEvents.Tests.Fakes; 6 | 7 | public class FakeEventPublisher : ITransactionalEventPublisher 8 | { 9 | public readonly List Staged = new(); 10 | public readonly List Committed = new(); 11 | 12 | public void PublishEvent(EntityEvent @event) 13 | { 14 | Staged.Add(@event); 15 | } 16 | 17 | public void Commit() 18 | { 19 | Committed.AddRange(Staged); 20 | Staged.Clear(); 21 | } 22 | } 23 | 24 | public class FakeTransactionalEventPublisherFactory : ITransactionalEventPublisherFactory 25 | { 26 | public FakeEventPublisher? CurrentEventPublisher; 27 | 28 | public ITransactionalEventPublisher Create() 29 | { 30 | CurrentEventPublisher = new FakeEventPublisher(); 31 | return CurrentEventPublisher; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/UnitOfWork/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Threading.Tasks; 4 | 5 | namespace Uow.Mssql.Database.UnitOfWork; 6 | 7 | public class UnitOfWork : IUnitOfWork, IDisposable 8 | { 9 | public SqlTransaction Transaction { get; } 10 | public SqlConnection Connection { get; } 11 | public bool IsDisposed { get; private set; } = false; 12 | 13 | public UnitOfWork(string connectionString) 14 | { 15 | Connection = new SqlConnection(connectionString); 16 | Connection.Open(); 17 | Transaction = Connection.BeginTransaction(); 18 | } 19 | 20 | public async Task RollBackAsync() 21 | { 22 | await Transaction.RollbackAsync(); 23 | } 24 | 25 | public async Task CommitAsync() 26 | { 27 | await Transaction.CommitAsync(); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | Transaction?.Dispose(); 33 | Connection?.Dispose(); 34 | 35 | IsDisposed = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Migrations/20230318093204_CreateEntityTable.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace Uow.EntityFramework.Example.Migrations; 4 | 5 | public partial class CreateEntityTable : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | _ = migrationBuilder.CreateTable( 10 | name: "Entities", 11 | columns: table => new 12 | { 13 | Id = table.Column(type: "int", nullable: false) 14 | .Annotation("SqlServer:Identity", "1, 1"), 15 | Value = table.Column(type: "int", nullable: false) 16 | }, 17 | constraints: table => 18 | { 19 | _ = table.PrimaryKey("PK_Entities", x => x.Id); 20 | }); 21 | } 22 | 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | _ = migrationBuilder.DropTable( 26 | name: "Entities"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/UnitOfWork/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Npgsql; 4 | 5 | namespace Uow.Postgresql.Database.UnitOfWork; 6 | 7 | public class UnitOfWork : IUnitOfWork, IDisposable 8 | { 9 | private readonly NpgsqlTransaction _transaction; 10 | public NpgsqlConnection Connection { get; } 11 | public bool IsDisposed { get; private set; } = false; 12 | 13 | public UnitOfWork(string connectionString) 14 | { 15 | Connection = new NpgsqlConnection(connectionString); 16 | Connection.Open(); 17 | _transaction = Connection.BeginTransaction(); 18 | } 19 | 20 | public async Task RollBackAsync() 21 | { 22 | await _transaction.RollbackAsync(); 23 | } 24 | 25 | public async Task CommitAsync() 26 | { 27 | await _transaction.CommitAsync(); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | _transaction?.Dispose(); 33 | Connection?.Dispose(); 34 | 35 | IsDisposed = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/EntityRepository.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using Uow.SqliteWithEvents.Database; 3 | using Uow.SqliteWithEvents.Example.UnitOfWork; 4 | 5 | namespace Uow.SqliteWithEvents.Example; 6 | 7 | public class EntityRepository 8 | { 9 | private readonly IGetConnection _getConnection; 10 | 11 | public EntityRepository(IGetConnection getConnection) 12 | { 13 | _getConnection = getConnection; 14 | } 15 | 16 | public int Create(int value) 17 | { 18 | var id = _getConnection.GetConnection().QuerySingle( 19 | @" 20 | insert into Entity (Value) values (@value); 21 | select last_insert_rowid(); 22 | ", new { value }); 23 | 24 | _getConnection.GetEventPublisher().PublishEvent(new EntityEvent { Id = id }); 25 | 26 | return id; 27 | } 28 | 29 | public Entity GetOrDefault(int id) 30 | { 31 | return _getConnection.GetConnection().QuerySingleOrDefault( 32 | @" 33 | select * from Entity where Id = @id 34 | ", new { id }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/EntityRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Dapper; 3 | using Uow.Postgresql.Database.UnitOfWork; 4 | 5 | namespace Uow.Postgresql.Database; 6 | 7 | public class EntityRepository 8 | { 9 | private readonly IGetConnection _getConnection; 10 | 11 | public EntityRepository(IGetConnection getConnection) 12 | { 13 | _getConnection = getConnection; 14 | } 15 | 16 | public async Task Create(int value) 17 | { 18 | var connection = _getConnection.GetConnection(); 19 | 20 | var query = @" 21 | insert into Entity (Value) values (@value) returning Id; 22 | "; 23 | 24 | return await connection.QuerySingleAsync(query, new { value }); 25 | } 26 | 27 | public async Task GetOrDefault(int id) 28 | { 29 | var connection = _getConnection.GetConnection(); 30 | 31 | var query = @" 32 | select * from Entity where Id = @id; 33 | "; 34 | 35 | return await connection.QuerySingleOrDefaultAsync(query, new { id }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Tests/EntityRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Uow.SqliteWithEvents.Example; 3 | using Uow.SqliteWithEvents.Tests.Infrastructure; 4 | using Xunit; 5 | 6 | namespace Uow.SqliteWithEvents.Tests; 7 | 8 | public class EntityRepositoryTest : RepositoryTest 9 | { 10 | private readonly EntityRepository _repository; 11 | 12 | public EntityRepositoryTest() 13 | { 14 | _repository = new EntityRepository(Fixture.GetConnection); 15 | } 16 | 17 | [Fact] 18 | public void Create_should_create_entity() 19 | { 20 | var value = 456; 21 | 22 | var id = _repository.Create(value); 23 | 24 | var result = _repository.GetOrDefault(id); 25 | result.Id.ShouldBe(id); 26 | result.Value.ShouldBe(value); 27 | } 28 | 29 | [Fact] 30 | public void Create_should_publish_event() 31 | { 32 | var id = _repository.Create(789); 33 | 34 | var @event = Fixture.CurrentEventPublisher!.Staged.ShouldHaveSingleItem(); 35 | @event.Id.ShouldBe(id); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Storage/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Storage; 4 | using Uow.EntityFramework.Example.Application; 5 | 6 | namespace Uow.EntityFramework.Example.Storage; 7 | 8 | public class UnitOfWork : IUnitOfWork, IDisposable 9 | { 10 | private readonly IDbContextTransaction _transaction; 11 | 12 | public ExampleDbContext DbContext { get; } 13 | 14 | public bool IsDisposed { get; private set; } = false; 15 | 16 | public UnitOfWork(IDbContextFactory dbContextFactory) 17 | { 18 | DbContext = dbContextFactory.CreateDbContext(); 19 | _transaction = DbContext.Database.BeginTransaction(); 20 | } 21 | 22 | public void RollBack() 23 | { 24 | _transaction.Rollback(); 25 | } 26 | 27 | public void Commit() 28 | { 29 | _transaction.Commit(); 30 | } 31 | 32 | public void Dispose() 33 | { 34 | _transaction?.Dispose(); 35 | DbContext?.Dispose(); 36 | 37 | IsDisposed = true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/EntityRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Dapper; 3 | using Uow.Mssql.Database.UnitOfWork; 4 | 5 | namespace Uow.Mssql.Database; 6 | 7 | public class EntityRepository 8 | { 9 | private readonly IGetConnection _getConnection; 10 | 11 | public EntityRepository(IGetConnection getConnection) 12 | { 13 | _getConnection = getConnection; 14 | } 15 | 16 | public async Task Create(int value) 17 | { 18 | var (connection, transaction) = _getConnection.GetConnection(); 19 | 20 | var query = @" 21 | insert into Entity (Value) values (@value); 22 | select SCOPE_IDENTITY(); 23 | "; 24 | 25 | return await connection.QuerySingleAsync(query, new { value }, transaction); 26 | } 27 | 28 | public async Task GetOrDefault(int id) 29 | { 30 | var (connection, transaction) = _getConnection.GetConnection(); 31 | 32 | var query = @" 33 | select * from Entity where Id = @id; 34 | "; 35 | 36 | return await connection.QuerySingleOrDefaultAsync(query, new { id }, transaction); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nathan Cooper 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/UnitOfWork/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SQLite; 3 | 4 | namespace Uow.SqliteWithEvents.Example.UnitOfWork; 5 | 6 | public class UnitOfWork : IUnitOfWork, IDisposable 7 | { 8 | private readonly ITransactionalEventPublisher _eventPublisher; 9 | private readonly SQLiteTransaction _transaction; 10 | 11 | public SQLiteConnection Connection { get; } 12 | public IEventPublisher EventPublisher => _eventPublisher; 13 | 14 | public bool IsDisposed { get; private set; } = false; 15 | 16 | public UnitOfWork(SQLiteConnection connection, ITransactionalEventPublisherFactory transactionalEventPublisherFactory) 17 | { 18 | Connection = connection; 19 | _transaction = Connection.BeginTransaction(); 20 | _eventPublisher = transactionalEventPublisherFactory.Create(); 21 | } 22 | 23 | public void RollBack() 24 | { 25 | _transaction.Rollback(); 26 | } 27 | 28 | public void Commit() 29 | { 30 | _transaction.Commit(); 31 | _eventPublisher.Commit(); 32 | } 33 | 34 | public void Dispose() 35 | { 36 | _transaction?.Dispose(); 37 | 38 | IsDisposed = true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Tests/Uow.Sqlite.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net7.0 4 | disable 5 | true 6 | false 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Tests/EntityRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Uow.Sqlite.Example.Storage; 3 | using Uow.Sqlite.Tests.Infrastructure; 4 | using Xunit; 5 | 6 | namespace Uow.Sqlite.Tests; 7 | 8 | public class EntityRepositoryTest : RepositoryTest 9 | { 10 | /** 11 | * This tests the repository, which is inside the unit of work. 12 | * It's entirely possible to wrap every test inside a transaction, but "RepositoryTest" does that by magic, 13 | * allowing one to write repository test on the real DB without worrying about unit of work 14 | * Additionally, "RepositoryTest" rolls back any changes, providing additional test isolation, which may be useful when using a non-in-memory database 15 | */ 16 | 17 | private readonly EntityRepository _repository; 18 | 19 | public EntityRepositoryTest() 20 | { 21 | _repository = new EntityRepository(Fixture.GetConnection); 22 | } 23 | 24 | [Fact] 25 | public void Create_should_create_entity() 26 | { 27 | var value = 456; 28 | 29 | var id = _repository.Create(value); 30 | 31 | var result = _repository.GetOrDefault(id); 32 | _ = result.ShouldNotBeNull(); 33 | result.Id.ShouldBe(id); 34 | result.Value.ShouldBe(value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Tests/Infrastructure/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Uow.SqliteWithEvents.Example.UnitOfWork; 3 | using Uow.SqliteWithEvents.Tests.Fakes; 4 | 5 | namespace Uow.SqliteWithEvents.Tests.Infrastructure; 6 | 7 | public class DatabaseFixture : IDisposable 8 | { 9 | private readonly TestDatabaseContext _testDatabaseContext; 10 | private readonly FakeTransactionalEventPublisherFactory _fakeTransactionalEventPublisherFactory; 11 | 12 | public ICreateUnitOfWork CreateUnitOfWork; 13 | public IGetConnection GetConnection { get; } 14 | public FakeEventPublisher? CurrentEventPublisher => _fakeTransactionalEventPublisherFactory.CurrentEventPublisher; 15 | 16 | public DatabaseFixture() 17 | { 18 | _testDatabaseContext = new TestDatabaseContext(); 19 | var connection = _testDatabaseContext.Connection; 20 | 21 | _fakeTransactionalEventPublisherFactory = new FakeTransactionalEventPublisherFactory(); 22 | var unitOfWorkContext = new UnitOfWorkContext(connection, _fakeTransactionalEventPublisherFactory); 23 | CreateUnitOfWork = unitOfWorkContext; 24 | GetConnection = unitOfWorkContext; 25 | } 26 | 27 | public void Dispose() 28 | { 29 | _testDatabaseContext.Dispose(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Tests/Uow.SqliteWithEvents.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net7.0 4 | disable 5 | true 6 | false 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Database/UnitOfWork/UnitOfWorkContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Uow.Postgresql.Database.UnitOfWork; 5 | 6 | public class UnitOfWorkContext : ICreateUnitOfWork, IGetConnection 7 | { 8 | private readonly string _connectionString; 9 | private UnitOfWork? _unitOfWork; 10 | 11 | private bool IsUnitOfWorkOpen => !(_unitOfWork == null || _unitOfWork.IsDisposed); 12 | 13 | public UnitOfWorkContext(SqlSettings sqlSettings) 14 | { 15 | _connectionString = sqlSettings.ConnectionString; 16 | } 17 | 18 | public IDbConnection GetConnection() 19 | { 20 | if (!IsUnitOfWorkOpen) 21 | { 22 | throw new InvalidOperationException( 23 | "There is not current unit of work from which to get a connection. Call Create first"); 24 | } 25 | 26 | return _unitOfWork!.Connection; 27 | } 28 | 29 | public IUnitOfWork Create() 30 | { 31 | if (IsUnitOfWorkOpen) 32 | { 33 | throw new InvalidOperationException( 34 | "Cannot begin a transaction before the unit of work from the last one is disposed"); 35 | } 36 | 37 | _unitOfWork = new UnitOfWork(_connectionString); 38 | return _unitOfWork; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Storage/UnitOfWorkContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.SQLite; 4 | using Uow.Sqlite.Example.Application; 5 | 6 | namespace Uow.Sqlite.Example.Storage; 7 | 8 | public class UnitOfWorkContext : ICreateUnitOfWork, IGetConnection 9 | { 10 | private readonly SQLiteConnection _connection; 11 | private UnitOfWork? _unitOfWork; 12 | 13 | private bool IsUnitOfWorkOpen => !(_unitOfWork == null || _unitOfWork.IsDisposed); 14 | 15 | public UnitOfWorkContext(SQLiteConnection connection) 16 | { 17 | _connection = connection; 18 | } 19 | 20 | public IDbConnection GetConnection() 21 | { 22 | if (!IsUnitOfWorkOpen) 23 | { 24 | throw new InvalidOperationException( 25 | "There is not current unit of work from which to get a connection. Call Create first"); 26 | } 27 | 28 | return _unitOfWork!.Connection; 29 | } 30 | 31 | public IUnitOfWork Create() 32 | { 33 | if (IsUnitOfWorkOpen) 34 | { 35 | throw new InvalidOperationException( 36 | "Cannot begin a transaction before the unit of work from the last one is disposed"); 37 | } 38 | 39 | _unitOfWork = new UnitOfWork(_connection); 40 | return _unitOfWork; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Tests/Infrastructure/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using Uow.Postgresql.Database; 4 | using Uow.Postgresql.Database.UnitOfWork; 5 | 6 | namespace Uow.Postgresql.Tests.Infrastructure; 7 | 8 | public class DatabaseFixture : IDisposable 9 | { 10 | private readonly ICreateUnitOfWork _createUnitOfWork; 11 | private readonly TestDatabaseContext _testDatabaseContext; 12 | 13 | public IGetConnection GetConnection { get; } 14 | 15 | public DatabaseFixture() 16 | { 17 | _testDatabaseContext = new TestDatabaseContext(); 18 | _testDatabaseContext.InitializeTestDatabase(); 19 | var connectionString = _testDatabaseContext.ConnectionString!; 20 | 21 | var sqlSettings = new SqlSettings(connectionString); 22 | 23 | var unitOfWorkContext = new UnitOfWorkContext(sqlSettings); 24 | _createUnitOfWork = unitOfWorkContext; 25 | GetConnection = unitOfWorkContext; 26 | } 27 | 28 | public IDbConnection GetCurrentConnection() 29 | { 30 | return GetConnection.GetConnection(); 31 | } 32 | 33 | public IUnitOfWork CreateUnitOfWork() 34 | { 35 | return _createUnitOfWork.Create(); 36 | } 37 | 38 | public void Dispose() 39 | { 40 | _testDatabaseContext.Dispose(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Tests/Infrastructure/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Uow.EntityFramework.Example.Application; 5 | using Uow.EntityFramework.Example.Storage; 6 | 7 | namespace Uow.EntityFramework.Tests.Infrastructure; 8 | 9 | public class DatabaseFixture : IDisposable 10 | { 11 | private readonly TestDatabaseContext _testDatabaseContext; 12 | 13 | public ICreateUnitOfWork CreateUnitOfWork { get; } 14 | public IGetDbContext GetDbContext { get; } 15 | 16 | public DatabaseFixture() 17 | { 18 | _testDatabaseContext = new TestDatabaseContext(); 19 | _testDatabaseContext.InitializeTestDatabase(); 20 | var connectionString = _testDatabaseContext.ConnectionString!; 21 | 22 | var options = new DbContextOptionsBuilder() 23 | .UseSqlServer(connectionString); 24 | var dbContextFactory = new PooledDbContextFactory(options.Options); 25 | 26 | var unitOfWorkContext = new UnitOfWorkContext(dbContextFactory); 27 | 28 | CreateUnitOfWork = unitOfWorkContext; 29 | GetDbContext = unitOfWorkContext; 30 | } 31 | 32 | public void Dispose() 33 | { 34 | _testDatabaseContext.Dispose(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Database/UnitOfWork/UnitOfWorkContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Uow.Mssql.Database.UnitOfWork; 5 | 6 | public class UnitOfWorkContext : ICreateUnitOfWork, IGetConnection 7 | { 8 | private readonly string _connectionString; 9 | private UnitOfWork? _unitOfWork; 10 | 11 | private bool IsUnitOfWorkOpen => !(_unitOfWork == null || _unitOfWork.IsDisposed); 12 | 13 | public UnitOfWorkContext(SqlSettings sqlSettings) 14 | { 15 | _connectionString = sqlSettings.ConnectionString; 16 | } 17 | 18 | public (IDbConnection connection, IDbTransaction transaction) GetConnection() 19 | { 20 | if (!IsUnitOfWorkOpen) 21 | { 22 | throw new InvalidOperationException( 23 | "There is not current unit of work from which to get a connection. Call Create first"); 24 | } 25 | 26 | return (_unitOfWork!.Connection, _unitOfWork.Transaction); 27 | } 28 | 29 | public IUnitOfWork Create() 30 | { 31 | if (IsUnitOfWorkOpen) 32 | { 33 | throw new InvalidOperationException( 34 | "Cannot begin a transaction before the unit of work from the last one is disposed"); 35 | } 36 | 37 | _unitOfWork = new UnitOfWork(_connectionString); 38 | return _unitOfWork; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Tests/Infrastructure/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using Uow.Mssql.Database; 4 | using Uow.Mssql.Database.UnitOfWork; 5 | 6 | namespace Uow.Mssql.Tests.Infrastructure; 7 | 8 | public class DatabaseFixture : IDisposable 9 | { 10 | private readonly ICreateUnitOfWork _createUnitOfWork; 11 | private readonly TestDatabaseContext _testDatabaseContext; 12 | 13 | public IGetConnection GetConnection { get; } 14 | 15 | public DatabaseFixture() 16 | { 17 | _testDatabaseContext = new TestDatabaseContext(); 18 | _testDatabaseContext.InitializeTestDatabase(); 19 | var connectionString = _testDatabaseContext.ConnectionString!; 20 | 21 | var sqlSettings = new SqlSettings(connectionString); 22 | 23 | var unitOfWorkContext = new UnitOfWorkContext(sqlSettings); 24 | _createUnitOfWork = unitOfWorkContext; 25 | GetConnection = unitOfWorkContext; 26 | } 27 | 28 | public (IDbConnection connection, IDbTransaction transaction) GetCurrentConnection() 29 | { 30 | return GetConnection.GetConnection(); 31 | } 32 | 33 | public IUnitOfWork CreateUnitOfWork() 34 | { 35 | return _createUnitOfWork.Create(); 36 | } 37 | 38 | public void Dispose() 39 | { 40 | _testDatabaseContext.Dispose(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Migrator/Uow.Mssql.Migrator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | disable 7 | true 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Migrator/Uow.Postgresql.Migrator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | disable 7 | true 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Migrator/Uow.EntityFramework.Migrator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | disable 7 | true 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Storage/UnitOfWorkContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Uow.EntityFramework.Example.Application; 4 | 5 | namespace Uow.EntityFramework.Example.Storage; 6 | 7 | public class UnitOfWorkContext : ICreateUnitOfWork, IGetDbContext 8 | { 9 | private readonly IDbContextFactory _dbContextFactory; 10 | private UnitOfWork? _unitOfWork; 11 | 12 | private bool IsUnitOfWorkOpen => !(_unitOfWork == null || _unitOfWork.IsDisposed); 13 | 14 | public UnitOfWorkContext(IDbContextFactory dbContextFactory) 15 | { 16 | _dbContextFactory = dbContextFactory; 17 | } 18 | 19 | public ExampleDbContext GetDbContext() 20 | { 21 | if (!IsUnitOfWorkOpen) 22 | { 23 | throw new InvalidOperationException( 24 | "There is not current unit of work from which to get a connection. Call Create first"); 25 | } 26 | 27 | return _unitOfWork!.DbContext; 28 | } 29 | 30 | public IUnitOfWork Create() 31 | { 32 | if (IsUnitOfWorkOpen) 33 | { 34 | throw new InvalidOperationException( 35 | "Cannot begin a transaction before the unit of work from the last one is disposed"); 36 | } 37 | 38 | _unitOfWork = new UnitOfWork(_dbContextFactory); 39 | return _unitOfWork; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Example/Application/Controller.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.Sqlite.Example.Application; 2 | 3 | public class Controller 4 | { 5 | private readonly IEntityRepository _entityRepository; 6 | private readonly ICreateUnitOfWork _createUnitOfWork; 7 | 8 | public Controller(IEntityRepository entityRepository, ICreateUnitOfWork createUnitOfWork) 9 | { 10 | _entityRepository = entityRepository; 11 | _createUnitOfWork = createUnitOfWork; 12 | } 13 | 14 | public Entity? GetOrDefault(int id) 15 | { 16 | using var uow = _createUnitOfWork.Create(); 17 | 18 | return _entityRepository.GetOrDefault(id); 19 | } 20 | 21 | public int CreateAndCommit(Entity entity) 22 | { 23 | using var uow = _createUnitOfWork.Create(); 24 | 25 | var id = _entityRepository.Create(entity.Value); 26 | uow.Commit(); 27 | 28 | return id; 29 | } 30 | 31 | public int CreateAndRollback(Entity entity) 32 | { 33 | using var uow = _createUnitOfWork.Create(); 34 | 35 | var id = _entityRepository.Create(entity.Value); 36 | uow.RollBack(); 37 | 38 | return id; 39 | } 40 | 41 | public int CreateAndNeitherCommitNorRollback(Entity entity) 42 | { 43 | using var uow = _createUnitOfWork.Create(); 44 | 45 | var id = _entityRepository.Create(entity.Value); 46 | 47 | return id; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Tests/Uow.Mssql.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | true 7 | false 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Application/Controller.cs: -------------------------------------------------------------------------------- 1 | namespace Uow.EntityFramework.Example.Application; 2 | 3 | public class Controller 4 | { 5 | private readonly IEntityRepository _entityRepository; 6 | private readonly ICreateUnitOfWork _createUnitOfWork; 7 | 8 | public Controller(IEntityRepository entityRepository, ICreateUnitOfWork createUnitOfWork) 9 | { 10 | _entityRepository = entityRepository; 11 | _createUnitOfWork = createUnitOfWork; 12 | } 13 | 14 | public Entity? GetOrDefault(int id) 15 | { 16 | using var uow = _createUnitOfWork.Create(); 17 | 18 | return _entityRepository.GetOrDefault(id); 19 | } 20 | 21 | public int CreateAndCommit(Entity entity) 22 | { 23 | using var uow = _createUnitOfWork.Create(); 24 | 25 | var id = _entityRepository.Create(entity.Value); 26 | uow.Commit(); 27 | 28 | return id; 29 | } 30 | 31 | public int CreateAndRollback(Entity entity) 32 | { 33 | using var uow = _createUnitOfWork.Create(); 34 | 35 | var id = _entityRepository.Create(entity.Value); 36 | uow.RollBack(); 37 | 38 | return id; 39 | } 40 | 41 | public int CreateAndNeitherCommitNorRollback(Entity entity) 42 | { 43 | using var uow = _createUnitOfWork.Create(); 44 | 45 | var id = _entityRepository.Create(entity.Value); 46 | 47 | return id; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Tests/UnitOfWorkTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Shouldly; 3 | using Uow.EntityFramework.Example.Application; 4 | using Uow.EntityFramework.Example.Storage; 5 | using Uow.EntityFramework.Tests.Infrastructure; 6 | using Xunit; 7 | 8 | namespace Uow.EntityFramework.Tests; 9 | 10 | public class UnitOfWorkTest : IClassFixture 11 | { 12 | private readonly DatabaseFixture _fixture; 13 | private readonly ICreateUnitOfWork _createUnitOfWork; 14 | private readonly EntityRepository _repository; 15 | 16 | public UnitOfWorkTest(DatabaseFixture fixture) 17 | { 18 | _fixture = fixture; 19 | _createUnitOfWork = _fixture.CreateUnitOfWork; 20 | _repository = new EntityRepository(_fixture.GetDbContext); 21 | } 22 | 23 | [Fact] 24 | public void Cannot_use_db_context_after_a_rollback() 25 | { 26 | int? createdId = null; 27 | using var uow = _createUnitOfWork.Create(); 28 | createdId = _repository.Create(1); 29 | uow.RollBack(); 30 | 31 | var attemptToContinueUsingUow = uow.Commit; 32 | _ = attemptToContinueUsingUow.ShouldThrow(); 33 | 34 | /* 35 | * The rolled back changes are still in the DbContext, but that's fine, because it's unusable now 36 | * If DbContext lives longer than the UOW, try doing DbContext.ChangeTracker.Clear() on rollback 37 | */ 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Tests/Uow.Postgresql.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | true 7 | false 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Migrations/ExampleDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using Uow.EntityFramework.Example.Storage; 8 | 9 | #nullable disable 10 | 11 | namespace Uow.EntityFramework.Example.Migrations 12 | { 13 | [DbContext(typeof(ExampleDbContext))] 14 | partial class ExampleDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "7.0.3") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 22 | 23 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 24 | 25 | modelBuilder.Entity("Uow.EntityFramework.Example.Application.Entity", b => 26 | { 27 | b.Property("Id") 28 | .ValueGeneratedOnAdd() 29 | .HasColumnType("int"); 30 | 31 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 32 | 33 | b.Property("Value") 34 | .HasColumnType("int"); 35 | 36 | b.HasKey("Id"); 37 | 38 | b.ToTable("Entities"); 39 | }); 40 | #pragma warning restore 612, 618 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Tests/Uow.EntityFramework.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | true 7 | false 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | all 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/MigrationToolHacks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Design; 4 | using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; 5 | using Microsoft.EntityFrameworkCore.Storage; 6 | using Uow.EntityFramework.Example.Storage; 7 | 8 | namespace Uow.EntityFramework.Example; 9 | 10 | /* 11 | * This class is just here to allow `dotnet ef migrations add [MigrationName]` to run on this class libary 12 | */ 13 | internal class MigrationToolDbContextFactory : IDesignTimeDbContextFactory 14 | { 15 | public ExampleDbContext CreateDbContext(string[] args) 16 | { 17 | var optionsBuilder = new DbContextOptionsBuilder() 18 | .UseSqlServer("thisiswheretheconnectionstringgoes") 19 | .ReplaceService(); 20 | 21 | return new ExampleDbContext(optionsBuilder.Options); 22 | } 23 | } 24 | 25 | /* 26 | * Remove the GO statements. 27 | * This has the advantage the generated SQL is actually SQL now, 28 | * but might cause trouble if the batch seperated are needed (but I think that's unlikely). 29 | */ 30 | 31 | #pragma warning disable EF1001 // Internal EF Core API usage. 32 | internal class NoGoSqlGenerationHelper : SqlServerSqlGenerationHelper 33 | { 34 | public NoGoSqlGenerationHelper(RelationalSqlGenerationHelperDependencies dependencies) 35 | : base(dependencies) 36 | { 37 | } 38 | 39 | // Avoids generating GO in scripts 40 | public override string BatchTerminator => Environment.NewLine; 41 | } 42 | #pragma warning restore EF1001 // Internal EF Core API usage. 43 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Example/Migrations/20230318093204_CreateEntityTable.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | using Uow.EntityFramework.Example.Storage; 9 | 10 | #nullable disable 11 | 12 | namespace Uow.EntityFramework.Example.Migrations 13 | { 14 | [DbContext(typeof(ExampleDbContext))] 15 | [Migration("20230318093204_CreateEntityTable")] 16 | partial class CreateEntityTable 17 | { 18 | /// 19 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 | { 21 | #pragma warning disable 612, 618 22 | modelBuilder 23 | .HasAnnotation("ProductVersion", "7.0.3") 24 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 25 | 26 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 27 | 28 | modelBuilder.Entity("Uow.EntityFramework.Example.Application.Entity", b => 29 | { 30 | b.Property("Id") 31 | .ValueGeneratedOnAdd() 32 | .HasColumnType("int"); 33 | 34 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 35 | 36 | b.Property("Value") 37 | .HasColumnType("int"); 38 | 39 | b.HasKey("Id"); 40 | 41 | b.ToTable("Entities"); 42 | }); 43 | #pragma warning restore 612, 618 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Example/UnitOfWork/UnitOfWorkContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.SQLite; 4 | 5 | namespace Uow.SqliteWithEvents.Example.UnitOfWork; 6 | 7 | public class UnitOfWorkContext : ICreateUnitOfWork, IGetConnection 8 | { 9 | private readonly ITransactionalEventPublisherFactory _transactionalEventPublisherFactory; 10 | private readonly SQLiteConnection _connection; 11 | private UnitOfWork? _unitOfWork; 12 | 13 | private bool IsUnitOfWorkOpen => !(_unitOfWork == null || _unitOfWork.IsDisposed); 14 | 15 | public UnitOfWorkContext(SQLiteConnection connection, ITransactionalEventPublisherFactory transactionalEventPublisherFactory) 16 | { 17 | _transactionalEventPublisherFactory = transactionalEventPublisherFactory; 18 | _connection = connection; 19 | } 20 | 21 | public IDbConnection GetConnection() 22 | { 23 | if (!IsUnitOfWorkOpen) 24 | { 25 | throw new InvalidOperationException( 26 | "There is not current unit of work from which to get a connection. Call Create first"); 27 | } 28 | 29 | return _unitOfWork!.Connection; 30 | } 31 | 32 | public IEventPublisher GetEventPublisher() 33 | { 34 | if (!IsUnitOfWorkOpen) 35 | { 36 | throw new InvalidOperationException( 37 | "There is not current unit of work from which to get a event publisher. Call Create first"); 38 | } 39 | 40 | return _unitOfWork!.EventPublisher; 41 | } 42 | 43 | public IUnitOfWork Create() 44 | { 45 | if (IsUnitOfWorkOpen) 46 | { 47 | throw new InvalidOperationException( 48 | "Cannot begin a transaction before the unit of work from the last one is disposed"); 49 | } 50 | 51 | _unitOfWork = new UnitOfWork(_connection, _transactionalEventPublisherFactory); 52 | return _unitOfWork; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /basic-example/Uow.Sqlite.Tests/ControllerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Shouldly; 3 | using Uow.Sqlite.Example.Application; 4 | using Uow.Sqlite.Example.Storage; 5 | using Uow.Sqlite.Tests.Infrastructure; 6 | using Xunit; 7 | 8 | namespace Uow.Sqlite.Tests; 9 | 10 | public class ControllerTest : IDisposable 11 | { 12 | /** 13 | * This tests "Controller", which uses unit of work 14 | * This, therefore, also test the Commit(), Rollback(), etc behaviour of unit of work 15 | */ 16 | 17 | private readonly DatabaseFixture _fixture = new(); 18 | private readonly Controller _sut; 19 | 20 | public ControllerTest() 21 | { 22 | var repository = new EntityRepository(_fixture.GetConnection); 23 | _sut = new Controller(repository, _fixture.CreateUnitOfWork); 24 | } 25 | 26 | [Fact] 27 | public void Creating_an_entity_and_commiting_saves_to_the_db() 28 | { 29 | var expectedEntity = new Entity { Id = null, Value = 10 }; 30 | 31 | var createdId = _sut.CreateAndCommit(expectedEntity); 32 | var createdEntity = _sut.GetOrDefault(createdId); 33 | 34 | _ = createdEntity.ShouldNotBeNull(); 35 | createdEntity.Value.ShouldBe(expectedEntity.Value); 36 | } 37 | 38 | [Fact] 39 | public void Creating_an_entity_and_rolling_back_saves_nothing() 40 | { 41 | var expectedEntity = new Entity { Id = null, Value = 10 }; 42 | 43 | var createdId = _sut.CreateAndRollback(expectedEntity); 44 | var createdEntity = _sut.GetOrDefault(createdId); 45 | 46 | createdEntity.ShouldBeNull(); 47 | } 48 | 49 | [Fact] 50 | public void Creating_an_entity_and_not_commiting_saves_nothing() 51 | { 52 | var expectedEntity = new Entity { Id = null, Value = 10 }; 53 | 54 | var createdId = _sut.CreateAndNeitherCommitNorRollback(expectedEntity); 55 | var createdEntity = _sut.GetOrDefault(createdId); 56 | 57 | createdEntity.ShouldBeNull(); 58 | } 59 | 60 | public void Dispose() 61 | { 62 | _fixture?.Dispose(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Tests/ControllerTest.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Uow.EntityFramework.Example.Application; 3 | using Uow.EntityFramework.Example.Storage; 4 | using Uow.EntityFramework.Tests.Infrastructure; 5 | using Xunit; 6 | 7 | namespace Uow.EntityFramework.Tests; 8 | 9 | public class ControllerTest : IClassFixture 10 | { 11 | /** 12 | * This tests "Controller", which uses unit of work 13 | * This, therefore, also test the Commit(), Rollback(), etc behaviour of unit of work 14 | */ 15 | 16 | private readonly DatabaseFixture _fixture; 17 | private readonly Controller _sut; 18 | 19 | public ControllerTest(DatabaseFixture fixture) 20 | { 21 | _fixture = fixture; 22 | var repository = new EntityRepository(_fixture.GetDbContext); 23 | _sut = new Controller(repository, _fixture.CreateUnitOfWork); 24 | } 25 | 26 | [Fact] 27 | public void Creating_an_entity_and_commiting_saves_to_the_db() 28 | { 29 | var expectedEntity = new Entity { Id = null, Value = 10 }; 30 | 31 | var createdId = _sut.CreateAndCommit(expectedEntity); 32 | var createdEntity = _sut.GetOrDefault(createdId); 33 | 34 | _ = createdEntity.ShouldNotBeNull(); 35 | createdEntity.Value.ShouldBe(expectedEntity.Value); 36 | } 37 | 38 | [Fact] 39 | public void Creating_an_entity_and_rolling_back_saves_nothing() 40 | { 41 | var expectedEntity = new Entity { Id = null, Value = 10 }; 42 | 43 | var createdId = _sut.CreateAndRollback(expectedEntity); 44 | var createdEntity = _sut.GetOrDefault(createdId); 45 | 46 | createdEntity.ShouldBeNull(); 47 | } 48 | 49 | [Fact] 50 | public void Creating_an_entity_and_not_commiting_saves_nothing() 51 | { 52 | var expectedEntity = new Entity { Id = null, Value = 10 }; 53 | 54 | var createdId = _sut.CreateAndNeitherCommitNorRollback(expectedEntity); 55 | var createdEntity = _sut.GetOrDefault(createdId); 56 | 57 | createdEntity.ShouldBeNull(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Tests/UnitOfWorkTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Shouldly; 3 | using Uow.Mssql.Database; 4 | using Uow.Mssql.Tests.Infrastructure; 5 | using Xunit; 6 | 7 | namespace Uow.Mssql.Tests; 8 | 9 | [Collection("DatabaseTest")] 10 | public class UnitOfWorkTest 11 | { 12 | private readonly DatabaseFixture _fixture; 13 | private readonly EntityRepository _repository; 14 | 15 | public UnitOfWorkTest(DatabaseFixture fixture) 16 | { 17 | _fixture = fixture; 18 | _repository = new EntityRepository(fixture.GetConnection); 19 | } 20 | 21 | [Fact] 22 | public async Task Creating_an_entity_and_commiting_saves_to_the_db() 23 | { 24 | var value = 567; 25 | int id; 26 | 27 | using (var uow = _fixture.CreateUnitOfWork()) 28 | { 29 | id = await _repository.Create(value); 30 | await uow.CommitAsync(); 31 | } 32 | 33 | using (_fixture.CreateUnitOfWork()) 34 | { 35 | var entity = await _repository.GetOrDefault(id); 36 | _ = entity.ShouldNotBeNull(); 37 | entity.Id.ShouldBe(id); 38 | entity.Value.ShouldBe(value); 39 | } 40 | } 41 | 42 | [Fact] 43 | public async Task Creating_an_entity_and_rolling_back_saves_nothing() 44 | { 45 | var value = 567; 46 | int id; 47 | 48 | using (var uow = _fixture.CreateUnitOfWork()) 49 | { 50 | id = await _repository.Create(value); 51 | await uow.RollBackAsync(); 52 | } 53 | 54 | using (_fixture.CreateUnitOfWork()) 55 | { 56 | var entity = await _repository.GetOrDefault(id); 57 | entity.ShouldBeNull(); 58 | } 59 | } 60 | 61 | [Fact] 62 | public async Task Creating_an_entity_and_not_commiting_saves_nothing() 63 | { 64 | var value = 567; 65 | int id; 66 | 67 | using (var uow = _fixture.CreateUnitOfWork()) 68 | { 69 | id = await _repository.Create(value); 70 | } 71 | 72 | using (_fixture.CreateUnitOfWork()) 73 | { 74 | var entity = await _repository.GetOrDefault(id); 75 | entity.ShouldBeNull(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Tests/UnitOfWorkTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Shouldly; 3 | using Uow.Postgresql.Database; 4 | using Uow.Postgresql.Tests.Infrastructure; 5 | using Xunit; 6 | 7 | namespace Uow.Postgresql.Tests; 8 | 9 | [Collection("DatabaseTest")] 10 | public class UnitOfWorkTest 11 | { 12 | private readonly DatabaseFixture _fixture; 13 | private readonly EntityRepository _repository; 14 | 15 | public UnitOfWorkTest(DatabaseFixture fixture) 16 | { 17 | _fixture = fixture; 18 | _repository = new EntityRepository(fixture.GetConnection); 19 | } 20 | 21 | [Fact] 22 | public async Task Creating_an_entity_and_commiting_saves_to_the_db() 23 | { 24 | var value = 567; 25 | int id; 26 | 27 | using (var uow = _fixture.CreateUnitOfWork()) 28 | { 29 | id = await _repository.Create(value); 30 | await uow.CommitAsync(); 31 | } 32 | 33 | using (_fixture.CreateUnitOfWork()) 34 | { 35 | var entity = await _repository.GetOrDefault(id); 36 | _ = entity.ShouldNotBeNull(); 37 | entity.Id.ShouldBe(id); 38 | entity.Value.ShouldBe(value); 39 | } 40 | } 41 | 42 | [Fact] 43 | public async Task Creating_an_entity_and_rolling_back_saves_nothing() 44 | { 45 | var value = 567; 46 | int id; 47 | 48 | using (var uow = _fixture.CreateUnitOfWork()) 49 | { 50 | id = await _repository.Create(value); 51 | await uow.RollBackAsync(); 52 | } 53 | 54 | using (_fixture.CreateUnitOfWork()) 55 | { 56 | var entity = await _repository.GetOrDefault(id); 57 | entity.ShouldBeNull(); 58 | } 59 | } 60 | 61 | [Fact] 62 | public async Task Creating_an_entity_and_not_commiting_saves_nothing() 63 | { 64 | var value = 567; 65 | int id; 66 | 67 | using (var uow = _fixture.CreateUnitOfWork()) 68 | { 69 | id = await _repository.Create(value); 70 | } 71 | 72 | using (_fixture.CreateUnitOfWork()) 73 | { 74 | var entity = await _repository.GetOrDefault(id); 75 | entity.ShouldBeNull(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | [Aa][Rr][Mm]/ 21 | [Aa][Rr][Mm]64/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | [Ll]ogs/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # .NET Core 37 | project.lock.json 38 | project.fragment.lock.json 39 | artifacts/ 40 | 41 | # Files built by Visual Studio 42 | *_i.c 43 | *_p.c 44 | *_h.h 45 | *.ilk 46 | *.meta 47 | *.obj 48 | *.iobj 49 | *.pch 50 | *.pdb 51 | *.ipdb 52 | *.pgc 53 | *.pgd 54 | *.rsp 55 | *.sbr 56 | *.tlb 57 | *.tli 58 | *.tlh 59 | *.tmp 60 | *.tmp_proj 61 | *_wpftmp.csproj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Visual Studio profiler 71 | *.psess 72 | *.vsp 73 | *.vspx 74 | *.sap 75 | 76 | # Visual Studio Trace Files 77 | *.e2e 78 | 79 | # ReSharper is a .NET coding add-in 80 | _ReSharper*/ 81 | *.[Rr]e[Ss]harper 82 | *.DotSettings.user 83 | 84 | # NuGet Packages 85 | *.nupkg 86 | # NuGet Symbol Packages 87 | *.snupkg 88 | # The packages folder can be ignored because of Package Restore 89 | **/[Pp]ackages/* 90 | # except build/, which is used as an MSBuild target. 91 | !**/[Pp]ackages/build/ 92 | # Uncomment if necessary however generally it will be regenerated when needed 93 | #!**/[Pp]ackages/repositories.config 94 | # NuGet v3's project.json files produces more ignorable files 95 | *.nuget.props 96 | *.nuget.targets 97 | 98 | # Visual Studio cache files 99 | # files ending in .cache can be ignored 100 | *.[Cc]ache 101 | # but keep track of directories ending in .cache 102 | !?*.[Cc]ache/ 103 | 104 | # Others 105 | ClientBin/ 106 | ~$* 107 | *~ 108 | *.dbmdl 109 | *.dbproj.schemaview 110 | *.jfm 111 | *.pfx 112 | *.publishsettings 113 | orleans.codegen.cs 114 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Tests/Infrastructure/TestDatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Reflection; 4 | using Dapper; 5 | using SimpleMigrations; 6 | using SimpleMigrations.DatabaseProvider; 7 | 8 | namespace Uow.Mssql.Tests.Infrastructure; 9 | 10 | public class TestDatabaseContext : IDisposable 11 | { 12 | private const string DataSource = "localhost,11433"; // todo for test in docker "sql-server,1433"; 13 | private readonly string _databaseName; 14 | 15 | public TestDatabaseContext() 16 | { 17 | _databaseName = $"uow-mssql-test{Guid.NewGuid()}"; 18 | } 19 | 20 | public void InitializeTestDatabase() 21 | { 22 | CreateTestDatabase(_databaseName); 23 | 24 | ConnectionString = GetSQLConnectionString(_databaseName); 25 | 26 | MigrateDatabase(ConnectionString); 27 | } 28 | 29 | public string? ConnectionString { get; private set; } 30 | 31 | private static void CreateTestDatabase(string databaseName) 32 | { 33 | var masterConnectionString = GetSQLConnectionString("master"); 34 | 35 | using var masterConnection = new SqlConnection(masterConnectionString); 36 | _ = masterConnection.Execute($"create database [{databaseName}]"); 37 | } 38 | 39 | private static void MigrateDatabase(string panelistCommunicationsConnectionString) 40 | { 41 | using var connection = new SqlConnection(panelistCommunicationsConnectionString); 42 | var databaseProvider = new MssqlDatabaseProvider(connection); 43 | var migrator = new SimpleMigrator(typeof(Migrator.Program).GetTypeInfo().Assembly, databaseProvider); 44 | migrator.Load(); 45 | migrator.MigrateToLatest(); 46 | } 47 | 48 | public void Dispose() 49 | { 50 | var masterConnectionString = GetSQLConnectionString("master"); 51 | using var masterConnection = new SqlConnection(masterConnectionString); 52 | 53 | _ = masterConnection.Execute($"alter database [{_databaseName}] set SINGLE_USER with rollback immediate"); 54 | _ = masterConnection.Execute($"drop database [{_databaseName}]"); 55 | } 56 | 57 | private static string GetSQLConnectionString(string database) 58 | { 59 | return new SqlConnectionStringBuilder 60 | { 61 | DataSource = DataSource, 62 | UserID = "sa", 63 | Password = "Password1!", 64 | InitialCatalog = database 65 | }.ToString(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Migrator/MigrationLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using SimpleMigrations; 4 | 5 | namespace Uow.Mssql.Migrator; 6 | 7 | public class MigrationLogger : SimpleMigrations.ILogger 8 | { 9 | /// 10 | /// Invoked when a sequence of migrations is started 11 | /// 12 | public void BeginSequence(MigrationData from, MigrationData to) 13 | { 14 | Log.Information($"Migrating from {from.Version}: {from.FullName} to {to.Version}: {to.FullName}"); 15 | } 16 | 17 | /// 18 | /// Invoked when a sequence of migrations is completed successfully 19 | /// 20 | public void EndSequence(MigrationData from, MigrationData to) 21 | { 22 | Log.Information("Done"); 23 | } 24 | 25 | /// 26 | /// Invoked when a sequence of migrations fails with an error 27 | /// 28 | public void EndSequenceWithError(Exception exception, MigrationData from, MigrationData currentVersion) 29 | { 30 | Log.Fatal("Database is currently on version {0}: {1}", currentVersion.Version, currentVersion.FullName); 31 | } 32 | 33 | // 34 | /// Invoked when an individual migration is started 35 | /// 36 | public void BeginMigration(MigrationData migration, MigrationDirection direction) 37 | { 38 | var term = direction == MigrationDirection.Up ? "migrating" : "reverting"; 39 | Log.Information($"{migration.Version}: {migration.FullName} {term}"); 40 | } 41 | 42 | /// 43 | /// Invoked when an individual migration is completed successfully 44 | /// 45 | public void EndMigration(MigrationData migration, MigrationDirection direction) 46 | { 47 | } 48 | 49 | /// 50 | /// Invoked when an individual migration fails with an error 51 | /// 52 | public void EndMigrationWithError(Exception exception, MigrationData migration, MigrationDirection direction) 53 | { 54 | Log.Fatal($"{migration.Version}: {migration.FullName} ERROR {exception.Message}"); 55 | } 56 | 57 | /// 58 | /// Invoked when another informative message should be logged 59 | /// 60 | public void Info(string message) 61 | { 62 | Log.Information(message); 63 | } 64 | 65 | /// 66 | /// Invoked when SQL being executed should be logged 67 | /// 68 | public void LogSql(string message) 69 | { 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Migrator/MigrationLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using SimpleMigrations; 4 | 5 | namespace Uow.Postgresql.Migrator; 6 | 7 | public class MigrationLogger : SimpleMigrations.ILogger 8 | { 9 | /// 10 | /// Invoked when a sequence of migrations is started 11 | /// 12 | public void BeginSequence(MigrationData from, MigrationData to) 13 | { 14 | Log.Information($"Migrating from {from.Version}: {from.FullName} to {to.Version}: {to.FullName}"); 15 | } 16 | 17 | /// 18 | /// Invoked when a sequence of migrations is completed successfully 19 | /// 20 | public void EndSequence(MigrationData from, MigrationData to) 21 | { 22 | Log.Information("Done"); 23 | } 24 | 25 | /// 26 | /// Invoked when a sequence of migrations fails with an error 27 | /// 28 | public void EndSequenceWithError(Exception exception, MigrationData from, MigrationData currentVersion) 29 | { 30 | Log.Fatal("Database is currently on version {0}: {1}", currentVersion.Version, currentVersion.FullName); 31 | } 32 | 33 | // 34 | /// Invoked when an individual migration is started 35 | /// 36 | public void BeginMigration(MigrationData migration, MigrationDirection direction) 37 | { 38 | var term = direction == MigrationDirection.Up ? "migrating" : "reverting"; 39 | Log.Information($"{migration.Version}: {migration.FullName} {term}"); 40 | } 41 | 42 | /// 43 | /// Invoked when an individual migration is completed successfully 44 | /// 45 | public void EndMigration(MigrationData migration, MigrationDirection direction) 46 | { 47 | } 48 | 49 | /// 50 | /// Invoked when an individual migration fails with an error 51 | /// 52 | public void EndMigrationWithError(Exception exception, MigrationData migration, MigrationDirection direction) 53 | { 54 | Log.Fatal($"{migration.Version}: {migration.FullName} ERROR {exception.Message}"); 55 | } 56 | 57 | /// 58 | /// Invoked when another informative message should be logged 59 | /// 60 | public void Info(string message) 61 | { 62 | Log.Information(message); 63 | } 64 | 65 | /// 66 | /// Invoked when SQL being executed should be logged 67 | /// 68 | public void LogSql(string message) 69 | { 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Migrator/MigrationLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using SimpleMigrations; 4 | 5 | namespace Uow.EntityFramework.Migrator; 6 | 7 | public class MigrationLogger : SimpleMigrations.ILogger 8 | { 9 | /// 10 | /// Invoked when a sequence of migrations is started 11 | /// 12 | public void BeginSequence(MigrationData from, MigrationData to) 13 | { 14 | Log.Information($"Migrating from {from.Version}: {from.FullName} to {to.Version}: {to.FullName}"); 15 | } 16 | 17 | /// 18 | /// Invoked when a sequence of migrations is completed successfully 19 | /// 20 | public void EndSequence(MigrationData from, MigrationData to) 21 | { 22 | Log.Information("Done"); 23 | } 24 | 25 | /// 26 | /// Invoked when a sequence of migrations fails with an error 27 | /// 28 | public void EndSequenceWithError(Exception exception, MigrationData from, MigrationData currentVersion) 29 | { 30 | Log.Fatal("Database is currently on version {0}: {1}", currentVersion.Version, currentVersion.FullName); 31 | } 32 | 33 | // 34 | /// Invoked when an individual migration is started 35 | /// 36 | public void BeginMigration(MigrationData migration, MigrationDirection direction) 37 | { 38 | var term = direction == MigrationDirection.Up ? "migrating" : "reverting"; 39 | Log.Information($"{migration.Version}: {migration.FullName} {term}"); 40 | } 41 | 42 | /// 43 | /// Invoked when an individual migration is completed successfully 44 | /// 45 | public void EndMigration(MigrationData migration, MigrationDirection direction) 46 | { 47 | } 48 | 49 | /// 50 | /// Invoked when an individual migration fails with an error 51 | /// 52 | public void EndMigrationWithError(Exception exception, MigrationData migration, MigrationDirection direction) 53 | { 54 | Log.Fatal($"{migration.Version}: {migration.FullName} ERROR {exception.Message}"); 55 | } 56 | 57 | /// 58 | /// Invoked when another informative message should be logged 59 | /// 60 | public void Info(string message) 61 | { 62 | Log.Information(message); 63 | } 64 | 65 | /// 66 | /// Invoked when SQL being executed should be logged 67 | /// 68 | public void LogSql(string message) 69 | { 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Tests/Infrastructure/TestDatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Reflection; 4 | using Dapper; 5 | using SimpleMigrations; 6 | using SimpleMigrations.DatabaseProvider; 7 | 8 | namespace Uow.EntityFramework.Tests.Infrastructure; 9 | 10 | public class TestDatabaseContext : IDisposable 11 | { 12 | private const string DataSource = "localhost,11433"; // todo for test in docker "sql-server,1433"; 13 | private readonly string _databaseName; 14 | 15 | public TestDatabaseContext() 16 | { 17 | _databaseName = $"uow-ef-test{Guid.NewGuid()}"; 18 | } 19 | 20 | public void InitializeTestDatabase() 21 | { 22 | CreateTestDatabase(_databaseName); 23 | 24 | ConnectionString = GetSQLConnectionString(_databaseName); 25 | 26 | MigrateDatabase(ConnectionString); 27 | } 28 | 29 | public string? ConnectionString { get; private set; } 30 | 31 | private static void CreateTestDatabase(string databaseName) 32 | { 33 | var masterConnectionString = GetSQLConnectionString("master"); 34 | 35 | using var masterConnection = new SqlConnection(masterConnectionString); 36 | _ = masterConnection.Execute($"create database [{databaseName}]"); 37 | } 38 | 39 | private static void MigrateDatabase(string panelistCommunicationsConnectionString) 40 | { 41 | using var connection = new SqlConnection(panelistCommunicationsConnectionString); 42 | var databaseProvider = new MssqlDatabaseProvider(connection); 43 | var migrator = new SimpleMigrator(typeof(Migrator.Program).GetTypeInfo().Assembly, databaseProvider); 44 | migrator.Load(); 45 | migrator.MigrateToLatest(); 46 | } 47 | 48 | public void Dispose() 49 | { 50 | var masterConnectionString = GetSQLConnectionString("master"); 51 | using var masterConnection = new SqlConnection(masterConnectionString); 52 | 53 | _ = masterConnection.Execute($"alter database [{_databaseName}] set SINGLE_USER with rollback immediate"); 54 | _ = masterConnection.Execute($"drop database [{_databaseName}]"); 55 | } 56 | 57 | private static string GetSQLConnectionString(string database) 58 | { 59 | return new SqlConnectionStringBuilder 60 | { 61 | DataSource = DataSource, 62 | UserID = "sa", 63 | Password = "Password1!", 64 | InitialCatalog = database, 65 | TrustServerCertificate = true, 66 | }.ToString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Tests/Infrastructure/TestDatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Dapper; 4 | using Npgsql; 5 | using SimpleMigrations; 6 | using SimpleMigrations.DatabaseProvider; 7 | 8 | namespace Uow.Postgresql.Tests.Infrastructure; 9 | 10 | public class TestDatabaseContext : IDisposable 11 | { 12 | private readonly string _databaseName; 13 | 14 | public TestDatabaseContext() 15 | { 16 | _databaseName = $"uow_postgresql_test{Guid.NewGuid()}".Replace("-", ""); 17 | } 18 | 19 | public void InitializeTestDatabase() 20 | { 21 | CreateTestDatabase(_databaseName); 22 | 23 | ConnectionString = GetConnectionString(_databaseName); 24 | 25 | MigrateDatabase(ConnectionString); 26 | } 27 | 28 | public string? ConnectionString { get; private set; } 29 | 30 | private static void CreateTestDatabase(string databaseName) 31 | { 32 | var defaultDbConnectionString = GetConnectionString("postgres"); 33 | 34 | using var defaultDbConnection = new NpgsqlConnection(defaultDbConnectionString); 35 | _ = defaultDbConnection.Execute($"create database {databaseName}"); 36 | } 37 | 38 | private static void MigrateDatabase(string panelistCommunicationsConnectionString) 39 | { 40 | using var connection = new NpgsqlConnection(panelistCommunicationsConnectionString); 41 | var databaseProvider = new PostgresqlDatabaseProvider(connection); 42 | var migrator = new SimpleMigrator(typeof(Migrator.Program).GetTypeInfo().Assembly, databaseProvider); 43 | migrator.Load(); 44 | migrator.MigrateToLatest(); 45 | } 46 | 47 | public void Dispose() 48 | { 49 | var defaultDbConnectionString = GetConnectionString("postgres"); 50 | using var defaultDbConnection = new NpgsqlConnection(defaultDbConnectionString); 51 | 52 | _ = defaultDbConnection.Execute($"SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '{_databaseName}' AND pid <> pg_backend_pid()"); 53 | _ = defaultDbConnection.Execute($"DROP DATABASE IF EXISTS {_databaseName}"); 54 | } 55 | 56 | private static string GetConnectionString(string database) 57 | { 58 | return new NpgsqlConnectionStringBuilder 59 | { 60 | Host = "localhost", // todo for test in docker "postgres" 61 | Port = 15432, // todo test for docker "5432" 62 | Username = "postgresqluser", 63 | Password = "postgresqluserpassword", 64 | Database = database 65 | }.ToString(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /mssql-example/Uow.Mssql.Migrator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Reflection; 4 | using Microsoft.Extensions.Configuration; 5 | using Serilog; 6 | using SimpleMigrations; 7 | using SimpleMigrations.DatabaseProvider; 8 | 9 | namespace Uow.Mssql.Migrator; 10 | 11 | public class Program 12 | { 13 | public static int Main(string[] args) 14 | { 15 | var configuration = Configure(); 16 | 17 | try 18 | { 19 | var sqlSettings = GetConfig(configuration); 20 | MigrateDatabase(sqlSettings); 21 | 22 | Log.Information("Exited without errors"); 23 | } 24 | catch (Exception ex) 25 | { 26 | Log.Fatal(ex, "Application terminated unexpectedly"); 27 | return 1; 28 | } 29 | finally 30 | { 31 | Log.CloseAndFlush(); 32 | } 33 | 34 | return 0; 35 | } 36 | 37 | private static void MigrateDatabase(SqlSettings settings) 38 | { 39 | var migrationsAssembly = typeof(Migrations.Migration001_CreateEntityTable).Assembly; 40 | using var connection = new SqlConnection(settings.ConnectionString); 41 | var databaseProvider = new MssqlDatabaseProvider(connection); 42 | 43 | var migrator = new SimpleMigrator(migrationsAssembly, databaseProvider) 44 | { 45 | Logger = new MigrationLogger() 46 | }; 47 | migrator.Load(); 48 | migrator.MigrateToLatest(); 49 | } 50 | 51 | private static IConfiguration Configure() 52 | { 53 | var configuration = new ConfigurationBuilder() 54 | .AddJsonFile("appsettings.json") 55 | .AddEnvironmentVariables() 56 | .Build(); 57 | 58 | Log.Logger = new LoggerConfiguration() 59 | .ReadFrom.Configuration(configuration) 60 | .Enrich.FromLogContext() 61 | .WriteTo.Console( 62 | outputTemplate: 63 | "[{Timestamp:HH:mm:ss} {SourceContext}:{Level:u3}] {Message:lj}{NewLine}{Exception}") 64 | .CreateLogger(); 65 | 66 | var assembly = Assembly.GetExecutingAssembly().GetName(); 67 | Log.Information("Logging Configured. Starting up {APPNAME} {VERSION}", assembly.Name, 68 | assembly.Version?.ToString()); 69 | 70 | return configuration; 71 | } 72 | 73 | private static T GetConfig(IConfiguration configuration) where T : class 74 | { 75 | var section = configuration.GetSection(typeof(T).Name); 76 | var config = section.Get(); 77 | if (config == null) 78 | { 79 | throw new ArgumentNullException(typeof(T).Name); 80 | } 81 | 82 | return config; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /postgresql-example/Uow.Postgresql.Migrator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Microsoft.Extensions.Configuration; 4 | using Npgsql; 5 | using Serilog; 6 | using SimpleMigrations; 7 | using SimpleMigrations.DatabaseProvider; 8 | 9 | namespace Uow.Postgresql.Migrator; 10 | 11 | public class Program 12 | { 13 | public static int Main(string[] args) 14 | { 15 | var configuration = Configure(); 16 | 17 | try 18 | { 19 | var sqlSettings = GetConfig(configuration); 20 | MigrateDatabase(sqlSettings); 21 | 22 | Log.Information("Exited without errors"); 23 | } 24 | catch (Exception ex) 25 | { 26 | Log.Fatal(ex, "Application terminated unexpectedly"); 27 | return 1; 28 | } 29 | finally 30 | { 31 | Log.CloseAndFlush(); 32 | } 33 | 34 | return 0; 35 | } 36 | 37 | private static void MigrateDatabase(SqlSettings settings) 38 | { 39 | var migrationsAssembly = typeof(Migrations.Migration001_CreateEntityTable).Assembly; 40 | using var connection = new NpgsqlConnection(settings.ConnectionString); 41 | var databaseProvider = new PostgresqlDatabaseProvider(connection); 42 | 43 | var migrator = new SimpleMigrator(migrationsAssembly, databaseProvider) 44 | { 45 | Logger = new MigrationLogger() 46 | }; 47 | migrator.Load(); 48 | migrator.MigrateToLatest(); 49 | } 50 | 51 | private static IConfiguration Configure() 52 | { 53 | var configuration = new ConfigurationBuilder() 54 | .AddJsonFile("appsettings.json") 55 | .AddEnvironmentVariables() 56 | .Build(); 57 | 58 | Log.Logger = new LoggerConfiguration() 59 | .ReadFrom.Configuration(configuration) 60 | .Enrich.FromLogContext() 61 | .WriteTo.Console( 62 | outputTemplate: 63 | "[{Timestamp:HH:mm:ss} {SourceContext}:{Level:u3}] {Message:lj}{NewLine}{Exception}") 64 | .CreateLogger(); 65 | 66 | var assembly = Assembly.GetExecutingAssembly().GetName(); 67 | Log.Information("Logging Configured. Starting up {APPNAME} {VERSION}", assembly.Name, 68 | assembly.Version?.ToString()); 69 | 70 | return configuration; 71 | } 72 | 73 | private static T GetConfig(IConfiguration configuration) where T : class 74 | { 75 | var section = configuration.GetSection(typeof(T).Name); 76 | var config = section.Get(); 77 | if (config == null) 78 | { 79 | throw new ArgumentNullException(typeof(T).Name); 80 | } 81 | 82 | return config; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /entity-framework-example/Uow.EntityFramework.Migrator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Reflection; 4 | using Microsoft.Extensions.Configuration; 5 | using Serilog; 6 | using SimpleMigrations; 7 | using SimpleMigrations.DatabaseProvider; 8 | 9 | namespace Uow.EntityFramework.Migrator; 10 | 11 | public class Program 12 | { 13 | public static int Main(string[] args) 14 | { 15 | var configuration = Configure(); 16 | 17 | try 18 | { 19 | var sqlSettings = GetConfig(configuration); 20 | MigrateDatabase(sqlSettings); 21 | 22 | Log.Information("Exited without errors"); 23 | } 24 | catch (Exception ex) 25 | { 26 | Log.Fatal(ex, "Application terminated unexpectedly"); 27 | return 1; 28 | } 29 | finally 30 | { 31 | Log.CloseAndFlush(); 32 | } 33 | 34 | return 0; 35 | } 36 | 37 | private static void MigrateDatabase(SqlSettings settings) 38 | { 39 | var migrationsAssembly = typeof(Migrations.Migration001_CreateEntityTable).Assembly; 40 | using var connection = new SqlConnection(settings.ConnectionString); 41 | var databaseProvider = new MssqlDatabaseProvider(connection); 42 | 43 | var migrator = new SimpleMigrator(migrationsAssembly, databaseProvider) 44 | { 45 | Logger = new MigrationLogger() 46 | }; 47 | migrator.Load(); 48 | migrator.MigrateToLatest(); 49 | } 50 | 51 | private static IConfiguration Configure() 52 | { 53 | var configuration = new ConfigurationBuilder() 54 | .AddJsonFile("appsettings.json") 55 | .AddEnvironmentVariables() 56 | .Build(); 57 | 58 | Log.Logger = new LoggerConfiguration() 59 | .ReadFrom.Configuration(configuration) 60 | .Enrich.FromLogContext() 61 | .WriteTo.Console( 62 | outputTemplate: 63 | "[{Timestamp:HH:mm:ss} {SourceContext}:{Level:u3}] {Message:lj}{NewLine}{Exception}") 64 | .CreateLogger(); 65 | 66 | var assembly = Assembly.GetExecutingAssembly().GetName(); 67 | Log.Information("Logging Configured. Starting up {APPNAME} {VERSION}", assembly.Name, 68 | assembly.Version?.ToString()); 69 | 70 | return configuration; 71 | } 72 | 73 | private static T GetConfig(IConfiguration configuration) where T : class 74 | { 75 | var section = configuration.GetSection(typeof(T).Name); 76 | var config = section.Get(); 77 | if (config == null) 78 | { 79 | throw new ArgumentNullException(typeof(T).Name); 80 | } 81 | 82 | return config; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /example-with-events/Uow.SqliteWithEvents.Tests/UnitOfWorkTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Shouldly; 3 | using Uow.SqliteWithEvents.Database; 4 | using Uow.SqliteWithEvents.Example; 5 | using Uow.SqliteWithEvents.Example.UnitOfWork; 6 | using Uow.SqliteWithEvents.Tests.Infrastructure; 7 | using Xunit; 8 | 9 | namespace Uow.SqliteWithEvents.Tests; 10 | 11 | public class UnitOfWorkTest : IDisposable 12 | { 13 | private readonly DatabaseFixture _fixture = new(); 14 | private readonly EntityRepository _repository; 15 | private readonly ICreateUnitOfWork _createUnitOfWork; 16 | 17 | public UnitOfWorkTest() 18 | { 19 | _repository = new EntityRepository(_fixture.GetConnection); 20 | _createUnitOfWork = _fixture.CreateUnitOfWork; 21 | } 22 | 23 | [Fact] 24 | public void Creating_an_entity_and_commiting_saves_to_the_db_and_publishes_an_event() 25 | { 26 | var expectedEntity = new Entity { Id = null, Value = 10 }; 27 | 28 | using (var uow = _createUnitOfWork.Create()) 29 | { 30 | expectedEntity.Id = _repository.Create(expectedEntity.Value); 31 | 32 | uow.Commit(); 33 | } 34 | 35 | var @event = _fixture.CurrentEventPublisher!.Committed.ShouldHaveSingleItem(); 36 | @event.Id.ShouldBe(expectedEntity.Id.Value); 37 | 38 | using (_createUnitOfWork.Create()) 39 | { 40 | var entity = _repository.GetOrDefault(expectedEntity.Id.Value); 41 | _ = entity.ShouldNotBeNull(); 42 | entity.Value.ShouldBe(expectedEntity.Value); 43 | } 44 | } 45 | 46 | [Fact] 47 | public void Creating_an_entity_and_rolling_back_saves_nothing_and_publishes_no_events() 48 | { 49 | var expectedEntity = new Entity { Id = null, Value = 10 }; 50 | 51 | using (var uow = _createUnitOfWork.Create()) 52 | { 53 | expectedEntity.Id = _repository.Create(expectedEntity.Value); 54 | 55 | uow.RollBack(); 56 | } 57 | 58 | _fixture.CurrentEventPublisher!.Committed.ShouldBeEmpty(); 59 | 60 | using (_createUnitOfWork.Create()) 61 | { 62 | var entity = _repository.GetOrDefault(expectedEntity.Id.Value); 63 | entity.ShouldBeNull(); 64 | } 65 | } 66 | 67 | [Fact] 68 | public void Creating_an_entity_and_not_commiting_saves_nothing_and_publishes_no_events() 69 | { 70 | var expectedEntity = new Entity { Id = null, Value = 10 }; 71 | 72 | using (var uow = _createUnitOfWork.Create()) 73 | { 74 | expectedEntity.Id = _repository.Create(expectedEntity.Value); 75 | } 76 | 77 | _fixture.CurrentEventPublisher!.Committed.ShouldBeEmpty(); 78 | 79 | using (_createUnitOfWork.Create()) 80 | { 81 | var entity = _repository.GetOrDefault(expectedEntity.Id.Value); 82 | entity.ShouldBeNull(); 83 | } 84 | } 85 | 86 | public void Dispose() 87 | { 88 | _fixture?.Dispose(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unit-of-work-example 2 | 3 | This is my preferred **Unit of Work** pattern. 4 | 5 | > Skip to [My Pattern](#my-pattern) to skip the essay and see the pattern 6 | 7 | > Skip to [Examples](#examples) 8 | 9 |
10 | 11 | # Background 12 | 13 | A unit of work is something that tracks the changes made during a business transaction, coordinates the writing of those changes and resolves concurrency problems. It allows the changes to be written or, in the event of failure not written, together as a single unit. 14 | 15 | Usually, I write my 'database code' in repositories and I use these repositories in my 'business code'. It's sometimes the case that I want to make multiple calls to multiple repositories and have those calls succeed or fail together. When that is the case, I use a unit of work pattern. 16 | 17 | This repositories contains examples of the pattern I use. All are written in C# and use relational databases, and most use [Dapper](https://github.com/DapperLib/Dapper). But the pattern is applicable to different technologies, languages and storage mechanisms. 18 | 19 | # Other patterns 20 | 21 | Before using this patterns, you should consider these other options: 22 | 23 | ## The best unit of work pattern is no pattern at all 24 | 25 | If you don't need to tie multiple repository methods together into transactions, then don't. Treating each method as an atomic operation is simplest and therefore best option. 26 | 27 | ORMs make it easy to save complex objects atomically and eliminate a lot of the need for transactions. It also could be the case, that when you find the need to tie disparate objects together with transactions, it might be better to re-consider your design rather than reaching for a unit of work pattern. 28 | 29 | ## Just use a DBContext 30 | 31 | In Entity Framework, this is very simple. Make changes on the `DBContext`, save them and you're done. 32 | 33 | public class MyEntityFrameworkDbContext : DbContext 34 | { 35 | ... // There is a transaction object in here somewhere 36 | // Your tables as collection objects: 37 | public DbSet Foos { get; set; } 38 | public DbSet Bars { get; set; } 39 | public void SaveChanges() => { ... } // commit transaction 40 | } 41 | 42 | // Just makes your changes and save 43 | _myEfDbContext.Foos.Add(foo); 44 | _myEfDbContext.Bars.Add(bar); 45 | _myEfDbContext.SaveChanges(); 46 | 47 | A similar thing is possible in Dapper. Just replace the DBSets with your repositories and write your own `SaveChanges` to manage the transaction. 48 | 49 | This simple, and if it works for you, it's a great option. My problem with this pattern is that it requires the caller to always have access to a container of all possible repositories. I think it's a bit of a God object. I prefer to be able to individually inject only the repositories needed into my business code, rather than having everything always just a "`.`" away. 50 | 51 |
52 | 53 | # My pattern 54 | 55 | For the external code calling the repository, the pattern looks like this: 56 | 57 | using (var uow = _createUnitOfWork.Create()) 58 | { 59 | // Do things inside the unit of work 60 | await _fooRepository.Create(foo); 61 | await _barRepository.Create(bar); 62 | 63 | await uow.Commit(); // Or maybe uow.Rollback() instead 64 | } 65 | 66 | // The unit of work is now finished 67 | 68 | That external code just needs to take the repositories it needs and also a reference to `ICreateUnitOfWork`. For example, here is the constructor for a class that uses `FooRepository` and `BarRepository`: 69 | 70 | public MyClass(ICreateUnitOfWork createUnitOfWork, FooRepository fooRepository, BarRepository barRepository) {...} 71 | 72 | There is a magic link between the repositories and the `ICreateUnitOfWork` that makes all this work. And the examples will show how that is done. 73 | 74 |
75 | 76 | # Examples 77 | 78 | ## 01: Basic Example using SQLite and Dapper 79 | 80 | Folder: *basic-example/* 81 | 82 | This is the most basic example. It uses Sqlite, an in-memory database, and Dapper. This should be your entry point for understanding this pattern. 83 | 84 | This code contains an Entity, a Repository, a Controller and the unit of work pattern. There are unit tests on both the Controller and the Repository. These tests can be run without any special setup. 85 | 86 | ### Notes: 87 | 88 | There is one strange quirk about how this example is written. In Sqlite the database only exists for as long as the connection exists. This makes `SQLiteConnection` a very long lived object. The other non-Sqlite examples create and destroy the connection alongside the transaction inside the `UnitOfWork`. 89 | 90 | The code is split up into `Application` and `Storage`, where `Storage` depends on `Application`. The first folder contains the parts of this pattern that need to be visible to all the code that uses Unit Of Work. The second contains the parts that are only needed internally by the repositories. The Controller class does not need to reference Storage to use `ICreateUnitOfWork`, so this pattern supports architectures where database code is decoupled. 91 | 92 | With this unit of work pattern everything has to be done inside a unit of work. But you could modify that by making chnages to what the UnitOfWorkContext does when no Unit of Work is open, if you disagree with that choice. 93 | 94 | This (and every other) example is just my current favourite way of structuring this pattern. If you were to dig through the commit history, you'll see that my preferences have changed over time. You can also change things to your preferences. 95 | 96 |
97 | 98 | ## 02: Example with events in the transaction 99 | 100 | Folder: *example-with-events/* 101 | 102 | Again, this uses Sqlite and Dapper. This time however, it also publishes events alongside the database changes. This an example of how non-database actions can included in the unit of work. This shows how Unit of work is a bigger concept than just a database transactions. 103 | 104 | I have used this pattern in real applications, where I have used a transactional event publisher to send messages to RabbitMq and enqueue work on Hangfire. 105 | 106 |
107 | 108 | ## 03: PostgreSQL and Dapper Example 109 | 110 | Folder *postgresql-example/* 111 | 112 | An example using PostgreSQL and Dapper. 113 | 114 | Requires Docker. Run `docker-compose` to start postgresql container needed for tests. Then just press play on the tests as usual. 115 | 116 |
117 | 118 | ## 04: SQL Server and Dapper Example 119 | 120 | Folder: *mssql-example/* 121 | 122 | An example using Sql Server and Dapper. A quirk of using Sql Server is that the transaction must be passed into Dapper separately from the connection. To make this possible the interface `IGetUnitOfWork ` is expanded to return the transaction in addition to the connection. 123 | 124 | Requires Docker. Run `docker-compose` to start sql server container needed for tests. Then just press play on the tests as usual. 125 | 126 |
127 | 128 | ## 05: Entity Framework Example 129 | 130 | Folder: *entity-framework-example/* 131 | 132 | An example using Entity Framework and SQL server. It could probably be easily adapted to other EF Data Providers, as long as they support transactions. 133 | 134 | Note, there is less of a usecase for having to use your own transactions with Ef, or any proper ORM. If you just use `SaveChanges`, EF will save large aggregates atomically by magic. Maybe that's all you need? Consider whether you really need this pattern before using it. 135 | 136 | Requires Docker. Run `docker-compose` to start sql server container needed for tests. Then just press play on the tests as usual. 137 | 138 |
139 | 140 | # How to use this in a dependency injection container 141 | 142 | Here is a a Dependency configuration lifted from one of my projects where I use this pattern. The IOC container I'm using is SimpleInjector. The important part is that `ICreateUnitOfWork` and `IGetConnection` are the same registration. The magic of this pattern is that the Unit of Work created via `ICreateUnitOfWork` in the 'business code' can be accessed by the repositories via `IGetConnection`, so it's critical that these interfaces are registered as the same underlying object. 143 | 144 | using System; 145 | using System.Linq; 146 | using System.Reflection; 147 | using MyProject.Domain.UnitOfWork; 148 | using MyProject.Storage; 149 | using MyProject.Storage.UnitOfWork; 150 | using Microsoft.Extensions.Configuration; 151 | using SimpleInjector; 152 | 153 | namespace MyProject.Api; 154 | 155 | internal static class Dependencies 156 | { 157 | internal static void Register(Container container, IConfiguration configuration) 158 | { 159 | var sqlSettings = configuration.GetSection(nameof(SqlSettings)).Get(); 160 | if (sqlSettings == null) 161 | { 162 | throw new ArgumentNullException(nameof(SqlSettings)); 163 | } 164 | 165 | container.RegisterInstance(sqlSettings); 166 | var uowRegistration = Lifestyle.Scoped.CreateRegistration( 167 | () => new UnitOfWorkContext(container.GetInstance()), container); 168 | container.AddRegistration(typeof(ICreateUnitOfWork), uowRegistration); 169 | container.AddRegistration(typeof(IGetConnection), uowRegistration); 170 | RegisterByConvention(typeof(MyProject.Storage.Users.UserRepository).Assembly, container, t => t.Name.EndsWith("Repository")); 171 | } 172 | 173 | private static void RegisterByConvention(Assembly assembly, Container container, Func condition) 174 | { 175 | var registrations = 176 | from type in assembly.GetExportedTypes() 177 | where condition(type) 178 | from service in type.GetInterfaces() 179 | select new { service, implementation = type }; 180 | 181 | foreach (var reg in registrations) 182 | { 183 | container.Register(reg.service, reg.implementation, Lifestyle.Scoped); 184 | } 185 | } 186 | } 187 | 188 | 189 | -------------------------------------------------------------------------------- /UnitOfWork.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33122.133 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1C8B7EDF-F099-4A28-803E-4A0621808E40}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | README.md = README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basic-example", "basic-example", "{72EC63AA-9F8C-464D-A2CD-9EB618F5885F}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.Sqlite.Tests", "basic-example\Uow.Sqlite.Tests\Uow.Sqlite.Tests.csproj", "{15CEDFB0-1BD8-4ECD-8B44-191F674EB65A}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mssql-example", "mssql-example", "{E33C43F1-0EAE-4406-A6F9-2F8487E1E91A}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.Mssql.Database", "mssql-example\Uow.Mssql.Database\Uow.Mssql.Database.csproj", "{C332E911-E8DC-4E00-80FB-D09B32470476}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.Mssql.Tests", "mssql-example\Uow.Mssql.Tests\Uow.Mssql.Tests.csproj", "{C6DF9767-9D0D-4445-ACB7-799DAF23B507}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.Mssql.Migrator", "mssql-example\Uow.Mssql.Migrator\Uow.Mssql.Migrator.csproj", "{69B75AF7-A8DA-499C-8CCE-BFF3CD8DBB70}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sql-server-container", "sql-server-container", "{B71926B3-73DE-48E3-929E-D37826193CD6}" 25 | ProjectSection(SolutionItems) = preProject 26 | sql-server-container\Dockerfile = sql-server-container\Dockerfile 27 | sql-server-container\entrypoint.sh = sql-server-container\entrypoint.sh 28 | sql-server-container\initialize-database.sql = sql-server-container\initialize-database.sql 29 | sql-server-container\setup-database.sh = sql-server-container\setup-database.sh 30 | EndProjectSection 31 | EndProject 32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example-with-events", "example-with-events", "{884F405E-F04F-4645-9825-E778C260E41E}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.SqliteWithEvents.Example", "example-with-events\Uow.SqliteWithEvents.Example\Uow.SqliteWithEvents.Example.csproj", "{C0B41774-40DA-463D-BF22-B55BA4FEF564}" 35 | EndProject 36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.SqliteWithEvents.Tests", "example-with-events\Uow.SqliteWithEvents.Tests\Uow.SqliteWithEvents.Tests.csproj", "{66873557-3181-43EF-8773-BA47CF64EE1C}" 37 | EndProject 38 | Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{D2C0E2B3-E223-41BF-8B23-6B1487F598FA}" 39 | EndProject 40 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postgresql-example", "postgresql-example", "{D5DCDE51-0581-44F4-AA19-299C80103AE9}" 41 | EndProject 42 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.Postgresql.Database", "postgresql-example\Uow.Postgresql.Database\Uow.Postgresql.Database.csproj", "{DB28EE51-9353-484B-9C27-D2704AC32F1B}" 43 | EndProject 44 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.Postgresql.Migrator", "postgresql-example\Uow.Postgresql.Migrator\Uow.Postgresql.Migrator.csproj", "{F8F224CC-7765-4ADA-8948-1757CA691616}" 45 | EndProject 46 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.Postgresql.Tests", "postgresql-example\Uow.Postgresql.Tests\Uow.Postgresql.Tests.csproj", "{BA1205AC-0046-4193-85B3-0F2103D5BA01}" 47 | EndProject 48 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.Sqlite.Example", "basic-example\Uow.Sqlite.Example\Uow.Sqlite.Example.csproj", "{1F0F5CD9-A942-49AB-8389-158706DDB505}" 49 | EndProject 50 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "entity-framework-example", "entity-framework-example", "{5E123332-EC8D-446F-ABB1-7904F4A0680C}" 51 | EndProject 52 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.EntityFramework.Example", "entity-framework-example\Uow.EntityFramework.Example\Uow.EntityFramework.Example.csproj", "{ECAAB73C-7694-44BF-B5BA-3B203D4633DF}" 53 | EndProject 54 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uow.EntityFramework.Tests", "entity-framework-example\Uow.EntityFramework.Tests\Uow.EntityFramework.Tests.csproj", "{9AFB1AE2-D2C3-4980-A09A-41FE3A95EA3B}" 55 | EndProject 56 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Uow.EntityFramework.Migrator", "entity-framework-example\Uow.EntityFramework.Migrator\Uow.EntityFramework.Migrator.csproj", "{1A5DFF83-4B74-4382-8933-D5307FF342AC}" 57 | EndProject 58 | Global 59 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 60 | Debug|Any CPU = Debug|Any CPU 61 | Release|Any CPU = Release|Any CPU 62 | EndGlobalSection 63 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 64 | {15CEDFB0-1BD8-4ECD-8B44-191F674EB65A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {15CEDFB0-1BD8-4ECD-8B44-191F674EB65A}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {15CEDFB0-1BD8-4ECD-8B44-191F674EB65A}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {15CEDFB0-1BD8-4ECD-8B44-191F674EB65A}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {C332E911-E8DC-4E00-80FB-D09B32470476}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {C332E911-E8DC-4E00-80FB-D09B32470476}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {C332E911-E8DC-4E00-80FB-D09B32470476}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {C332E911-E8DC-4E00-80FB-D09B32470476}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {C6DF9767-9D0D-4445-ACB7-799DAF23B507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 73 | {C6DF9767-9D0D-4445-ACB7-799DAF23B507}.Debug|Any CPU.Build.0 = Debug|Any CPU 74 | {C6DF9767-9D0D-4445-ACB7-799DAF23B507}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {C6DF9767-9D0D-4445-ACB7-799DAF23B507}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {69B75AF7-A8DA-499C-8CCE-BFF3CD8DBB70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {69B75AF7-A8DA-499C-8CCE-BFF3CD8DBB70}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {69B75AF7-A8DA-499C-8CCE-BFF3CD8DBB70}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {69B75AF7-A8DA-499C-8CCE-BFF3CD8DBB70}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {C0B41774-40DA-463D-BF22-B55BA4FEF564}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {C0B41774-40DA-463D-BF22-B55BA4FEF564}.Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {C0B41774-40DA-463D-BF22-B55BA4FEF564}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {C0B41774-40DA-463D-BF22-B55BA4FEF564}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {66873557-3181-43EF-8773-BA47CF64EE1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 85 | {66873557-3181-43EF-8773-BA47CF64EE1C}.Debug|Any CPU.Build.0 = Debug|Any CPU 86 | {66873557-3181-43EF-8773-BA47CF64EE1C}.Release|Any CPU.ActiveCfg = Release|Any CPU 87 | {66873557-3181-43EF-8773-BA47CF64EE1C}.Release|Any CPU.Build.0 = Release|Any CPU 88 | {D2C0E2B3-E223-41BF-8B23-6B1487F598FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {D2C0E2B3-E223-41BF-8B23-6B1487F598FA}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {D2C0E2B3-E223-41BF-8B23-6B1487F598FA}.Release|Any CPU.ActiveCfg = Release|Any CPU 91 | {D2C0E2B3-E223-41BF-8B23-6B1487F598FA}.Release|Any CPU.Build.0 = Release|Any CPU 92 | {DB28EE51-9353-484B-9C27-D2704AC32F1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 93 | {DB28EE51-9353-484B-9C27-D2704AC32F1B}.Debug|Any CPU.Build.0 = Debug|Any CPU 94 | {DB28EE51-9353-484B-9C27-D2704AC32F1B}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {DB28EE51-9353-484B-9C27-D2704AC32F1B}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {F8F224CC-7765-4ADA-8948-1757CA691616}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 97 | {F8F224CC-7765-4ADA-8948-1757CA691616}.Debug|Any CPU.Build.0 = Debug|Any CPU 98 | {F8F224CC-7765-4ADA-8948-1757CA691616}.Release|Any CPU.ActiveCfg = Release|Any CPU 99 | {F8F224CC-7765-4ADA-8948-1757CA691616}.Release|Any CPU.Build.0 = Release|Any CPU 100 | {BA1205AC-0046-4193-85B3-0F2103D5BA01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 101 | {BA1205AC-0046-4193-85B3-0F2103D5BA01}.Debug|Any CPU.Build.0 = Debug|Any CPU 102 | {BA1205AC-0046-4193-85B3-0F2103D5BA01}.Release|Any CPU.ActiveCfg = Release|Any CPU 103 | {BA1205AC-0046-4193-85B3-0F2103D5BA01}.Release|Any CPU.Build.0 = Release|Any CPU 104 | {1F0F5CD9-A942-49AB-8389-158706DDB505}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 105 | {1F0F5CD9-A942-49AB-8389-158706DDB505}.Debug|Any CPU.Build.0 = Debug|Any CPU 106 | {1F0F5CD9-A942-49AB-8389-158706DDB505}.Release|Any CPU.ActiveCfg = Release|Any CPU 107 | {1F0F5CD9-A942-49AB-8389-158706DDB505}.Release|Any CPU.Build.0 = Release|Any CPU 108 | {ECAAB73C-7694-44BF-B5BA-3B203D4633DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 109 | {ECAAB73C-7694-44BF-B5BA-3B203D4633DF}.Debug|Any CPU.Build.0 = Debug|Any CPU 110 | {ECAAB73C-7694-44BF-B5BA-3B203D4633DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 111 | {ECAAB73C-7694-44BF-B5BA-3B203D4633DF}.Release|Any CPU.Build.0 = Release|Any CPU 112 | {9AFB1AE2-D2C3-4980-A09A-41FE3A95EA3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 113 | {9AFB1AE2-D2C3-4980-A09A-41FE3A95EA3B}.Debug|Any CPU.Build.0 = Debug|Any CPU 114 | {9AFB1AE2-D2C3-4980-A09A-41FE3A95EA3B}.Release|Any CPU.ActiveCfg = Release|Any CPU 115 | {9AFB1AE2-D2C3-4980-A09A-41FE3A95EA3B}.Release|Any CPU.Build.0 = Release|Any CPU 116 | {1A5DFF83-4B74-4382-8933-D5307FF342AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 117 | {1A5DFF83-4B74-4382-8933-D5307FF342AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 118 | {1A5DFF83-4B74-4382-8933-D5307FF342AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 119 | {1A5DFF83-4B74-4382-8933-D5307FF342AC}.Release|Any CPU.Build.0 = Release|Any CPU 120 | EndGlobalSection 121 | GlobalSection(SolutionProperties) = preSolution 122 | HideSolutionNode = FALSE 123 | EndGlobalSection 124 | GlobalSection(NestedProjects) = preSolution 125 | {15CEDFB0-1BD8-4ECD-8B44-191F674EB65A} = {72EC63AA-9F8C-464D-A2CD-9EB618F5885F} 126 | {C332E911-E8DC-4E00-80FB-D09B32470476} = {E33C43F1-0EAE-4406-A6F9-2F8487E1E91A} 127 | {C6DF9767-9D0D-4445-ACB7-799DAF23B507} = {E33C43F1-0EAE-4406-A6F9-2F8487E1E91A} 128 | {69B75AF7-A8DA-499C-8CCE-BFF3CD8DBB70} = {E33C43F1-0EAE-4406-A6F9-2F8487E1E91A} 129 | {C0B41774-40DA-463D-BF22-B55BA4FEF564} = {884F405E-F04F-4645-9825-E778C260E41E} 130 | {66873557-3181-43EF-8773-BA47CF64EE1C} = {884F405E-F04F-4645-9825-E778C260E41E} 131 | {DB28EE51-9353-484B-9C27-D2704AC32F1B} = {D5DCDE51-0581-44F4-AA19-299C80103AE9} 132 | {F8F224CC-7765-4ADA-8948-1757CA691616} = {D5DCDE51-0581-44F4-AA19-299C80103AE9} 133 | {BA1205AC-0046-4193-85B3-0F2103D5BA01} = {D5DCDE51-0581-44F4-AA19-299C80103AE9} 134 | {1F0F5CD9-A942-49AB-8389-158706DDB505} = {72EC63AA-9F8C-464D-A2CD-9EB618F5885F} 135 | {ECAAB73C-7694-44BF-B5BA-3B203D4633DF} = {5E123332-EC8D-446F-ABB1-7904F4A0680C} 136 | {9AFB1AE2-D2C3-4980-A09A-41FE3A95EA3B} = {5E123332-EC8D-446F-ABB1-7904F4A0680C} 137 | {1A5DFF83-4B74-4382-8933-D5307FF342AC} = {5E123332-EC8D-446F-ABB1-7904F4A0680C} 138 | EndGlobalSection 139 | GlobalSection(ExtensibilityGlobals) = postSolution 140 | SolutionGuid = {0F5E5E04-7E6E-4A54-867D-12ED0CE09C45} 141 | EndGlobalSection 142 | EndGlobal 143 | --------------------------------------------------------------------------------