web-dev-qa-db-fra.com

Entity Framework: une base de données, plusieurs DbContexts. Est-ce une mauvaise idée?

Mon impression à ce jour a été qu'un DbContext est censé représenter votre base de données et donc, si votre application utilise une base de données, vous ne voudriez qu'un seul DbContext. Cependant, certains collègues souhaitent diviser les zones fonctionnelles en classes séparées de DbContext. Je crois que cela vient d'un bon endroit - une volonté de garder le code plus propre - mais cela semble volatil. Mon instinct me dit que c'est une mauvaise idée, mais malheureusement, mon instinct n'est pas une condition suffisante pour une décision de conception.

Je cherche donc A) des exemples concrets de la raison pour laquelle cela pourrait être une mauvaise idée, ou B) des assurances que tout ira bien.

171
Josh Schultz

Vous pouvez avoir plusieurs contextes pour une base de données unique. Cela peut être utile, par exemple, si votre base de données contient plusieurs schémas de base de données et que vous souhaitez gérer chacun d'eux comme une zone autonome distincte. 

Le problème est lorsque vous voulez utiliser le code en premier pour créer votre base de données - seul un contexte dans votre application peut le faire. L'astuce pour cela est généralement un contexte supplémentaire contenant toutes vos entités qui est utilisé uniquement pour la création de base de données. Vos contextes d'application réels contenant uniquement des sous-ensembles de vos entités doivent avoir l'initialiseur de base de données défini sur null.

Si vous utilisez plusieurs types de contexte, vous constaterez d’autres problèmes, par exemple les types d’entités partagées et leur passage d’un contexte à un autre, etc. En général, il est possible de rendre votre conception plus propre et de séparer différents domaines coûts en complexité supplémentaire.

143
Ladislav Mrnka

Ce fil vient juste de déborder sur StackOverflow et je voulais donc offrir une autre "assurance B) que tout se passera bien" :)

Je fais exactement cela par le biais du modèle DDD Bounded Context. J'ai écrit à ce sujet dans mon livre, Programming Entity Framework: DbContext, qui fait l'objet d'un module de 50 minutes dans l'un de mes cours sur Pluralsight -> http://pluralsight.com/training/Courses/TableOfContents/ efarchitecture

48
Julie Lerman

Distinguer les contextes en définissant le schéma par défaut

Dans EF6, vous pouvez avoir plusieurs contextes, indiquez simplement le nom du schéma de base de données par défaut dans la méthode OnModelCreating de votre classe dérivée DbContext (où la configuration de Fluent-API est) . Cela fonctionnera dans EF6:

public partial class CustomerModel : DbContext
{   
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.HasDefaultSchema("Customer");

        // Fluent API configuration
    }   
}

Cet exemple utilisera "Client" comme préfixe pour vos tables de base de données (au lieu de "dbo") . Plus important encore, il préfixera également la ou les tables __MigrationHistory, par exemple. Customer.__MigrationHistory. Ainsi, vous pouvez avoir plus d'une table __MigrationHistory dans une base de données unique, une pour chaque contexte . Ainsi, les modifications que vous apportez pour un contexte ne seront pas gênantes pour l'autre.

Lors de l'ajout de la migration, spécifiez le nom complet de votre classe de configuration (dérivé de DbMigrationsConfiguration) en tant que paramètre dans la commande add-migration:

add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS


Un petit mot sur la clé de contexte

Selon cet article MSDN " Chapitre - Plusieurs modèles ciblant la même base de données " EF 6 gérera probablement la situation même s'il ne restait qu'une table MigrationHistory, car dans la table se trouve une colonne ContextKey permettant de distinguer le migrations.

Cependant, je préfère avoir plus d'une table MigrationHistory en spécifiant le schéma par défaut, comme expliqué ci-dessus.


Utilisation de dossiers de migration distincts

Dans un tel scénario, vous pouvez également vouloir utiliser différents dossiers de "migration" dans votre projet. Vous pouvez configurer votre classe dérivée DbMigrationsConfiguration en conséquence en utilisant la propriété MigrationsDirectory:

internal sealed class ConfigurationA : DbMigrationsConfiguration<ModelA>
{
    public ConfigurationA()
    {
        AutomaticMigrationsEnabled = false;
        MigrationsDirectory = @"Migrations\ModelA";
    }
}

internal sealed class ConfigurationB : DbMigrationsConfiguration<ModelB>
{
    public ConfigurationB()
    {
        AutomaticMigrationsEnabled = false;
        MigrationsDirectory = @"Migrations\ModelB";
    }
}


Résumé

Globalement, vous pouvez dire que tout est clairement séparé: contextes, dossiers de migration dans le projet et tables dans la base de données.

Je choisirais une telle solution s'il existe des groupes d'entités qui font partie d'un sujet plus vaste, mais ne sont pas liés (via des clés étrangères) les uns aux autres.

