work unit unidad trabajo repositorio pattern patrón patrones patron parte net implementación framework diseño repository-pattern dapper unit-of-work

repository pattern - unit - ¿Cómo implementar el patrón de Unidad de Trabajo con Dapper?



unit of work (2)

Actualmente, estoy tratando de usar DAPper ORM con la Unidad de trabajo + Patrón de repositorio.

Quiero usar Unit of Work en lugar de un simple Repositorio Dapper debido a que mi inserción y actualizaciones requieren un cierto grado de procesamiento de transacciones. No he podido encontrar ningún ejemplo útil, ya que la mayoría parece usar Entity Framework y tienen problemas de fugas dentro de la Unidad de Trabajo.

¿Podría alguien, por favor, señalarme en la dirección correcta?


Edición 2018-08-03: El comentario de Amit realmente me hizo pensar, y me hizo darme cuenta de que el repositorio no NECESITA ser propiedad del contexto en sí. Pero más bien, los repositorios podrían tener una dependencia del contexto. En lugar de continuar realizando cambios incrementales en los ejemplos de código a continuación. Simplemente haré referencia a un repositorio git que he reunido para contener este concepto.

De pie sobre los hombros de los demás aquí.

Teniendo en cuenta que esta respuesta es superior en la mayoría de las búsquedas de Google que pertenecen a "dapper" y "unidad de trabajo". Quería proporcionar mi enfoque, que ahora he usado con gran efecto varias veces.

Usando un ejemplo ficticio (y demasiado simplificado):

public interface IUnitOfWorkFactory { UnitOfWork Create(); } public interface IDbContext { IProductRepository Product { get; set; } void Commit(); void Rollback(); } public interface IUnitOfWork { IDbTransaction Transaction { get;set; } void Commit(); void Rollback(); } public interface IProductRepository { Product Read(int id); }

Tenga en cuenta que ni IDbContext ni IUnitOfWorkFactory implementan IDisposable. Esto se hace a propósito para evitar una abstracción con fugas . En cambio, la confianza está en Commit() / Rollback() para cuidar de la limpieza y eliminación.

Un par de puntos antes de compartir implementaciones.

  • IUnitOfWorkFactory es responsable de crear una instancia de UnitOfWork e intermediar la conexión de la base de datos.
  • IDbContext es la columna vertebral del repositorio.
  • IUnitOfWork es una encapsulación de IDbTransaction y garantiza que, al trabajar con múltiples repositorios, compartan un solo contexto de base de datos.

Implementación de IUnitOfWorkFactory

public class UnitOfWorkFactory<TConnection> : IUnitOfWorkFactory where TConnection : IDbConnection, new() { private string connectionString; public UnitOfWorkFactory(string connectionString) { if (string.IsNullOrWhiteSpace(connectionString)) { throw new ArgumentNullException("connectionString cannot be null"); } this.connectionString = connectionString; } public UnitOfWork Create() { return new UnitOfWork(CreateOpenConnection()); } private IDbConnection CreateOpenConnection() { var conn = new TConnection(); conn.ConnectionString = connectionString; try { if (conn.State != ConnectionState.Open) { conn.Open(); } } catch (Exception exception) { throw new Exception("An error occured while connecting to the database. See innerException for details.", exception); } return conn; } }

Implementación de IDbContext

public class DbContext : IDbContext { private IUnitOfWorkFactory unitOfWorkFactory; private UnitOfWork unitOfWork; private IProductRepository product; public DbContext(IUnitOfWorkFactory unitOfWorkFactory) { this.unitOfWorkFactory = unitOfWorkFactory; } public ProductRepository Product => product ?? (product = new ProductRepository(UnitOfWork)); protected UnitOfWork UnitOfWork => unitOfWork ?? (unitOfWork = unitOfWorkFactory.Create()); public void Commit() { try { UnitOfWork.Commit(); } finally { Reset(); } } public void Rollback() { try { UnitOfWork.Rollback(); } finally { Reset(); } } private void Reset() { unitOfWork = null; product = null; } }

Implementación de IUnitOfWork

public class UnitOfWork : IUnitOfWork { private IDbTransaction transaction; public UnitOfWork(IDbConnection connection) { transaction = connection.BeginTransaction(); } public IDbTransaction Transaction => transaction; public void Commit() { try { transaction.Commit(); transaction.Connection?.Close(); } catch { transaction.Rollback(); throw; } finally { transaction?.Dispose(); transaction.Connection?.Dispose(); transaction = null; } } public void Rollback() { try { transaction.Rollback(); transaction.Connection?.Close(); } catch { throw; } finally { transaction?.Dispose(); transaction.Connection?.Dispose(); transaction = null; } } }

Implementación de IProductRepository

