实体框架:一个数据库,多个DbContexts。这是一个坏主意吗?

问题描述 投票:180回答:11

我的印象是DbContext意味着代表你的数据库,因此,如果你的应用程序使用一个数据库,你只需要一个DbContext。但是,有些同事希望将功能区域分解为单独的DbContext类。我相信这来自一个好地方 - 希望保持代码清洁 - 但它似乎不稳定。我的直觉告诉我这是一个坏主意,但不幸的是,我的直觉并不是设计决策的充分条件。

所以我正在寻找A)为什么这可能是一个坏主意的具体例子,或B)保证这一切都很好。

entity-framework ef-code-first entity-framework-4.3 dbcontext
11个回答
152
投票

您可以为单个数据库设置多个上下文。例如,如果您的数据库包含多个数据库模式,并且您希望将它们作为单独的自包含区域进行处理,那么它可能很有用。

问题是当您想首先使用代码创建数据库时 - 应用程序中只有单个上下文可以做到这一点。这方面的技巧通常是一个附加的上下文,其中包含仅用于创建数据库的所有实体。只包含实体子集的真实应用程序上下文必须将数据库初始化程序设置为null。

在使用多种上下文类型时,您会看到其他问题 - 例如共享实体类型及其从一个上下文传递到另一个上下文等。一般情况下,它可以使您的设计更清晰,并将不同的功能区域分开,但它有它的另外复杂的成本。


1
投票

灵感来自[@ JulieLerman的DDD MSDN Mag Article 2013] [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")
  {}
}   

“如果您正在进行新的开发,并且希望让Code First根据您的类创建或迁移您的数据库,则需要使用包含所需的所有类和关系的DbContext创建”超级模型“。构建一个代表数据库的完整模型。但是,这个上下文不能继承BaseContext。“ JL


1
投票

我想分享一个案例,我认为在同一个数据库中拥有多个DBContexts的可能性很有意义。

我有两个数据库的解决方案。一个是除了用户信息之外的域数据。另一个仅用于用户信息。该部门主要由欧盟General Data Protection Regulation推动。通过拥有两个数据库,只要用户数据保留在一个安全的位置,我就可以自由地移动域数据(例如从Azure到我的开发环境)。

现在对于用户数据库,我已经通过EF实现了两个模式。一个是AspNet Identity框架提供的默认值。另一个是我们自己实现用户相关的任何其他内容。我更喜欢这个解决方案而不是扩展ApsNet模式,因为我可以轻松处理对AspNet Identity的未来更改,同时分离使程序员清楚地知道“我们自己的用户信息”在我们定义的特定用户模式中。


48
投票

这个帖子刚刚在StackOverflow上冒出来,所以我想提供另一个“B)保证这一切都很好”:)

我通过DDD Bounded Context模式正是这样做的。我在我的书“编程实体框架:DbContext”中写过这篇文章,它是我在Pluralsight课程中的一个50分钟模块的焦点 - > http://pluralsight.com/training/Courses/TableOfContents/efarchitecture


43
投票

大约四年前我写了这个答案,我的意见没有改变。但从那以后,微服务方面取得了重大进展。我最后添加了微服务特定的笔记......

我会反对这个想法,用现实世界的经验来支持我的投票。

我被带到一个大型应用程序,该应用程序有五个上下文用于单个数据库。最后,我们最终删除了除了一个之外的所有上下文 - 恢复到单个上下文。

起初,多个上下文的想法似乎是一个好主意。我们可以将数据访问分离到域中,并提供几个干净的轻量级上下文。听起来像DDD,对吧?这将简化我们的数据访问。另一个论点是性能,因为我们只访问我们需要的上下文。

但实际上,随着我们的应用程序的发展,我们的许多表在各种环境中共享了关系。例如,在上下文1中对表A的查询也需要在上下文2中连接表B.

这给我们留下了几个糟糕的选择。我们可以在各种上下文中复制表。我们试过这个。这创建了几个映射问题,包括要求每个实体具有唯一名称的EF约束。所以我们在不同的上下文中得到了名为Person1和Person2的实体。有人可能会说这是我们糟糕的设计,但尽管我们付出了最大的努力,但这就是我们的应用程序在现实世界中实际增长的方式。

我们还尝试查询两个上下文以获取我们需要的数据。例如,我们的业务逻辑将从上下文1查询其所需内容的一半,而从上下文2查询另一半。这有一些重大问题。我们不得不针对单个上下文执行一个查询,而是必须跨不同的上下文执行多个查询。这有一个真正的性能损失。

最后,好消息是很容易剥离多个上下文。上下文旨在成为轻量级对象。所以我认为性能不是多个上下文的好参数。在几乎所有情况下,我认为单个上下文更简单,更简单,并且可能性能更好,并且您不必实施一系列解决方案来使其工作。

