REMARQUE: la réponse parle d'Entity Framework DbContext
, mais
il est applicable à toute sorte d'Unité de Travail mise en œuvre, tels que
LINQ to SQL DataContext
, et NHibernate de l' ISession
.
Commençons par l'écho Ian: le fait d'Avoir un seul DbContext
pour l'ensemble de l'application est une Mauvaise Idée. La seule situation où cela a un sens, c'est quand vous avez un seul thread de l'application et une base de données qui est utilisé exclusivement par cette seule instance de l'application. L' DbContext
n'est pas thread-safe et et depuis l' DbContext
des caches de données, il devient obsolète assez rapidement. Cela vous aidera dans toutes sortes d'ennuis lorsque plusieurs utilisateurs/applications fonctionnent sur la base de données en même temps (ce qui est très courant). Mais je pense que vous savez déjà que et veux juste savoir pourquoi ne pas simplement injecter une nouvelle instance (c'est à dire avec une transition de mode de vie) de l' DbContext
dans tous ceux qui en ont besoin. (pour plus d'informations sur le pourquoi d'un seul DbContext
-ou même sur le contexte par thread est mauvais, lire cette réponse).
Permettez-moi de commencer par dire que l'inscription d'un DbContext
transitoire pourrait fonctionner, mais en général, vous voulez avoir une seule instance d'une unité de travail à l'intérieur d'un certain périmètre. Dans une application web, il peut être pratique de définir une telle portée sur les limites d'une requête web; ainsi, Par Requête Web de style de vie. Cela vous permet de laisser tout un ensemble d'objets fonctionnent dans le même contexte. En d'autres termes, ils fonctionnent dans la même transaction commerciale.
Si vous n'avez pas d'objectif de disposer d'un ensemble d'opérations qui opèrent dans le même contexte, dans ce cas, le transitoire mode de vie est bien, mais il ya quelques choses à regarder:
- Étant donné que chaque objet possède sa propre instance de chaque classe qui modifie l'état du système, les besoins à l'appel de
_context.SaveChanges()
(sinon, les modifications se perd). Cela peut compliquer votre code, et ajoute une seconde responsabilité pour le code (la responsabilité de la maîtrise du contexte), et constitue une violation du Principe de Responsabilité Unique.
- Vous devez vous assurer que les entités [chargé et enregistré par un
DbContext
] ne jamais laisser à la portée d'une telle classe, parce qu'ils ne peuvent pas être utilisés dans le cadre de l'instance d'une autre classe. Cela peut compliquer votre code énormément, parce que quand vous avez besoin de ces entités, vous avez besoin de les charger à nouveau par id, ce qui pourrait aussi causer des problèmes de performances.
- Depuis
DbContext
implémente IDisposable
, vous avez probablement encore voulez vous Débarrasser de toutes les instances. Si vous voulez faire cela, vous avez essentiellement deux options. Vous devez disposer de la même méthode à droite après l'appel de context.SaveChanges()
, mais dans ce cas, la logique d'entreprise prend possession d'un objet, celui-ci est transmis à partir de l'extérieur. La deuxième option est de Disposer de toutes les instances sur la frontière de la Requête Http, mais dans ce cas, vous avez encore besoin d'une sorte de délimitation de l'étendue de laisser le conteneur de savoir quand ces instances doivent être Éliminés.
Une autre option est de ne pas injecter un DbContext
. Au lieu de cela, vous pouvez injecter un DbContextFactory
qui est en mesure de créer une nouvelle instance (j'ai l'habitude d'utiliser cette approche dans le passé). De cette façon, la logique d'entreprise contrôle le contexte explicitement. Si pourrait ressembler à ceci:
public void SomeOperation()
{
using (var context = this.contextFactory.CreateNew())
{
var entities = this.otherDependency.Operate(
context, "some value");
context.Entities.InsertOnSubmit(entities);
context.SaveChanges();
}
}
Le côté positif, c'est que vous gérer la durée de vie de l' DbContext
explicitement et il est facile de le faire. Il vous permet également d'utiliser un seul contexte dans une certaine étendue, qui possède de nombreux avantages, tels que l'exécution de code dans une seule transaction commerciale, et d'être en mesure de passer autour des entités, car ils proviennent de la même DbContext
.
L'inconvénient est que vous aurez à passer autour de la DbContext
à partir de la méthode à la méthode (qui est appelé la Méthode d'Injection). Notez que dans un sens, cette solution est la même que la "portée", mais maintenant, le champ d'application est contrôlée dans le code de l'application elle-même (et est peut-être répété de nombreuses fois). C'est l'application qui est responsable de la création et de mise au rebut de l'unité de travail. Depuis l' DbContext
est créée après le graphe de dépendance est construit, Constructeur d'Injection est hors de l'image et vous devez vous reporter à la Méthode d'Injection lorsque vous devez passer sur le contexte d'une classe à l'autre.
Méthode d'Injection n'est pas mauvais, mais quand la logique métier devient de plus en plus complexe, et plus de classes s'impliquer, vous aurez à passer d'une méthode à l'autre et d'une classe à l'autre, ce qui peut compliquer le code beaucoup (j'ai vu cela dans le passé). Pour une application simple, cette approche fera l'amende juste.
Parce que des inconvénients, cette usine approche a pour des systèmes plus grands, une autre approche peut être utile et c'est le seul endroit où vous laissez le récipient ou le code d'infrastructure / de la Composition de la Racine de gérer l'unité de travail. C'est le style que votre question est à propos.
En laissant le récipient et/ou de l'infrastructure de gérer cela, le code de votre application n'est pas polluée par avoir à créer, (éventuellement) s'engager et de Disposer d'un UoW exemple, qui maintient la logique d'entreprise simple et propre (juste une Responsabilité Unique). Il y a quelques difficultés avec cette approche. Par exemple, faire de vous Engager et de Disposer de l'instance?
L'élimination d'une unité de travail peut être fait à la fin de la requête web. Beaucoup de gens cependant tort de supposer que tel est aussi le lieu de Commettre l'unité de travail. Cependant, à ce point de l'application, vous simplement ne peut pas déterminer pour assurer que l'unité de travail doit être réellement commis. par exemple, Si le code de l'application a généré une exception, vous avez certainement n'avez pas envie de s'Engager.
La vraie solution est de nouveau explicitement gérer une sorte de champ d'application, mais cette fois le faire à l'intérieur de la Composition de la Racine. L'abstraction de toute la logique métier derrière la commande / gestionnaire de modèle, vous serez capable d'écrire un décorateur qui peut être enroulé autour de chaque gestionnaire de commande qui permet de faire cela. Exemple:
class TransactionalCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
readonly DbContext context;
readonly ICommandHandler<TCommand> decorated;
public TransactionCommandHandlerDecorator(
DbContext context,
ICommandHandler<TCommand> decorated)
{
this.context = context;
this.decorated = decorated;
}
public void Handle(TCommand command)
{
this.decorated.Handle(command);
context.SaveChanges();
}
}
Cela garantit que vous avez seulement besoin d'écrire cette infrastructure de code à la fois. Tout solide DI conteneur permet de configurer un décorateur pour être enroulé autour de tous les ICommandHandler<T>
des implémentations de manière cohérente.