Patrón de diseño de repository con Dapper

Esta es quizás más una pregunta para la revisión del código en lugar del desbordamiento de la stack.

Estoy usando Dapper para un MicroORM para recuperar y guardar datos en SQL Server 2014. Tengo clases de DTO en un DTO Proj que representan los datos recuperados del DB o guardados en el DB.

Estoy usando el patrón de repository, por lo que en mi capa de servicio si se requiere un repository, estoy usando el constructor DI para inyectar esa dependencia y luego invoco el método en el repository para hacer el trabajo.

así que supongamos que tengo 2 servicios llamados CustomerService y CarService.

Luego tengo 2 repositorys, un CustomerRepository y un CarRepository.

Tengo una interfaz que define todos los métodos en cada repository y luego las implementaciones concretas.

Un método de ejemplo se muestra a continuación (llamando a un progtwig almacenado para hacer el DB INSERT (observe que la variable de cadena real para el proceso almacenado se define como una cadena privada en la parte superior de la clase):

public void SaveCustomer(CustomerDTO custDTO) { using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString)) { db.Execute(saveCustSp, custDTO, commandType: CommandType.StoredProcedure); } } 

Todo esto funciona bien, pero me encuentro repitiendo el uso de bloque en cada método en cada repository. Tengo dos preguntas reales que se detallan a continuación.

¿Existe un mejor enfoque que podría estar usando quizás de alguna manera al utilizar una clase BaseRepository de la que todos los demás Repositorios hereden y la Base implementaría la creación de instancias de la conexión DB?

¿Eso funcionaría bien para múltiples usuarios simultáneos en el sistema?

****ACTUALIZAR****

Basado en la respuesta de Silas, he creado lo siguiente

 public interface IBaseRepository { void Execute(Action query); } public class BaseRepository: IBaseRepository { public void Execute(Action query) { using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString)) { query.Invoke(db); } } } 

Sin embargo, en mis repositorys, tengo otros métodos como el siguiente:

  public bool IsOnlyCarInStock(int carId, int year) { using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString)) { var car = db.ExecuteScalar(anotherStoredSp, new { CarID = carId, Year = year }, commandType: CommandType.StoredProcedure); return car > 0 ? true : false; } } 

y

  public IEnumerable GetEmployeeDetails(int employeeId) { using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString)) { return db.Query(anotherSp, new { EmployeeID = employeeId }, commandType: CommandType.StoredProcedure); } } 

¿Cuál es la forma correcta de agregarlos a mi repository de base utilizando el tipo genérico T para poder devolver cualquier tipo de DTO o cualquier tipo nativo de C #?

Claro, una función para crear y eliminar su conexión funcionará de maravilla.

 protected void Execute(Action query) { using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString)) { query.Invoke(db); } } 

Y su sitio de llamadas simplificado:

 public void SaveCustomer(CustomerDTO custDTO) { Execute(db => db.Execute(saveCustSp, custDTO, CommandType.StoredProcedure)); } 

Con valores de retorno:

 public T Get(Func query) { using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString)) { return query.Invoke(db); } } 

En su sitio de llamadas, simplemente escriba la lógica que desea usar.

 public IEnumerable GetEmployeeDetails(int employeeId) { return Get(db => db.Query(anotherSp, new { EmployeeID = employeeId }, CommandType.StoredProcedure)); } 

Esto no es directamente relevante para su pregunta. Pero sugiero que considere usar DapperExtensions.

Inicialmente, implementé el patrón Repository usando Dapper. El inconveniente fue que tengo que escribir consultas por todas partes; fue muy fibroso. Debido a las consultas codificadas, era casi imposible escribir un repository genérico.

Recientemente, actualicé mi código para usar DapperExtensions. Esto soluciona muchos problemas.

