2017-03-22 4 views
5

Il s'agit peut-être davantage d'une question de révision de code que d'un dépassement de pile.Modèle de conception de référentiel avec Dapper

J'utilise Dapper pour un MicroORM pour récupérer et enregistrer des données dans SQL Server 2014. J'ai des classes DTO dans un projet DTO qui représentent les données extraites de la base de données ou enregistrées dans la base de données. J'utilise le Repository Pattern donc à ma couche Service si un référentiel est requis J'utilise le constructeur DI pour injecter cette dépendance et ensuite appeler la méthode sur le Repository pour faire le travail. Alors disons que j'ai 2 services appelés CustomerService et CarService. J'ai ensuite 2 dépôts dans CustomerRepository et un CarRepository.

J'ai une interface qui définit toutes les méthodes dans chaque Repository puis les implémentations concrètes.

Un exemple de procédé est illustré ci-dessous (appeler un proc stocké pour faire le DB INSERT (notez la variable de chaîne réelle pour la procédure stockée est définie comme une chaîne privée en haut de la classe):

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

Tout cela fonctionne bien mais je me retrouve à répéter le bloc using dans chaque méthode dans chaque référentiel.J'ai deux vraies questions décrites ci-dessous:

Y at-il une meilleure approche que je pourrais utiliser peut-être en quelque sorte en utilisant une classe BaseRepository tous les autres référentiels héritent de et la base implémenterait l'instanciation de la connexion DB ?

Cela fonctionnerait-il encore correctement pour plusieurs utilisateurs simultanés sur le système?

**** **** MISE A JOUR

Sur la base de réponse Silas J'ai créé le

suivant
public interface IBaseRepository 
{ 
    void Execute(Action<IDbConnection> query); 
} 

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

Cependant, dans mes dépôts, j'ai d'autres méthodes telles que le ci-dessous:

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

      return car > 0 ? true : false; 
     } 
    } 

et

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

Quelle est la bonne façon de les ajouter à mon dépôt de base en utilisant le type générique T afin que je puisse retourner n'importe quel type de DTO ou n'importe quel C#

+0

C'est la façon de le réaliser, vous devez rendre votre BaseRepository jetable pour disposer de votre IDbConnection. Vous pouvez jeter un œil sur l'utilisation du modèle de référentiel et du modèle d'unité de travail dans la documentation Microsoft https://docs.microsoft.com/fr-fr/aspnet/mvc/overview/older-versions/getting-started-with-ef- 5-using-mvc-4/implementation-le-repository-et-unit-of-work-patterns-in-an-asp-net-mvc-application – OrcusZ

+0

le bloc 'using' est un mal nécessaire parce que vous ouvrez les connexions à la base de données qui doivent être fermées. Donc la répétition est nécessaire. Je suggérerais seulement de ne pas être pris dans toute la substance modèle de conception de dépôt .... –

+0

@ Callum - quel autre modèle suggéreriez-vous ou pourriez-vous illustrer avec un exemple. J'avais regardé en utilisant CQRS mais je me sentais référentiel comme ci-dessus travaillé pour moi basé sur KISS –

Répondre

7

Bien sûr, une fonction pour créer et disposer votre connexion fonctionnera très bien.

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

Et votre site d'appel simplifié:

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

avec des valeurs de retour:

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

Dans votre site d'appel, il suffit d'écrire la logique que vous souhaitez utiliser.

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

Merci Silas. Je regarde ça. Recommanderiez-vous puttinh la méthode Execute dans un dépôt de base et tous les autres dépôts en héritent? Est-ce que query.Invoke fonctionnera pour n'importe quel type de Dapper db.function? –

+1

Si vous prévoyez avoir plusieurs classes qui ont besoin de cette fonctionnalité, un référentiel de base est une bonne idée. –

+1

L'appel de la requête/action fonctionnera pour tout ce que vous souhaitez faire avec IDbConnection, y compris les méthodes Dapper et les méthodes non-Dapper. –

3

Ceci n'est pas directement lié à votre question. Mais je vous suggère d'envisager d'utiliser DapperExtensions.

Au départ, j'ai implémenté le modèle de référentiel à l'aide de Dapper. L'inconvénient était que, je dois écrire des requêtes partout; C'était très filandreux. En raison de requêtes codées en dur, il était presque impossible d'écrire un référentiel générique.

Récemment, j'ai mis à jour mon code pour utiliser DapperExtensions. Cela corrige beaucoup de problèmes.

