web-dev-qa-db-fra.com

Modèle de conception de référentiel avec Dapper

C'est peut-être plus une question de révision de code que de débordement de pile.

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 DTO Proj qui représentent les données récupérées de la base de données ou enregistrées dans la base de données.

J'utilise le modèle de référentiel, donc au niveau de ma couche de service, si un référentiel est requis, j'utilise le constructeur DI pour injecter cette dépendance, puis j'appelle la méthode sur le référentiel pour faire le travail.

alors disons que j'ai 2 services appelés CustomerService et CarService.

J'ai ensuite 2 référentiels, un CustomerRepository et un CarRepository.

J'ai une interface qui définit toutes les méthodes de chaque référentiel puis les implémentations concrètes.

Un exemple de méthode est illustré ci-dessous (appel d'un processus stocké pour effectuer la DB INSERT (notez que la variable de chaîne réelle pour le processus stocké 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 de chaque référentiel. J'ai deux vraies questions décrites ci-dessous.

Existe-t-il une meilleure approche que je pourrais peut-être utiliser en utilisant d'une manière ou d'une autre une classe BaseRepository dont tous les autres référentiels héritent et la Base implémenterait l'instanciation de la connexion DB?

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

****MISE À JOUR****

Sur la base de la réponse de Silas, j'ai créé ce qui suit

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 référentiels, j'ai d'autres méthodes telles que les suivantes:

    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 référentiel de base en utilisant le type générique T afin que je puisse retourner tout type de DTO ou tout type natif C #

17
Ctrl_Alt_Defeat

Bien sûr, une fonction pour créer et éliminer 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, écrivez simplement 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));
}
18
Silas Reinagel

Cela ne concerne pas directement votre question. Mais je vous suggère d'envisager d'utiliser DapperExtensions.

Initialement, 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 filamenteux. 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 de nombreux problèmes.

Voici le référentiel 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 ne font qu'encapsuler la classe DapperExtensionsProxy sous-jacente. DapperExtensionsProxy gère également en interne UnitOfWork que vous pouvez voir ci-dessous. Ces deux classes peuvent être combinées sans aucun problème. Personnellement, je préfère les garder séparées.

Vous pouvez également remarquer que des méthodes supplémentaires Exists, DeleteById et DeleteAll sont implémentées et ne font pas partie de DapperExtensionsProxy.

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

Voici 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;
    }
}

Voici le BasePoco utilisé ci-dessus:

public abstract class BasePoco
{
    Guid pocoId = Guid.NewGuid();

    public Guid PocoId { get { return pocoId; } }

    public virtual void SetDbId(object id)
    {//Each POCO should override this method for specific implementation.
        throw new NotImplementedException("This method is not implemented by Poco.");
    }

    public override string ToString()
    {
        return PocoId + Environment.NewLine + base.ToString();
    }
}

Cela utilise également UnitOfWork qui est expliqué ici .

8
Amit Joshi