A continuación está el repository genérico:

 public abstract class BaseRepository where T : BasePoco { internal BaseRepository(IUnitOfWork unitOfWork) { dapperExtensionsProxy = new DapperExtensionsProxy(unitOfWork); } DapperExtensionsProxy dapperExtensionsProxy = null; protected bool Exists() { return (GetCount() == 0) ? false : true; } protected int GetCount() { var result = dapperExtensionsProxy.Count(null); return result; } protected T GetById(Guid id) { var result = dapperExtensionsProxy.Get(id); return result; } protected T GetById(string id) { var result = dapperExtensionsProxy.Get(id); return result; } protected List GetList() { var result = dapperExtensionsProxy.GetList(null); return result.ToList(); } protected void Insert(T poco) { var result = dapperExtensionsProxy.Insert(poco); } protected void Update(T poco) { var result = dapperExtensionsProxy.Update(poco); } protected void Delete(T poco) { var result = dapperExtensionsProxy.Delete(poco); } protected void DeleteById(Guid id) { T poco = (T)Activator.CreateInstance(typeof(T)); poco.SetDbId(id); var result = dapperExtensionsProxy.Delete(poco); } protected void DeleteById(string id) { T poco = (T)Activator.CreateInstance(typeof(T)); poco.SetDbId(id); var result = dapperExtensionsProxy.Delete(poco); } protected void DeleteAll() { var predicateGroup = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List() }; var result = dapperExtensionsProxy.Delete(predicateGroup);//Send empty predicateGroup to delete all records. } 

Como puede ver en el código anterior, la mayoría de los métodos son solo envoltorios sobre la clase subyacente DapperExtensionsProxy . DapperExtensionsProxy internamente también administra UnitOfWork, que puedes ver a continuación. Estas dos clases se pueden combinar sin ningún problema. Personalmente prefiero mantenerlos separados.

También puede observar que DeleteById métodos adicionales, DeleteById y DeleteAll no son parte de DapperExtensionsProxy .

Método poco.SetDbId se define en cada clase POCO para establecer su propiedad Identificador. En mi caso, los identificadores de POCO pueden tener diferentes tipos de datos y nombres.

Lo siguiente es DapperExtensionsProxy :

 internal sealed class DapperExtensionsProxy { internal DapperExtensionsProxy(IUnitOfWork unitOfWork) { this.unitOfWork = unitOfWork; } IUnitOfWork unitOfWork = null; internal int Count(object predicate) where T : BasePoco { var result = unitOfWork.Connection.Count(predicate, unitOfWork.Transaction); return result; } internal T Get(object id) where T : BasePoco { var result = unitOfWork.Connection.Get(id, unitOfWork.Transaction); return result; } internal IEnumerable GetList(object predicate, IList sort = null, bool buffered = false) where T : BasePoco { var result = unitOfWork.Connection.GetList(predicate, sort, unitOfWork.Transaction, null, buffered); return result; } internal IEnumerable GetPage(object predicate, int page, int resultsPerPage, IList sort = null, bool buffered = false) where T : BasePoco { var result = unitOfWork.Connection.GetPage(predicate, sort, page, resultsPerPage, unitOfWork.Transaction, null, buffered); return result; } internal dynamic Insert(T poco) where T : BasePoco { var result = unitOfWork.Connection.Insert(poco, unitOfWork.Transaction); return result; } internal void Insert(IEnumerable listPoco) where T : BasePoco { unitOfWork.Connection.Insert(listPoco, unitOfWork.Transaction); } internal bool Update(T poco) where T : BasePoco { var result = unitOfWork.Connection.Update(poco, unitOfWork.Transaction); return result; } internal bool Delete(T poco) where T : BasePoco { var result = unitOfWork.Connection.Delete(poco, unitOfWork.Transaction); return result; } internal bool Delete(object predicate) where T : BasePoco { var result = unitOfWork.Connection.Delete(predicate, unitOfWork.Transaction); return result; } } 

Esto también usa UnitOfWork que se explica aquí .