Si les groupes d'entités n'ont rien à faire les uns des autres, je créerais une base de données distincte pour chacun d'eux et j'y accéderais également dans différents projets, avec probablement un seul contexte dans chaque projet.

40
Martin

Je vais peser contre l'idée, avec l'expérience du monde réel pour étayer mon vote. 

J'ai été amené à une application volumineuse comportant cinq contextes pour une base de données unique. En fin de compte, nous avons fini par supprimer tous les contextes sauf un: revenir à un contexte unique.

Au début, l'idée de contextes multiples semble être une bonne idée. Nous pouvons séparer notre accès aux données en domaines et fournir plusieurs contextes légers et propres. On dirait que DDD, non? Cela simplifierait notre accès aux données. Un autre argument concerne la performance dans la mesure où nous n’avons accès qu’au contexte dont nous avons besoin.

Mais dans la pratique, à mesure que notre application grandissait, nombre de nos tables partageaient des relations dans nos divers contextes. Par exemple, les requêtes sur la table A dans le contexte 1 ont également nécessité de joindre la table B dans le contexte 2. 

Cela nous a laissé un couple de mauvais choix. Nous pourrions dupliquer les tableaux dans les différents contextes. Nous avons essayé cela. Cela a créé plusieurs problèmes de mappage, notamment une contrainte EF qui exige que chaque entité ait un nom unique. Nous nous sommes donc retrouvés avec des entités nommées Person1 et Person2 dans les différents contextes. On pourrait dire que notre conception était médiocre, mais malgré tous nos efforts, c'est ainsi que notre application a grandi dans le monde réel. 

Nous avons également essayé d'interroger les deux contextes pour obtenir les données dont nous avions besoin. Par exemple, notre logique métier interroge la moitié de ce dont elle a besoin du contexte 1 et l’autre moitié du contexte 2. Cela pose quelques problèmes majeurs. Au lieu d'effectuer une requête sur un seul contexte, nous avons dû effectuer plusieurs requêtes dans différents contextes. Cela a une pénalité de performance réelle. 

En fin de compte, la bonne nouvelle est qu’il était facile d’éliminer les multiples contextes. Le contexte est destiné à être un objet léger. Donc, je ne pense pas que la performance soit un bon argument pour plusieurs contextes. Dans presque tous les cas, je pense qu'un seul contexte est plus simple, moins complexe et aura probablement de meilleures performances, et vous n'aurez pas à mettre en œuvre de nombreuses solutions de contournement pour le faire fonctionner. 

J'ai pensé à une situation où plusieurs contextes pourraient être utiles. Un contexte séparé peut être utilisé pour résoudre un problème physique avec la base de données dans laquelle elle contient en réalité plusieurs domaines. Idéalement, un contexte serait un à un pour un domaine, ce qui serait un à un pour une base de données. En d'autres termes, si un ensemble de tables n'est en aucune manière lié aux autres tables d'une base de données donnée, elles devraient probablement être extraites dans une base de données séparée. Je réalise que ce n'est pas toujours pratique. Mais si un ensemble de tables est si différent que vous vous sentiriez à l'aise de les séparer dans une base de données séparée (mais vous choisissez de ne pas le faire), je pourrais alors plaider en faveur de l'utilisation d'un contexte séparé, mais uniquement parce qu'il existe en fait deux domaines distincts.

Je suis intéressé par vos pensées. 

38
Francisco d'Anconia

Rappel: Si vous combinez plusieurs contextes, assurez-vous de couper et coller toutes les fonctionnalités de vos diverses RealContexts.OnModelCreating() dans votre unique CombinedContext.OnModelCreating().

J'ai juste perdu du temps à chercher pourquoi mes relations de suppression en cascade n'étaient pas préservées, mais seulement à découvrir que je n'avais pas transféré le code modelBuilder.Entity<T>()....WillCascadeOnDelete(); de mon contexte réel à mon contexte combiné.

6
Ilan

Exemple simple pour réaliser ce qui suit:

    ApplicationDbContext forumDB = new ApplicationDbContext();
    MonitorDbContext monitor = new MonitorDbContext();