ci-après le dépôt générique:

public abstract class BaseRepository<T> 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<T>(null); 
     return result; 
    } 

    protected T GetById(Guid id) 
    { 
     var result = dapperExtensionsProxy.Get<T>(id); 
     return result; 
    } 
    protected T GetById(string id) 
    { 
     var result = dapperExtensionsProxy.Get<T>(id); 
     return result; 
    } 

    protected List<T> GetList() 
    { 
     var result = dapperExtensionsProxy.GetList<T>(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<IPredicate>() }; 
     var result = dapperExtensionsProxy.Delete<T>(predicateGroup);//Send empty predicateGroup to delete all records. 
    } 

Comme vous pouvez le voir dans le code ci-dessus, la plupart des méthodes sont emballage un peu plus sous-jacente DapperExtensionsProxy classe. DapperExtensionsProxy gère également en interne UnitOfWork que vous pouvez voir ci-dessous. Ces deux classes peuvent être combinées sans problème. Personnellement, je préfère les garder séparés.

Vous pouvez également remarquer que d'autres méthodes Exists, DeleteById et DeleteAll sont mis en œuvre ce ne sont pas une partie de DapperExtensionsProxy.

La méthode poco.SetDbId est définie dans chaque classe POCO pour définir sa propriété Identifier. Dans mon cas, les identifiants de POCO peuvent avoir des types de données et des noms différents.

Après est DapperExtensionsProxy:

internal sealed class DapperExtensionsProxy 
{ 
    internal DapperExtensionsProxy(IUnitOfWork unitOfWork) 
    { 
     this.unitOfWork = unitOfWork; 
    } 

    IUnitOfWork unitOfWork = null; 

    internal int Count<T>(object predicate) where T : BasePoco 
    { 
     var result = unitOfWork.Connection.Count<T>(predicate, unitOfWork.Transaction); 
     return result; 
    } 

    internal T Get<T>(object id) where T : BasePoco 
    { 
     var result = unitOfWork.Connection.Get<T>(id, unitOfWork.Transaction); 
     return result; 
    } 

    internal IEnumerable<T> GetList<T>(object predicate, IList<ISort> sort = null, bool buffered = false) where T : BasePoco 
    { 
     var result = unitOfWork.Connection.GetList<T>(predicate, sort, unitOfWork.Transaction, null, buffered); 
     return result; 
    } 

    internal IEnumerable<T> GetPage<T>(object predicate, int page, int resultsPerPage, IList<ISort> sort = null, bool buffered = false) where T : BasePoco 
    { 
     var result = unitOfWork.Connection.GetPage<T>(predicate, sort, page, resultsPerPage, unitOfWork.Transaction, null, buffered); 
     return result; 
    } 

    internal dynamic Insert<T>(T poco) where T : BasePoco 
    { 
     var result = unitOfWork.Connection.Insert<T>(poco, unitOfWork.Transaction); 
     return result; 
    } 

    internal void Insert<T>(IEnumerable<T> listPoco) where T : BasePoco 
    { 
     unitOfWork.Connection.Insert<T>(listPoco, unitOfWork.Transaction); 
    } 

    internal bool Update<T>(T poco) where T : BasePoco 
    { 
     var result = unitOfWork.Connection.Update<T>(poco, unitOfWork.Transaction); 
     return result; 
    } 

    internal bool Delete<T>(T poco) where T : BasePoco 
    { 
     var result = unitOfWork.Connection.Delete<T>(poco, unitOfWork.Transaction); 
     return result; 
    } 

    internal bool Delete<T>(object predicate) where T : BasePoco 
    { 
     var result = unitOfWork.Connection.Delete<T>(predicate, unitOfWork.Transaction); 
     return result; 
    } 
} 

Il utilise également UnitOfWork qui est expliqué here.

+0

Nice @Amit Joshi ... si j'ai plusieurs chaînes de connexion de base de données cela fonctionnera-t-il? Oui les pls suggèrent que des changements minimes doivent être faits? Pls suggèrent – Ljt

+1

@LajithKumar: Aucune modification ne sera nécessaire pour que cela fonctionne avec plusieurs instances de base de données. Notez que UnitOfWork est injecté dans le Repository comme ceci 'BaseRepository (IUnitOfWork unitOfWork)'. Vous pouvez créer un nouveau UnitOfWork pour chaque instance de base de données et l'injecter dans la même classe (nouvelle instance de Repository) sans aucune modification. –