426 votes

Un DbContext par web demande... pourquoi?

J'ai lu beaucoup d'articles expliquant comment configurer l'Entity Framework DbContext alors qu'un seul est créé et utilisé par HTTP web demande l'aide de divers DI cadres.

Pourquoi est-ce une bonne idée en premier lieu? Quels avantages pouvez-vous obtenir par l'utilisation de cette approche? Certaines situations où ce serait une bonne idée? Existe-il des choses que vous pouvez faire en utilisant cette technique, vous ne pouvez pas faire lors de l'instanciation DbContexts par dépôt d'un appel de méthode?

Veuillez pardonner mon ignorance, mais j'essaye vraiment de comprendre pourquoi.

603voto

Steven Points 56939

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.

21voto

Ian Points 2455

Je suis assez certain que c'est parce que le DbContext n'est pas thread-safe. Donc le partage de la chose n'est jamais une bonne idée.

12voto

Miroslav Holec Points 446

Je suis d'accord avec les avis précédents. Il est bon de dire, que si vous allez partager DbContext dans un seul thread de l'application, vous aurez besoin de plus de mémoire. Par exemple, mon application web sur Azure (un petit extra exemple) besoin de l'autre de 150 MO de mémoire et j'ai environ 30 utilisateurs par heure. Application sharing DBContext in HTTP Request

Ici est l'exemple réel de l'image: application ont été déployés dans les 12H

3voto

RB. Points 17993

Ce que j'aime c'est qu'il aligne l'unité de travail (en tant que l'utilisateur le voit - c'est à dire une page soumettre) avec l'unité de travail dans l'ORM sens.

Par conséquent, vous pouvez faire la totalité de la page de soumission transactionnelle, qui vous ne pourriez pas faire si vous avez été exposer les méthodes CRUD avec chaque création d'un nouveau contexte.

-7voto

D Baldwin Points 1

Devrait Jetables travail de modèle?

class MyClass: IDisposable

  private MyDataModelDataContext _dbContext;

  // Disposable types implement a finalizer.
    #region Deconstucter
    protected virtual void Dispose(bool disposing)
    {
      if (disposing)
      {
        // Dispose managed resources
      }
      // Free native resources
      _dbContext.Dispose();
    }

    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    // Disposable types implement a finalizer.
    ~MyClass()
    {
      Dispose(false);
    }
    #endregion

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X