Définissez simplement les propriétés dans le contexte principal: (utilisé pour créer et gérer la base de données) Remarque: Utilisez simplement protected: (l'entité n'est pas exposée ici).

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("QAForum", throwIfV1Schema: false)
    {

    }
    protected DbSet<Diagnostic> Diagnostics { get; set; }
    public DbSet<Forum> Forums { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<Thread> Threads { get; set; }
    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

MonitorContext: Exposer une entité séparée ici

public class MonitorDbContext: DbContext
{
    public MonitorDbContext()
        : base("QAForum")
    {

    }
    public DbSet<Diagnostic> Diagnostics { get; set; }
    // add more here
}

Modèle de diagnostic:

public class Diagnostic
{
    [Key]
    public Guid DiagnosticID { get; set; }
    public string ApplicationName { get; set; }
    public DateTime DiagnosticTime { get; set; }
    public string Data { get; set; }
}

Si vous le souhaitez, vous pouvez marquer toutes les entités comme protégées dans le cadre principal ApplicationDbContext, puis créer des contextes supplémentaires selon les besoins pour chaque séparation de schémas.

Ils utilisent tous la même chaîne de connexion, mais ils utilisent des connexions distinctes. Par conséquent, ne croisez pas les transactions et soyez conscient des problèmes de verrouillage. Généralement, votre séparation de conception ne devrait donc pas se produire de toute façon.

6
Choco

Mon instinct m'a dit la même chose quand je suis tombé sur cette conception.

Je travaille sur une base de code où il y a trois dbContexts dans une base de données. 2 des 3 dbcontexts dépendent des informations de 1 dbcontext car elles servent les données administratives. Cette conception a imposé des contraintes sur la manière dont vous pouvez interroger vos données. J'ai rencontré ce problème où vous ne pouvez pas rejoindre à travers dbcontexts. Au lieu de cela, vous devez interroger les deux dbcontexts distincts, puis effectuer une jointure en mémoire ou effectuer une itération entre les deux pour obtenir la combinaison des deux en tant que résultat. Le problème est que, au lieu de rechercher un jeu de résultats spécifique, vous chargez maintenant tous vos enregistrements en mémoire, puis vous effectuez une jointure avec les deux jeux de résultats en mémoire. Il peut vraiment ralentir les choses .

Je voudrais poser la question "juste parce que vous le pouvez, devriez-vous?"

Voir cet article pour connaître le problème que j'ai rencontré en rapport avec cette conception . L'expression LINQ spécifiée contient des références à des requêtes associées à différents contextes

4
Victor J. Garcia

Un autre peu de "sagesse". J'ai une base de données faisant face à la fois, Internet et une application interne. J'ai un contexte pour chaque visage. Cela m'aide à maintenir une ségrégation disciplinée et sécurisée.

2
Miguel Delgado

D'abord dans le code, vous pouvez avoir plusieurs DBContext et une seule base de données. Vous devez simplement spécifier la chaîne de connexion dans le constructeur.

public class MovieDBContext : DbContext
{
    public MovieDBContext()
        : base("DefaultConnection")
    {

    }
    public DbSet<Movie> Movies { get; set; }
}
2
Daniel

Inspiré de [l'article de 2013 de DDD MSDN Mag de @JulieLerman] [1]

    public class ShippingContext : BaseContext<ShippingContext>
{
  public DbSet<Shipment> Shipments { get; set; }
  public DbSet<Shipper> Shippers { get; set; }
  public DbSet<OrderShippingDetail> Order { get; set; } //Orders table
  public DbSet<ItemToBeShipped> ItemsToBeShipped { get; set; }
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    modelBuilder.Ignore<LineItem>();
    modelBuilder.Ignore<Order>();
    modelBuilder.Configurations.Add(new ShippingAddressMap());
  }
}

public class BaseContext<TContext>
  DbContext where TContext : DbContext
{
  static BaseContext()
  {
    Database.SetInitializer<TContext>(null);
  }
  protected BaseContext() : base("DPSalesDatabase")
  {}
}   

"Si vous effectuez un nouveau développement et que vous souhaitez laisser Code First créer ou migrer votre base de données en fonction de vos classes, vous devez créer un" modèle uber "à l'aide d'un DbContext qui inclut toutes les classes et relations nécessaires construire un modèle complet qui représente la base de données. Cependant, ce contexte ne doit pas hériter de BaseContext. " JL

1
OzBob

Je souhaite partager un cas dans lequel la possibilité de disposer de plusieurs DBContexts dans la même base de données est logique. 

J'ai une solution avec deux bases de données. L'une concerne les données de domaine, à l'exception des informations sur l'utilisateur. L'autre est uniquement pour l'information de l'utilisateur. Cette division est principalement régie par le règlement UE Règlement général sur la protection des données . En disposant de deux bases de données, je peux librement déplacer les données de domaine (par exemple d’Azure vers mon environnement de développement) tant que les données de l’utilisateur restent dans un emplacement sécurisé. 

Maintenant, pour la base de données utilisateur, j'ai implémenté deux schémas via EF. L'un est celui par défaut fourni par la structure AspNet Identity. L'autre est notre propre implémentation de tout ce qui concerne l'utilisateur. Je préfère cette solution à l'extension du schéma ApsNet, car je peux facilement gérer les modifications futures apportées à AspNet Identity et, en même temps, la séparation indique clairement aux programmeurs que "nos propres informations utilisateur" sont intégrées au schéma d'utilisateur spécifique que nous avons défini. .

0
freilebt