J'ai lu de nombreux articles expliquant comment configurer Entity Framework DbContext
afin qu'un seul soit créé et utilisé par demande Web HTTP à l'aide de divers cadres DI.
Pourquoi est-ce une bonne idée en premier lieu? Quels avantages retirez-vous de cette approche? Y a-t-il certaines situations où ce serait une bonne idée? Y a-t-il des choses que vous pouvez faire en utilisant cette technique que vous ne pouvez pas faire lors de l'instanciation de DbContext
s par appel de méthode de référentiel?
Réponses:
Commençons par faire écho à Ian: Avoir un single
DbContext
pour toute l'application est une mauvaise idée. La seule situation où cela a du sens est lorsque vous avez une application à thread unique et une base de données qui est uniquement utilisée par cette instance d'application unique. LeDbContext
n'est pas thread-safe et et puisque lesDbContext
données en cache, il devient périmé très bientôt. Cela vous mettra dans toutes sortes de problèmes lorsque plusieurs utilisateurs / applications travaillent simultanément sur cette base de données (ce qui est très courant bien sûr). Mais je m'attends à ce que vous le sachiez déjà et que vous vouliez simplement savoir pourquoi ne pas simplement injecter une nouvelle instance (c'est-à-dire avec un style de vie transitoire) de laDbContext
personne qui en a besoin. (pour plus d'informations sur les raisons pour lesquelles un singleDbContext
-ou même sur le contexte par thread- est mauvais, lisez cette réponse ).Permettez-moi de commencer par dire que l'enregistrement d'un
DbContext
transitoire pourrait fonctionner, mais généralement vous voulez avoir une seule instance d'une telle unité de travail dans une certaine portée. Dans une application Web, il peut être pratique de définir une telle portée sur les limites d'une demande Web; ainsi un style de vie par demande Web. Cela vous permet de laisser un ensemble d'objets fonctionner dans le même contexte. En d'autres termes, ils opèrent au sein de la même transaction commerciale.Si vous n'avez aucun objectif de faire fonctionner un ensemble d'opérations dans le même contexte, dans ce cas, le mode de vie transitoire est bien, mais il y a quelques choses à surveiller:
_context.SaveChanges()
(sinon les modifications seraient perdues). Cela peut compliquer votre code et ajoute une deuxième responsabilité au code (la responsabilité de contrôler le contexte) et constitue une violation du principe de responsabilité unique .DbContext
] ne quittent jamais la portée d'une telle classe, car elles ne peuvent pas être utilisées dans l'instance de contexte d'une autre classe. Cela peut compliquer énormément votre code, car lorsque vous avez besoin de ces entités, vous devez les charger à nouveau par id, ce qui pourrait également entraîner des problèmes de performances.DbContext
implémentationsIDisposable
, vous souhaiterez probablement toujours supprimer toutes les instances créées. Si vous voulez le faire, vous avez essentiellement deux options. Vous devez les disposer de la même méthode juste après l'appelcontext.SaveChanges()
, mais dans ce cas, la logique métier s'approprie un objet qu'il est transmis de l'extérieur. La deuxième option consiste à supprimer toutes les instances créées à la limite de la demande Http, mais dans ce cas, vous avez toujours besoin d'une sorte de portée pour indiquer au conteneur quand ces instances doivent être supprimées.Une autre option consiste à ne pas injecter
DbContext
du tout. Au lieu de cela, vous injectez unDbContextFactory
qui est capable de créer une nouvelle instance (j'avais l'habitude d'utiliser cette approche dans le passé). De cette façon, la logique métier contrôle explicitement le contexte. Si cela pourrait ressembler à ceci:Le côté positif de cela est que vous gérez la vie de manière
DbContext
explicite et il est facile de configurer cela. Il vous permet également d'utiliser un contexte unique dans une certaine portée, ce qui présente des avantages évidents, tels que l'exécution de code dans une seule transaction commerciale et la possibilité de contourner des entités, car elles proviennent de la mêmeDbContext
.L'inconvénient est que vous devrez contourner la
DbContext
méthode from to method (qui est appelée méthode d'injection). Notez que dans un sens, cette solution est la même que l'approche «portée», mais maintenant la portée est contrôlée dans le code d'application lui-même (et peut-être répétée plusieurs fois). C'est l'application qui est responsable de la création et de l'élimination de l'unité de travail. Étant donné que leDbContext
est créé après la construction du graphique de dépendance, l'injection de constructeur est hors de l'image et vous devez vous reporter à l'injection de méthode lorsque vous devez transmettre le contexte d'une classe à l'autre.L'injection de méthodes n'est pas si mauvaise, mais lorsque la logique métier devient plus complexe et que davantage de classes sont impliquées, vous devrez la passer de méthode en méthode et de classe en classe, ce qui peut compliquer beaucoup le code (j'ai vu cela dans le passé). Pour une application simple, cette approche fera très bien l'affaire cependant.
En raison des inconvénients, cette approche d'usine s'applique aux systèmes plus gros, une autre approche peut être utile et c'est celle où vous laissez le conteneur ou le code d'infrastructure / Racine de composition gérer l'unité de travail. C'est le style sur lequel porte votre question.
En laissant le conteneur et / ou l'infrastructure gérer cela, votre code d'application n'est pas pollué en devant créer, (éventuellement) valider et supprimer une instance UoW, ce qui maintient la logique métier simple et propre (juste une responsabilité unique). Il y a quelques difficultés avec cette approche. Par exemple, avez-vous validé et supprimé l'instance?
L'élimination d'une unité de travail peut être effectuée à la fin de la demande Web. Cependant, beaucoup de gens supposent à tort que c'est aussi le lieu pour engager l'unité de travail. Cependant, à ce stade de la demande, vous ne pouvez tout simplement pas déterminer avec certitude que l'unité d'oeuvre doit réellement être engagée. Par exemple, si le code de la couche métier a levé une exception qui a été interceptée plus haut dans la pile d'appels, vous ne voulez certainement pas vous engager .
La vraie solution est à nouveau de gérer explicitement une sorte de portée, mais cette fois-ci, faites-le à l'intérieur de la racine de composition. En résumant toute la logique métier derrière le modèle de commande / gestionnaire , vous pourrez écrire un décorateur qui peut être enroulé autour de chaque gestionnaire de commandes qui permet de le faire. Exemple:
Cela garantit que vous n'avez besoin d'écrire ce code d'infrastructure qu'une seule fois. Tout conteneur DI solide vous permet de configurer un tel décorateur pour qu'il soit enroulé autour de toutes les
ICommandHandler<T>
implémentations de manière cohérente.la source
CreateCommand<TEnity>
et un génériqueCreateCommandHandler<TEntity> : ICommandHandler<CreateCommand<TEntity>>
(et faire de même pour Mettre à jour et Supprimer et avoir une seuleGetByIdQuery<TEntity>
requête). Cependant, vous devez vous demander si ce modèle est une abstraction utile pour les opérations CRUD, ou s'il ajoute simplement de la complexité. Néanmoins, vous pourriez bénéficier de la possibilité d'ajouter facilement des préoccupations transversales (par le biais de décorateurs) en utilisant ce modèle. Vous devrez peser le pour et le contre.TransactionCommandHandlerDecorator
? par exemple, si la classe décorée est uneInsertCommandHandler
classe, comment pourrait-elle enregistrer l'opération d'insertion dans le contexte (DbContext dans EF)?Il existe deux recommandations contradictoires de Microsoft et de nombreuses personnes utilisent DbContexts de manière complètement divergente.
Ceux-ci se contredisent parce que si votre demande fait beaucoup de choses sans rapport avec le contenu Db, votre DbContext est conservé sans raison. Il est donc inutile de garder votre DbContext en vie alors que votre demande n'attend que des choses aléatoires pour se faire ...
Tant de personnes qui suivent la règle 1 ont leurs DbContexts à l'intérieur de leur "modèle de référentiel" et créent une nouvelle instance par requête de base de données afin X * DbContext par demande
Ils obtiennent simplement leurs données et éliminent le contexte dès que possible. Ceci est considéré par beaucoup de gens comme une pratique acceptable. Bien que cela ait les avantages d'occuper vos ressources de base de données pour le minimum de temps, cela sacrifie clairement tout ce que EF UnitOfWork et Caching Candy a à offrir.
Garder en vie une seule instance polyvalente de DbContext maximise les avantages de la mise en cache, mais comme DbContext n'est pas sûr pour les threads et que chaque demande Web s'exécute sur son propre thread, un DbContext par demande est le plus long que vous pouvez le conserver.
Ainsi, la recommandation de l'équipe d'EF concernant l'utilisation de 1 Db Context par demande est clairement basée sur le fait que dans une application Web, UnitOfWork va très probablement être dans une demande et que cette demande a un thread. Ainsi, un DbContext par demande est comme l'avantage idéal de UnitOfWork et Caching.
Mais dans de nombreux cas, ce n'est pas vrai. Je considère que la journalisation d' un UnitOfWork distinct ayant ainsi un nouveau DbContext pour la journalisation post-requête dans les threads asynchrones est tout à fait acceptable
Finalement, il s'avère que la durée de vie d'un DbContext est limitée à ces deux paramètres. UnitOfWork et Thread
la source
Pas une seule réponse ici ne répond réellement à la question. Le PO n'a pas posé de questions sur une conception DbContext singleton / par application, il a posé des questions sur une conception de demande par (Web) et sur les avantages potentiels qui pourraient exister.
Je ferai référence à http://mehdi.me/ambient-dbcontext-in-ef6/ car Mehdi est une ressource fantastique:
Gardez à l'esprit qu'il y a aussi des inconvénients. Ce lien contient de nombreuses autres ressources à lire sur le sujet.
Il suffit de poster ceci au cas où quelqu'un d'autre tomberait sur cette question et ne serait pas absorbé par des réponses qui ne répondent pas réellement à la question.
la source
Je suis presque certain que c'est parce que le DbContext n'est pas du tout sûr pour les threads. Partager la chose n'est donc jamais une bonne idée.
la source
Une chose qui n'est pas vraiment abordée dans la question ou la discussion est le fait que DbContext ne peut pas annuler les modifications. Vous pouvez soumettre des modifications, mais vous ne pouvez pas effacer l'arborescence des modifications, donc si vous utilisez un contexte par demande, vous n'avez pas de chance si vous devez annuler les modifications pour une raison quelconque.
Personnellement, je crée des instances de DbContext lorsque cela est nécessaire - généralement attaché à des composants métier qui ont la capacité de recréer le contexte si nécessaire. De cette façon, j'ai le contrôle sur le processus, plutôt que d'avoir une seule instance imposée à moi. Je n'ai pas non plus à créer le DbContext à chaque démarrage du contrôleur, qu'il soit réellement utilisé ou non. Ensuite, si je veux toujours avoir des instances par demande, je peux les créer dans le CTOR (via DI ou manuellement) ou les créer selon les besoins dans chaque méthode de contrôleur. Personnellement, je prends généralement cette dernière approche pour éviter de créer des instances DbContext lorsqu'elles ne sont pas réellement nécessaires.
Cela dépend de quel angle vous le regardez aussi. Pour moi, l'instance par demande n'a jamais eu de sens. Le DbContext appartient-il vraiment à la demande Http? En termes de comportement, ce n'est pas le bon endroit. Vos composants métier doivent créer votre contexte, pas la demande Http. Ensuite, vous pouvez créer ou jeter vos composants métier selon vos besoins et ne vous souciez jamais de la durée de vie du contexte.
la source
Je suis d'accord avec les avis précédents. Il est bon de dire que si vous souhaitez partager DbContext dans une application à un seul thread, vous aurez besoin de plus de mémoire. Par exemple, mon application Web sur Azure (une petite instance supplémentaire) a besoin de 150 Mo de mémoire supplémentaires et j'ai environ 30 utilisateurs par heure.
Voici un exemple réel d'image: l'application a été déployée en 12PM
la source
Ce que j'aime à ce sujet, c'est qu'il aligne l'unité de travail (telle que l'utilisateur la voit - c'est-à-dire une page soumise) avec l'unité de travail au sens ORM.
Par conséquent, vous pouvez rendre la soumission de page entière transactionnelle, ce que vous ne pourriez pas faire si vous exposiez des méthodes CRUD à chaque création d'un nouveau contexte.
la source
Une autre raison sous-estimée de ne pas utiliser un DbContext singleton, même dans une application mono-utilisateur unique, est due au modèle de carte d'identité qu'il utilise. Cela signifie que chaque fois que vous récupérez des données à l'aide d'une requête ou d'un identifiant, il conserve les instances d'entité récupérées dans le cache. La prochaine fois que vous récupérerez la même entité, elle vous donnera l'instance mise en cache de l'entité, si disponible, avec toutes les modifications que vous avez apportées dans la même session. Cela est nécessaire pour que la méthode SaveChanges ne se retrouve pas avec plusieurs instances d'entité différentes des mêmes enregistrements de base de données; sinon, le contexte devrait d'une manière ou d'une autre fusionner les données de toutes ces instances d'entité.
La raison qui pose problème est qu'un DbContext singleton peut devenir une bombe à retardement qui pourrait éventuellement mettre en cache toute la base de données + la surcharge des objets .NET en mémoire.
Il existe des moyens de contourner ce comportement en utilisant uniquement des requêtes Linq avec la
.NoTracking()
méthode d'extension. De plus, de nos jours, les PC ont beaucoup de RAM. Mais ce n'est généralement pas le comportement souhaité.la source
Un autre problème à surveiller avec Entity Framework en particulier est lors de l'utilisation d'une combinaison de création de nouvelles entités, de chargement différé, puis d'utilisation de ces nouvelles entités (à partir du même contexte). Si vous n'utilisez pas IDbSet.Create (vs juste nouveau), le chargement paresseux sur cette entité ne fonctionne pas lorsqu'il est récupéré hors du contexte dans lequel il a été créé. Exemple:
la source