Last active
April 20, 2024 13:54
-
-
Save azborgonovo/445ef9a864ea9ec38a2e to your computer and use it in GitHub Desktop.
Creating the 'best' Unit of Work and Repository implementation on C#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Basic unitOfWork pattern as described by Martin Fowler (http://martinfowler.com/eaaCatalog/unitOfWork.html) | |
// Other methos as 'registerNew' are going to be managed by each repository | |
public interface IUnitOfWork : IDisposable | |
{ | |
void Commit(); | |
Task CommitAsync(); | |
void Rollback(); | |
} | |
public interface IUnitOfWorkFactory | |
{ | |
IUnitOfWork CreateUnitOfWork(); | |
IUnitOfWork CreateUnitOfWork(bool beginDatabaseTransaction); | |
} | |
public interface IDbSetFactory | |
{ | |
IDbSet<T> CreateDbSet<T>() where T : class; | |
} | |
// TODO: include async | |
public interface IReadOnlyRepository<T> where T : class | |
{ | |
IEnumerable<T> GetAll(Func<T, bool> predicate = null); | |
T Get(Func<T, bool> predicate); | |
} | |
public interface IRepository<T> : IReadOnlyRepository<T> where T : class | |
{ | |
void Add(T entity); | |
void Update(T entity); | |
void Remove(T entity); | |
} | |
public abstract class Repository<T> : IRepository<T> | |
{ | |
readonly IDbSetFactory _dbSetFactory; | |
public Repository(IDbSetFactory dbSetFactory) | |
{ | |
_dbSetFactory = dbSetFactory; | |
} | |
} | |
public abstract class EntityFrameworkContext : DbContext, IUnitOfWorkFactory, IDbSetFactory | |
{ | |
public IDbSet<T> CreateDbSet<T>() where T : class | |
{ | |
return Set<T>(); | |
} | |
public IUnitOfWork CreateUnitOfWork() | |
{ | |
return CreateUnitOfWork(false); | |
} | |
public IUnitOfWork CreateUnitOfWork(bool beginDatabaseTransaction) | |
{ | |
return new EntityFrameworkUnitOfWork(this, beginDatabaseTransaction); | |
} | |
} | |
public class EntityFrameworkUnitOfWork : IUnitOfWork | |
{ | |
bool _usesDatabaseTransaction; | |
DbContext _dbContext; | |
DbContextTransaction _dbContextTransaction; | |
public EntityFrameworkUnitOfWork(DbContext dbContext, bool beginDatabaseTransaction) | |
{ | |
_dbContext = dbContext; | |
_usesDatabaseTransaction = beginDatabaseTransaction; | |
if (_usesDatabaseTransaction) | |
_dbContextTransaction = _dbContext.Database.BeginTransaction(); | |
} | |
public void Commit() | |
{ | |
_dbContext.SaveChanges(); | |
if (_usesDatabaseTransaction && _dbContextTransaction != null) | |
_dbContextTransaction.Commit(); | |
} | |
public void Rollback() | |
{ | |
if (_usesDatabaseTransaction && _dbContextTransaction != null) | |
_dbContextTransaction.Rollback(); | |
} | |
public void Dispose() | |
{ | |
if (_dbContextTransaction != null) | |
_dbContextTransaction.Dispose(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// IUnitOfWork.cs | |
public interface IUnitOfWork : IDisposable | |
{ | |
void Commit(); | |
Task CommitAsync(); | |
} | |
// ITenant.cs | |
public interface ITenant | |
{ | |
public string ConnectionString { get; } | |
public string Schema { get; } | |
} | |
// IKonexiaContext.cs | |
public interface IKonexiaContext | |
{ | |
IUnitOfWork CreateCoreUnitOfWork(); | |
IUnitOfWork CreateTenantUnitOfWork(ITenant tenant); | |
} | |
// KonexiaContext.cs | |
public abstract class KonexiaContext : IKonexiaContext | |
{ | |
public IUnitOfWork CreateCoreUnitOfWork() | |
{ | |
return new EntityFrameworkUnitOfWork(null); | |
} | |
public IUnitOfWork CreateTenantUnitOfWork(ITenant tenant) | |
{ | |
return new EntityFrameworkUnitOfWork(tenant); | |
} | |
} | |
// EntityFrameworkUnitOfWork.cs | |
public class EntityFrameworkUnitOfWork : DbContext, IUnitOfWork | |
{ | |
ITenant _tenant; | |
public EntityFrameworkUnitOfWork(tenant) | |
: base(tenant == null ? "DefaultConnection" : tenant.ConnectionString) | |
{ | |
_tenant = tenant; | |
} | |
protected override void OnModelCreating(DbModelBuilder modelBuilder) | |
{ | |
if (tenant != null && !string.IsNullOrEmpty(tenant.Schema)) | |
{ | |
//Configure default schema | |
modelBuilder.HasDefaultSchema(tenant.Schema); | |
} | |
} | |
public void Commit() | |
{ | |
this.SaveChanges(); | |
} | |
public void CommitAsync() | |
{ | |
this.SaveChangesAsync(); | |
} | |
} | |
// --------------------------------- | |
// Usage | |
// --------------------------------- | |
// UsuariosAppService.cs | |
public class UsuariosAppService : IUsuariosAppService | |
{ | |
IKonexiaContext _context; | |
public UsuariosAppService(IKonexiaContext context) | |
{ | |
_context = context; | |
} | |
public void CriarUsuario(UsuarioDto dto) | |
{ | |
var usuario = Mapper.Map<Usuario>(dto); // just for clarification | |
using (var uow = _context.CreateCoreUnitOfWork()) | |
{ | |
var usuariosRepository = uow.Repository<IUsuariosRepository>(); | |
usuariosRepository.Add(usuario); | |
uow.Commit(); | |
} | |
} | |
public void CriarAlgoComTenant(ITenant tenant, Algo algo) | |
{ | |
using (var uow = _context.CreateTenantUnitOfWork(tenant)) | |
{ | |
var algosRepository = uow.Repository<IAlgosRepository>(); | |
algosRepository.Add(algo); | |
uow.Commit(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface ISalesContext : IUnitOfWorkFactory, IDbSetFactory | |
{ | |
IClientsRepository ClientsRepository { get; } | |
} | |
public class SalesContext : EntityFrameworkContext, ISalesContext | |
{ | |
readonly Func<IClientsRepository> _clientsRepository; | |
public IClientsRepository ClientsRepository { get { return _clientsRepository(); } } | |
public SalesContext(Func<IClientsRepository> clientsRepository) | |
{ | |
_clientsRepository = clientsRepository; | |
} | |
} | |
public interface IClientsRepository : IRepository<Client> { } | |
public class ClientsRepository : Repository<Client>, IClientsRepository | |
{ | |
public ClientsRepository(ISalesContext salesContext) | |
: base(salesContext) | |
{ | |
} | |
} | |
public class TestContext : ISalesContext | |
{ | |
readonly Func<IClientsRepository> _clientsRepository; | |
public IClientsRepository ClientsRepository { get { return _clientsRepository(); } } | |
public TestContext(Func<IClientsRepository> clientsRepository) | |
{ | |
_clientsRepository = clientsRepository; | |
} | |
public IDbSet<T> CreateDbSet<T>() where T : class | |
{ | |
return new TestDbSet<T>(); // TODO: put the sets into static for state maintenance | |
} | |
public IUnitOfWork CreateUnitOfWork() | |
{ | |
return new TestUnitOfWork(); | |
} | |
public void Dispose() { } | |
} | |
// Application Service sample usage | |
public class ClientsService | |
{ | |
readonly ISalesContext _salesContext; | |
public ClientsService(ISalesContext salesContext) | |
{ | |
_salesContext = salesContext; | |
} | |
public IEnumerable<ClientDto> GetAllClients() | |
{ | |
// This method doesn't need to init a Unit of Work | |
// since it isn't making any business transaction | |
var clients = _salesContext.ClientsRepository.GetAll(); | |
// ... | |
return list; | |
} | |
public void CreateClient(ClientDto clientDto) | |
{ | |
using (var unitOfWork = _salesContext.CreateUnitOfWork()) | |
{ | |
try | |
{ | |
// ... | |
_salesContext.ClientsRepository.Add(client); | |
unitOfWork.Commit(); | |
} | |
catch | |
{ | |
unitOfWork.Rollback(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment