web-dev-qa-db-fra.com

Structure d'entité c #: utilisation correcte de la classe DBContext dans votre classe de référentiel

J'avais l'habitude d'implémenter mes classes de référentiel comme vous pouvez le voir ci-dessous

public Class MyRepository
{
      private MyDbContext _context; 

      public MyRepository(MyDbContext context)
      {
          _context = context;
      }

      public Entity GetEntity(Guid id)
      {
          return _context.Entities.Find(id);
      }
}

Cependant, j'ai récemment lu cet article qui dit que c'est une mauvaise pratique d'avoir un contexte de données en tant que membre privé dans votre référentiel: http://devproconnections.com/development/solving-net-scalability-problem

Maintenant, théoriquement, l'article est correct: puisque DbContext implémente IDisposable, l'implémentation la plus correcte serait la suivante.

public Class MyRepository
{
      public Entity  GetEntity(Guid id)
      {
          using (MyDbContext context = new MyDBContext())
          {
              return context.Entities.Find(id);
          }
      }
}

Cependant, selon cet autre article, disposer de DbContext ne serait pas essentiel: http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html

Lequel des deux articles a raison? Je suis assez confus. Avoir DbContext en tant que membre privé dans votre classe de référentiel peut vraiment causer des "problèmes d'évolutivité" comme le suggère le premier article?

68
Errore Fatale

Je pense que vous ne devriez pas suivre le premier article, et je vais vous dire pourquoi.

Suite à la première approche, vous perdez à peu près toutes les fonctionnalités fournies par Entity Framework via le DbContext, y compris son cache de premier niveau, sa carte d’identité, son unité de travail, son suivi des modifications et sa gestion paresseuse. capacités de chargement. En effet, dans le scénario ci-dessus, une nouvelle instance DbContext est créée pour chaque requête de base de données et supprimée immédiatement après, ce qui empêche l’instance DbContext de pouvoir suivre l’état de vos objets de données à travers le système. transaction commerciale entière.

Le fait d'avoir DbContext en tant que propriété privée dans votre classe de référentiel pose également des problèmes. Je crois que la meilleure approche est d'avoir un CustomDbContextScope. Cette approche est très bien expliquée par ce gars: Mehdi El Gueddari

Cet article http://mehdi.me/ambient-dbcontext-in-ef6/ l'un des meilleurs articles sur EntityFramework que j'ai vu. Vous devriez le lire entièrement et je pense qu'il répondra à toutes vos questions.

38
Fabio Luz

Supposons que vous avez plusieurs référentiels et que vous devez mettre à jour 2 enregistrements de différents référentiels. Et vous devez le faire de manière transactionnelle (le cas échéant, les annulations de mises à jour):

var repositoryA = GetRepository<ClassA>();
var repositoryB = GetRepository<ClassB>();

repository.Update(entityA);
repository.Update(entityB);

Ainsi, si vous avez propre DbContext pour chaque référentiel (cas 2), vous devez utiliser TransactionScope pour y parvenir.

Meilleure façon - avoir un DbContext partagé pour une opération (pour un appel, pour un nité de travail ). Donc, DbContext peut gérer les transactions. EF est tout à fait pour ça. Vous ne pouvez créer qu'un seul DbContext, apporter toutes les modifications dans de nombreux référentiels, appeler une fois SaveChanges, le supprimer une fois toutes les opérations et le travail terminés.

Here est un exemple d'implémentation de modèle UnitOfWork.

Votre deuxième manière peut être utile pour les opérations en lecture seule.

18
Backs

La règle racine est la suivante: votre durée de vie DbContext doit être limitée à la transaction que vous exécutez .

Ici, "transaction" peut faire référence à une requête en lecture seule ou à une requête en écriture. Et comme vous le savez peut-être déjà, une transaction doit être aussi courte que possible.

Cela dit, je dirais que vous devriez privilégier la méthode "utiliser" dans la plupart des cas et ne pas utiliser de membre privé.

Le seul cas que je vois pour utiliser un membre privé concerne un modèle CQRS (CQRS: un examen croisé de son fonctionnement) .

À propos, la réponse de Diego Vega dans le post de Jon Gallant donne également quelques conseils avisés:

Il existe deux raisons principales pour lesquelles notre exemple de code utilise toujours "utiliser" ou élimine le contexte d'une autre manière:

  1. Le comportement d'ouverture/fermeture automatique par défaut est relativement facile à remplacer: vous pouvez assumer le contrôle du moment où la connexion est ouverte et fermée en ouvrant manuellement la connexion. Une fois que vous avez commencé à faire cela dans une partie de votre code, oublier de définir le contexte devient néfaste, car vous risquez de perdre des connexions ouvertes.

  2. DbContext implémente IDiposable en suivant le modèle recommandé, qui inclut l'exposition d'une méthode Dispose protégée virtuelle que les types dérivés peuvent remplacer si, par exemple, le besoin d'agréger d'autres ressources non managées dans la durée de vie du contexte.

HTH

12
Dude Pascalou

Quelle approche utiliser dépend de la responsabilité du référentiel.

La responsabilité du référentiel est-elle d'exécuter des transactions complètes? c'est-à-dire apporter des modifications puis enregistrer les modifications dans la base de données en appelant `SaveChanges? Ou est-ce seulement une partie d'une transaction plus importante et par conséquent, il ne fera que des modifications sans les sauvegarder?

Cas n ° 1) Le référentiel exécutera des transactions complètes (il apportera des modifications et les enregistrera):

Dans ce cas, la deuxième approche est meilleure (l'approche de votre deuxième exemple de code).

Je ne modifierai que légèrement cette approche en introduisant une usine comme celle-ci:

public interface IFactory<T>
{
    T Create();
}

public class Repository : IRepository
{
    private IFactory<MyContext> m_Factory;

    public Repository(IFactory<MyContext> factory)
    {
        m_Factory = factory;
    }

    public void AddCustomer(Customer customer)
    {
        using (var context = m_Factory.Create())
        {
            context.Customers.Add(customer);
            context.SaveChanges();
        }            
    }
}

Je fais ce léger changement pour activer injection de dépendance . Cela nous permet de modifier ultérieurement la manière dont nous créons le contexte.

Je ne veux pas que le référentiel ait la responsabilité de créer le contexte lui-même. L'usine qui implémente IFactory<MyContext> aura la responsabilité de créer le contexte.

Remarquez comment le référentiel gère la durée de vie du contexte, il crée le contexte, apporte certaines modifications, enregistre les modifications, puis élimine le contexte. La durée de conservation du référentiel est supérieure à celle du contexte dans ce cas.

Cas n ° 2) Le référentiel fait partie d'une transaction plus importante (il apportera certaines modifications, d'autres référentiels en feront d'autres, puis quelqu'un d'autre va valider la transaction en appelant SaveChanges):

Dans ce cas, la première approche (que vous décrivez en premier dans votre question) est meilleure.

Imaginez ceci pour comprendre comment le référentiel peut faire partie d’une transaction plus importante:

using(MyContext context = new MyContext ())
{
    repository1 = new Repository1(context);
    repository1.DoSomething(); //Modify something without saving changes
    repository2 = new Repository2(context);
    repository2.DoSomething(); //Modify something without saving changes

    context.SaveChanges();
}

Veuillez noter qu'une nouvelle instance du référentiel est utilisée pour chaque transaction. Cela signifie que la durée de vie du référentiel est très courte.

Veuillez noter que je suis en train de créer un nouveau référentiel dans mon code (ce qui constitue une violation de l'injection de dépendance). Je montre juste ceci à titre d'exemple. Dans le code réel, nous pouvons utiliser des usines pour résoudre ce problème.

Maintenant, une amélioration que nous pouvons faire à cette approche est de cacher le contexte derrière une interface afin que le référentiel n’ait plus accès à SaveChanges (regardez le principe de séparation d’interface ).

Vous pouvez avoir quelque chose comme ça:

public interface IDatabaseContext
{
    IDbSet<Customer> Customers { get; }
}

public class MyContext : DbContext, IDatabaseContext
{
    public IDbSet<Customer> Customers { get; set; }
}


public class Repository : IRepository
{
    private IDatabaseContext m_Context;

    public Repository(IDatabaseContext context)
    {
        m_Context = context;
    }

    public void AddCustomer(Customer customer)
    {
        m_Context.Customers.Add(customer);      
    }
}

Vous pouvez ajouter d'autres méthodes dont vous avez besoin à l'interface si vous le souhaitez.

Veuillez également noter que cette interface n'hérite pas de IDisposable. Ce qui signifie que la classe Repository n'est pas responsable de la gestion de la durée de vie du contexte. Le contexte dans ce cas a une durée de vie plus grande que celle du référentiel. Quelqu'un d'autre gérera la durée de vie du contexte.

Notes sur le premier article:

Le premier article suggère que vous ne devriez pas utiliser la première approche que vous décrivez dans votre question (injecter le contexte dans le référentiel).

L'article n'est pas clair sur la façon dont le référentiel est utilisé. Est-ce utilisé dans le cadre d'une transaction unique? Ou cela couvre-t-il plusieurs transactions?

Je suppose (je ne suis pas sûr) que, dans l'approche décrite dans l'article (de manière négative), le référentiel est utilisé comme un service de longue durée qui couvrira de nombreuses transactions. Dans ce cas, je suis d'accord avec l'article.

Mais ce que je propose ici est différent, je suggère que cette approche ne soit utilisée que dans le cas où une nouvelle instance du référentiel est créée chaque fois qu'une transaction est nécessaire.

Notes sur le deuxième article:

Je pense que le deuxième article ne traite pas de l’approche que vous devriez utiliser.

Le deuxième article discute de la nécessité de disposer du contexte dans tous les cas (sans rapport avec la conception du référentiel).

Veuillez noter que dans les deux approches de conception, nous nous débarrassons du contexte. La seule différence est de savoir qui est responsable de cette élimination.

L'article dit que les DbContext semblent nettoyer les ressources sans qu'il soit nécessaire de disposer explicitement du contexte.

5
Yacoub Massad

Le premier article que vous avez lié a oublié de préciser une chose importante: quelle est la durée de vie des instances dites NonScalableUserRepostory (elle a également oublié de faire NonScalableUserRepostory implémente également IDisposable, afin de disposer correctement de l’instance DbContext).

Imaginez le cas suivant:

public string SomeMethod()
{
    using (var myRepository = new NonScalableUserRepostory(someConfigInstance))
    {
        return myRepository.GetMyString();
    }
}

Eh bien ... il y aurait toujours un champ privé DbContext à l'intérieur de la classe NonScalableUserRepostory, mais le contexte ne sera utilisé qu'une seule fois . C'est donc exactement la même chose que ce que l'article décrit comme étant la meilleure pratique.

Donc, la question n'est pas " dois-je utiliser un membre privé contre une instruction using? ", c'est plus " quoi devrait être la durée de vie de mon contexte? ".

La réponse serait alors: essayez de le raccourcir autant que possible. Il y a la notion de nité de travail , qui représente une opération commerciale. En gros, vous devriez avoir un nouveau DbContext pour chaque unité de travail.

La définition d'une unité de travail et son implémentation dépendront de la nature de votre application. par exemple, pour une application ASP.Net MVC, la durée de vie de DbContext correspond généralement à celle de HttpRequest, c.-à-d. qu'un nouveau contexte est créé chaque fois que l'utilisateur génère une nouvelle requête Web.


MODIFIER :

Pour répondre à votre commentaire:

Une solution consisterait à injecter via le constructeur une méthode d'usine. Voici quelques exemples de base:

public class MyService : IService
{
    private readonly IRepositoryFactory repositoryFactory;

    public MyService(IRepositoryFactory repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory;
    }

    public void BusinessMethod()
    {
        using (var repo = this.repositoryFactory.Create())
        {
            // ...
        }
    }
}

public interface IRepositoryFactory
{
    IRepository Create();
}

public interface IRepository : IDisposable
{
    //methods
}
4
ken2k

Le premier code n'a pas de rapport avec le problème de scability, la raison pour laquelle il est mauvais, c'est parce qu'il crée un nouveau contexte pour chaque référentiel qui est mauvais, que l'un des commentateurs commente mais qu'il n'a même pas répondu. Sur le Web, il y avait 1 demande 1 dbContext, si vous envisagez d'utiliser un modèle de référentiel, il est traduit en 1 demande> plusieurs référentiels> 1 dbContext. c'est facile à réaliser avec IoC, mais pas nécessaire. Voici comment vous le faites sans IoC:

var dbContext = new DBContext(); 
var repository = new UserRepository(dbContext); 
var repository2 = new ProductRepository(dbContext);
// do something with repo

Pour ce qui est de disposer ou non, je l’utilise habituellement, mais si le responsable lui-même l’a dit, il n’ya probablement aucune raison de le faire. J'aime juste me débarrasser si elle a une bonne idée.

2
kirie

Fondamentalement, la classe DbContext n’est autre qu’un wrapper qui gère tous les éléments liés à la base de données, tels que: 1. Créer une connexion 2. Exécuter la requête. Maintenant, si nous faisons les choses mentionnées ci-dessus en utilisant ado.net normal, nous devons fermer explicitement la connexion en écrivant le code dans une instruction ou en appelant la méthode close () sur un objet de classe connection.

Maintenant, comme la classe de contexte implémente l’interface IDisposable en interne, il est recommandé d’écrire le dbcontext dans l’utilisation de statement afin que nous n’ayions pas à nous soucier de fermer la connexion.

Merci.

1
Sachin Gaikwad

La deuxième approche (utiliser) est préférable car elle maintient la connexion uniquement pendant le temps nécessaire minimum et est plus facile à sécuriser pour les threads.

0
Dexion

Je pense que la première approche est préférable, vous ne devez jamais créer un contexte de base de données pour chaque référentiel, même si vous en disposez. Cependant, dans le premier cas, vous pouvez utiliser une base de données databaseFactory pour instancier un seul dbcontext:

 public class DatabaseFactory : Disposable, IDatabaseFactory {
    private XXDbContext dataContext;

    public ISefeViewerDbContext Get() {
        return dataContext ?? (dataContext = new XXDbContext());
    }

    protected override void DisposeCore() {
        if (dataContext != null) {
            dataContext.Dispose();
        }
    }
}

Et utilisez cette instance dans le référentiel:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    private IXXDbContext dataContext;

    private readonly DbSet<TEntity> dbset;

    public Repository(IDatabaseFactory databaseFactory) {
        if (databaseFactory == null) {
            throw new ArgumentNullException("databaseFactory", "argument is null");
        }
        DatabaseFactory = databaseFactory;
        dbset = DataContext.Set<TEntity>();
    }

    public ISefeViewerDbContext DataContext {
        get { return (dataContext ?? (dataContext = DatabaseFactory.Get()));
    }

    public virtual TEntity GetById(Guid id){
        return dbset.Find(id);
    }
....
0
Donald

J'utilise le premier moyen (injecter le dbContext) Bien sûr, il devrait s'agir d'un IMyDbContext et votre moteur d'injection de dépendance gère le cycle de vie du contexte afin qu'il ne soit actif que s'il est requis.

Cela vous permet de simuler le contexte pour les tests, la deuxième manière rend impossible l'examen des entités sans base de données pour le contexte à utiliser.

0
Loofer