public class ProductRepository : IProductRepository { protected readonly IDbConnection connection; protected readonly IDbTransaction transaction; public ProductRepository(UnitOfWork unitOfWork) { connection = unitOfWork.Transaction.Connection; transaction = unitOfWork.Transaction; } public Product Read(int id) { return connection.QuerySingleOrDefault<Product>("select * from dbo.Product where Id = @id", new { id }, transaction: Transaction); } }

Para acceder a la base de datos, simplemente DbContext instancia de DbContext o inyecte utilizando el contenedor IoC que elija (personalmente uso el contenedor IoC proporcionado por .NET Core ).

var unitOfWorkFactory = new UnitOfWorkFactory<SqlConnection>("your connection string"); var db = new DbContext(unitOfWorkFactory); Product product = null; try { product = db.Product.Read(1); db.Commit(); } catch (SqlException ex) { //log exception db.Rollback(); }

La necesidad explícita de Commit() para esta simple operación de solo lectura parece excesiva, pero paga dividendos a medida que el sistema crece. Y al parecer, ofrece un beneficio de rendimiento menor según Sam Saffron . También "puede" omitir db.Commit() en operaciones de lectura simples, al hacer esto aunque deje la conexión abierta y ponga la responsabilidad de limpiar las cosas en el recolector de basura. Así que esto no es recomendable.

Normalmente traigo el DbContext al pliegue en el nivel de servicio, donde funciona al unísono con otros servicios para formar el "ServiceContext". Luego hago referencia a este ServiceContext en la capa MVC real. Como otro punto de mención, se recomienda usar async toda la pila si es posible. Se omite aquí por simplicidad.


Este proyecto Git es muy útil. Comencé desde el mismo e hice algunos cambios según mi necesidad.

public sealed class DalSession : IDisposable { public DalSession() { _connection = new OleDbConnection(DalCommon.ConnectionString); _connection.Open(); _unitOfWork = new UnitOfWork(_connection); } IDbConnection _connection = null; UnitOfWork _unitOfWork = null; public UnitOfWork UnitOfWork { get { return _unitOfWork; } } public void Dispose() { _unitOfWork.Dispose(); _connection.Dispose(); } } public sealed class UnitOfWork : IUnitOfWork { internal UnitOfWork(IDbConnection connection) { _id = Guid.NewGuid(); _connection = connection; } IDbConnection _connection = null; IDbTransaction _transaction = null; Guid _id = Guid.Empty; IDbConnection IUnitOfWork.Connection { get { return _connection; } } IDbTransaction IUnitOfWork.Transaction { get { return _transaction; } } Guid IUnitOfWork.Id { get { return _id; } } public void Begin() { _transaction = _connection.BeginTransaction(); } public void Commit() { _transaction.Commit(); Dispose(); } public void Rollback() { _transaction.Rollback(); Dispose(); } public void Dispose() { if(_transaction != null) _transaction.Dispose(); _transaction = null; } } interface IUnitOfWork : IDisposable { Guid Id { get; } IDbConnection Connection { get; } IDbTransaction Transaction { get; } void Begin(); void Commit(); void Rollback(); }

Ahora, tus repositorios deberían aceptar este UnitOfWork de alguna manera. Elijo Inyección de Dependencia con Constructor.

public sealed class MyRepository { public MyRepository(IUnitOfWork unitOfWork) { this.unitOfWork = unitOfWork; } IUnitOfWork unitOfWork = null; //You also need to handle other parameters like ''sql'', ''param'' ect. This is out of scope of this answer. public MyPoco Get() { return unitOfWork.Connection.Query(sql, param, unitOfWork.Transaction, .......); } public void Insert(MyPoco poco) { return unitOfWork.Connection.Execute(sql, param, unitOfWork.Transaction, .........); } }

Y luego lo llamas así:

Con la transacción:

using(DalSession dalSession = new DalSession()) { UnitOfWork unitOfWork = dalSession.UnitOfWork; unitOfWork.Begin(); try { //Your database code here MyRepository myRepository = new MyRepository(unitOfWork); myRepository.Insert(myPoco); //You may create other repositories in similar way in same scope of UoW. unitOfWork.Commit(); } catch { unitOfWork.Rollback(); throw; } }

Sin Transacción:

using(DalSession dalSession = new DalSession()) { //Your database code here MyRepository myRepository = new MyRepository(dalSession.UnitOfWork);//UoW have no effect here as Begin() is not called. myRepository.Insert(myPoco); }

Tenga en cuenta que UnitOfWork es more que DBTransaction.

Más detalles sobre el Repositorio en el código anterior se pueden encontrar here .

Ya he publicado este código here . Pero esta pregunta me parece más relevante para este código; Así que estoy publicando de nuevo en lugar de solo un enlace a la respuesta original.