web-dev-qa-db-fra.com

Dapper avec .NET Core - durée de vie / portée SqlConnection injectée

J'utilise .NET Core Dependency Injection pour instancier un objet SqlConnection lors du démarrage de l'application, que je prévois ensuite d'injecter dans mon référentiel. Ce SqlConnection sera utilisé par Dapper pour lire/écrire des données à partir de la base de données dans l'implémentation de mon référentiel. Je vais utiliser les appels async avec Dapper.

La question est: dois-je injecter le SqlConnection comme transitoire ou comme singleton? Compte tenu du fait que je veux utiliser async, ma pensée serait d'utiliser transitoire à moins que Dapper n'implémente des conteneurs d'isolation en interne et que la portée de mon singleton soit toujours enveloppée dans la portée que Dapper utilise en interne.

Existe-t-il des recommandations/meilleures pratiques concernant la durée de vie de l'objet SqlConnection lorsque vous travaillez avec Dapper? Y a-t-il des mises en garde que je pourrais manquer?

Merci d'avance.

13
Phil P.

Si vous fournissez une connexion SQL en tant que singleton, vous ne pourrez pas servir plusieurs demandes en même temps, sauf si vous activez MARS, qui a également ses limites. La meilleure pratique consiste à utiliser une connexion SQL transitoire et à s'assurer qu'elle est correctement éliminée.

Dans mes applications, je passe des IDbConnectionFactory personnalisés aux référentiels qui sont utilisés pour créer une connexion à l'intérieur de l'instruction using. Dans ce cas, le référentiel lui-même peut être singleton pour réduire les allocations sur le tas.

6
Andrii Litvinov

Je suis d'accord avec @Andrii Litvinov, à la fois réponse et commentaire.

Dans ce cas, j'irais avec l'approche d'une fabrique de connexions spécifique à la source de données.

Avec la même approche, je mentionne une manière différente - UnitOfWork.

Référez DalSession et UnitOfWork de this réponse. Cela gère la connexion.
Référez BaseDal de this réponse. Ceci est mon implémentation de Repository (en fait BaseRepository).

  • UnitOfWork est injecté comme transitoire.
  • Plusieurs sources de données peuvent être gérées en créant un DalSession distinct pour chaque source de données.
  • UnitOfWork est injecté dans BaseDal.

Existe-t-il des recommandations/meilleures pratiques concernant la durée de vie de l'objet SqlConnection lorsque vous travaillez avec Dapper?

La plupart des développeurs conviennent que la connexion doit être aussi courte que possible. Je vois deux approches ici:

  1. Connexion par action.
    Bien entendu, cette durée de connexion sera la plus courte. Vous placez la connexion dans le bloc using pour chaque action. C'est une bonne approche tant que vous ne voulez pas grouper les actions. Même lorsque vous souhaitez regrouper les actions, vous pouvez utiliser la transaction dans la plupart des cas.
    Le problème survient lorsque vous souhaitez regrouper des actions sur plusieurs classes/méthodes. Vous ne pouvez pas utiliser le bloc using ici. La solution est UnitOfWork comme ci-dessous.
  2. Connexion par unité de travail.
    Définissez votre unité d'oeuvre. Ce sera différent par application. Dans les applications Web, la "connexion par demande" est une approche largement utilisée.
    Cela a plus de sens car, en général, il y a (la plupart du temps) des groupes d'actions que nous voulons effectuer dans leur ensemble. Ceci est expliqué dans deux liens que j'ai fournis ci-dessus.
    Un autre avantage de cette approche est que l'application (qui utilise DAL) obtient plus de contrôle sur la façon dont la connexion doit être utilisée. Et à ma connaissance, l'application sait mieux que DAL comment la connexion doit être utilisée.
4
Amit Joshi

Grande question, et déjà deux bonnes réponses. J'ai été perplexe au début et j'ai trouvé la solution suivante pour résoudre le problème, qui encapsule les référentiels dans un gestionnaire. Le gestionnaire lui-même est chargé d'extraire la chaîne de connexion et de l'injecter dans les référentiels.

J'ai trouvé cette approche pour rendre les tests des référentiels individuellement, par exemple dans une application de console fictive, beaucoup plus simples, et j'ai beaucoup de chance de suivre ce modèle sur plusieurs projets à plus grande échelle. Bien que je ne sois certes pas un expert des tests, de l'injection de dépendance, ou bien quoi que ce soit vraiment!

La principale question que je me pose est de savoir si le DbService doit être un singleton ou non. Mon raisonnement était que, il était inutile de créer et de détruire constamment les différents référentiels encapsulés dans DbService et comme ils sont tous apatrides, je ne voyais pas beaucoup de problème à leur permettre de "vivre". Bien que cela puisse être une logique totalement invalide.

EDIT: Si vous voulez une solution prête à l'emploi, consultez mon implémentation du référentiel Dapper sur GitHub

Le gestionnaire de référentiel est structuré comme suit:

/*
 * Db Service
 */
public interface IDbService
{
    ISomeRepo SomeRepo { get; }
}

public class DbService : IDbService
{
    readonly string connStr;
    ISomeRepo someRepo;

    public DbService(string connStr)
    {
        this.connStr = connStr;
    }

    public ISomeRepo SomeRepo
    {
        get
        {
            if (someRepo == null)
            {
                someRepo = new SomeRepo(this.connStr);
            }

            return someRepo;
        }
    }
}

Un exemple de référentiel serait structuré comme suit:

/*
 * Mock Repo
 */
public interface ISomeRepo
{
    IEnumerable<SomeModel> List();
}

public class SomeRepo : ISomeRepo
{
    readonly string connStr;

    public SomeRepo(string connStr)
    {
        this.connStr = connStr;
    }

    public IEnumerable<SomeModel> List()
    {
        //work to return list of SomeModel 
    }
}

Tout câbler:

/*
 * Startup.cs
 */
public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    //...rest of services

    services.AddSingleton<IDbService, DbService>();

    //...rest of services
}

Et enfin, en l'utilisant:

public SomeController : Controller 
{
    IDbService dbService;

    public SomeController(IDbService dbService)
    {
        this.dbService = dbService;
    }

    public IActionResult Index()
    {
        return View(dbService.SomeRepo.List());
    }
}
1
robopim