我想到了一种情况,其中多个上下文可能有用。可以使用单独的上下文来修复实际包含多个域的数据库的物理问题。理想情况下,上下文与域一对一,与数据库一对一。换句话说,如果一组表与给定数据库中的其他表没有任何关系,那么它们应该被拉出到一个单独的数据库中。我意识到这并不总是实用的。但是如果一组表格如此不同以至于您可以将它们分成单独的数据库(但您选择不这样做),那么我可以看到使用单独的上下文的情况,但仅仅因为实际上有两个独立的域。

关于微服务,一个单一的背景仍然有意义。但是,对于微服务,每个服务都有自己的上下文,其中只包含与该服务相关的数据库表。换句话说,如果服务x访问表1和2,并且服务y访问表3和4,则每个服务将具有其自己的唯一上下文,其包括特定于该服务的表。

我对你的想法很感兴趣。


42
投票

通过设置默认架构来区分上下文

在EF6中,您可以拥有多个上下文,只需在OnModelCreating派生类(Fluent-API配置所在的)的DbContext方法中指定默认数据库模式的名称。这将适用于EF6:

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

        // Fluent API configuration
    }   
}

此示例将使用“Customer”作为数据库表的前缀(而不是“dbo”)。更重要的是,它还将在__MigrationHistory表格前加上前缀,例如: Customer.__MigrationHistory。因此,您可以在一个数据库中拥有多个__MigrationHistory表,每个上下文一个。因此,您为一个上下文所做的更改不会与另一个上下文混淆。

添加迁移时,请将配置类的完全限定名称(从DbMigrationsConfiguration派生)指定为add-migration命令中的参数:

add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS

关于上下文密钥的简短说明

根据这篇MSDN文章“Chapter - Multiple Models Targeting the Same Database”EF 6可能会处理这种情况,即使只有一个MigrationHistory表存在,因为在表中有一个ContextKey列来区分迁移。

但是,我更喜欢通过指定默认架构来拥有多个MigrationHistory表,如上所述。

使用单独的迁移文件夹

在这种情况下,您可能还希望在项目中使用不同的“迁移”文件夹。您可以使用DbMigrationsConfiguration属性相应地设置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";
    }
}

摘要

总而言之,您可以说一切都是完全分开的:上下文,项目中的迁移文件夹和数据库中的表。

我会选择这样一个解决方案,如果有一组实体是一个更大的主题的一部分,但没有相互关联(通过外键)。

如果实体组彼此没有任何关系,我会为每个实体创建一个单独的数据库,并在不同的项目中访问它们,可能每个项目中只有一个上下文。


6
投票

简单的例子来实现以下目的:

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

只需在主上下文中定义属性:(用于创建和维护DB)注意:只需使用protected :(实体不在此处公开)

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:在这里公开单独的实体

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

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

诊断模型:

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

如果您愿意,可以将所有实体标记为主ApplicationDbContext中的受保护,然后根据需要为每个模式分隔创建其他上下文。

它们都使用相同的连接字符串,但它们使用单​​独的连接,因此不要交叉事务并注意锁定问题。通常你的设计分离,所以这不应该发生。


6
投票

提醒:如果你结合了多个上下文,请确保你将各种RealContexts.OnModelCreating()中的所有功能都粘贴到你的单个CombinedContext.OnModelCreating()中。

我只是浪费时间寻找为什么我的级联删除关系没有被保留只是为了发现我没有将modelBuilder.Entity<T>()....WillCascadeOnDelete();代码从我的真实上下文移植到我的组合上下文中。


4
投票

当我遇到这个设计时,我的直觉告诉了我同样的事情。 我正在开发一个代码库,其中有三个dbContexts到一个数据库。 3个dbcontex中的2个依赖于来自1个dbcontext的信息,因为它提供了管理数据。此设计限制了您如何查询数据。我遇到了这个问题,你无法加入dbcontexts。相反,您需要做的是查询两个单独的dbcontexts然后在内存中进行连接或迭代两者以获得两者的组合作为结果集。问题在于,不是查询特定的结果集,而是将所有记录加载到内存中,然后对内存中的两个结果集进行连接。它可以真的减慢速度。 我会问这个问题“只是因为你可以,你呢?” 有关我遇到的与此设计相关的问题,请参阅此文章。 The specified LINQ expression contains references to queries that are associated with different contexts


2
投票

在代码中,您可以拥有多个DBContext和一个数据库。您只需在构造函数中指定连接字符串即可。

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

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

2
投票

另一点“智慧”。我有一个面向互联网和内部应用程序的数据库。每张脸都有一个背景。这有助于我保持纪律严明,安全的隔离。

© www.soinside.com 2019 - 2024. All rights reserved.