web-dev-qa-db-fra.com

Comment dois-je gérer DbContext Lifetime dans MVC Core?

Depuis Documentation

Les contextes Entity Framework doivent être ajoutés au conteneur de services à l'aide de la durée de vie Scoped. Ceci est pris en charge automatiquement si vous utilisez les méthodes d'assistance comme indiqué ci-dessus. Les référentiels qui utiliseront Entity Framework doivent utiliser la même durée de vie.

J'ai toujours pensé que je devrais créer un nouveau Context pour chaque unité de travail que je dois traiter. Cela me laisse penser, si j'ai un ServiceA et ServiceB, qui appliquent des actions différentes sur le DbContext qu'ils devraient obtenir une instance différente de DbContext .

documentation se lit comme suit:

  • Transient les objets sont toujours différents; une nouvelle instance est fournie à chaque contrôleur et à chaque service.

  • Scoped les objets sont les mêmes dans une demande, mais différents d'une demande à l'autre

Pour en revenir à ServiceA et ServiceB, cela me semble, Transient est plus approprié.

J'ai recherché que le contexte ne devrait être enregistré qu'une seule fois par HttpRequest, mais je ne comprends vraiment pas comment cela fonctionne.

Surtout si nous examinons un service:

using (var transaction = dbContext.Database.BeginTransaction())
{
    //Create some entity
    var someEntity = new SomeEntity();
    dbContext.SomeEntity.Add(someEntity);

    //Save in order to get the the id of the entity
    dbContext.SaveChanges();

    //Create related entity
    var relatedEntity = new RelatedEntity
    {
        SomeEntityId = someEntity.Id
    };
    dbContext.RelatedEntity.Add(relatedEntity)
    dbContext.SaveChanges();
    transaction.Commit();
}

Ici, nous devons enregistrer le contexte afin d'obtenir l'ID d'une entité qui est liée à une autre que nous venons de créer.

Dans le même temps, un autre service pourrait mettre à jour le même contexte. D'après ce que j'ai lu, DbContext n'est pas sûr pour les threads.

Dois-je utiliser Transient dans ce cas? Pourquoi la documentation suggère-t-elle que devrais-je utiliser Scoped?

Est-ce que je manque une partie importante du cadre?

17
Christian Gollhardt

Comme d'autres l'ont déjà expliqué, vous devez utiliser une dépendance de portée pour les contextes de base de données pour vous assurer qu'elle sera correctement réutilisée. Pour la concurrence, n'oubliez pas que vous pouvez également interroger la base de données de manière asynchrone, de sorte que vous n'aurez peut-être pas besoin de threads réels.

Si vous avez besoin de threads, c'est-à-dire des travailleurs en arrière-plan, il est probable que ceux-ci auront une durée de vie différente de la demande. En tant que tels, ces threads ne devraient pas utiliser des dépendances récupérées à partir de la portée de la demande. Lorsque la demande se termine et que son étendue de dépendance est fermée, les dépendances jetables sont correctement supprimées. Pour d'autres threads, cela signifierait que leurs dépendances pourraient finir par être supprimées bien qu'elles en aient encore besoin: Mauvaise idée.

Au lieu de cela, vous devez ouvrir explicitement une nouvelle étendue de dépendance pour chaque thread que vous créez. Vous pouvez le faire en injectant le IServiceScopeFactory et en créant une étendue à l'aide de CreateScope . L'objet résultant contiendra alors un fournisseur de services à partir duquel vous pourrez récupérer vos dépendances. Comme il s'agit d'une étendue distincte, les dépendances étendues comme les contextes de base de données seront recréées pour la durée de vie de cette étendue.

Afin d'éviter d'entrer dans le modèle de localisateur de service, vous devriez envisager d'avoir un service central exécuté par votre thread qui rassemble toutes les dépendances nécessaires. Le thread pourrait alors faire ceci:

using (var scope = _scopeFactory.CreateScope())
{
    var service = scope.ServiceProvider.GetService<BackgroundThreadService>();
    service.Run();
}

Le BackgroundThreadService et toutes ses dépendances peuvent alors suivre la façon courante d'injecter des dépendances pour recevoir des dépendances.

23
poke

Je pense que vous ne rencontreriez pas de problèmes de concurrence dans la plupart des cas lorsque vous utilisez une durée de vie limitée. Même dans l'exemple que vous avez publié, il n'y a aucun problème de concurrence, car les services de la demande actuelle seront appelés par la suite. Je ne peux même pas imaginer le cas où vous exécuterez 2 services ou plus en parallèle (c'est possible mais pas habituel) dans le contexte d'une demande HTTP (portée).

À vie, c'est juste un moyen de stocker vos données (pour être simple ici). Il suffit de regarder certains gestionnaires de vie dans les frameworks DI populaires, ils fonctionnent tous à peu près de la même manière - il s'agit simplement d'objets de dictionnaire qui implémentent un modèle jetable. En utilisant Transient, je crois que votre méthode d'objet get retournera toujours null, donc DI créera une nouvelle instance chaque fois qu'elle le demandera. SingleInstance stockera les objets dans quelque chose comme un dictionnaire simultané statique afin que le conteneur ne crée une instance qu'une seule fois, puis en recevra une existante.

La portée est généralement un objet de portée moyenne est utilisé pour stocker les objets créés. Dans asp net pipeline, cela signifie généralement la même chose que par demande (car la portée peut être transmise via le pipeline)

Pour être bref - ne vous inquiétez pas, utilisez simplement scoped, il est sûr et vous pouvez appeler cela selon la demande.

J'ai essayé d'être très simple dans mon explication, vous pouvez toujours consulter le code source pour trouver les détails de correspondance dont vous avez besoin ici https://github.com/aspnet/DependencyInjection

2
Alex Lyalka