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

Jos*_*ltz 193 entity-framework ef-code-first dbcontext entity-framework-4.3

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

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

Lad*_*nka 159

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

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

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

  • 如果应用程序具有许多实体/表,则每个应用程序使用单个上下文可能很昂贵 因此,根据模式,拥有多个上下文也是有意义的. (17认同)
  • @PiotrKwiatek不确定你的评论和现在之间是否发生了变化,但是现在可以使用"启用 - 迁移 - 文本类型名称MyContext -MigrationsDirectory Migrations\MyContextMigrations". (8认同)
  • 由于我没有订阅复音,我发现这篇很棒的文章由Julie Lerman([她的评论](http://stackoverflow.com/a/12625918/65716))在此Q/A之后写得很好,但非常合适: http://msdn.microsoft.com/en-us/magazine/jj883952.aspx (6认同)

Jul*_*man 51

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

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

  • 让朱莉分享关于OP的问题/问题的一些信息本来是很好的.相反,这篇文章只是为了促进对多元化的付费订阅.如果产品的插件,至少是建议的解决方案的信息链接(DDD有界上下文模式)将是有帮助的."DDD"在"编程实体框架:DBContext"的第222页中描述了什么?因为我看了(没有索引)'DDD'甚至'Bounded Context',并且找不到......不能等你为EF6做新的修改...... (54认同)
  • 对不起,我没有尝试推广,只是增加OP"想要保证".拉迪斯拉夫和其他人在细节方面做得很好.所以我只是想尝试一些我已经创建的东西,这个东西比我可能在SO上传播更深入.这里有其他资源,我已经介绍了一些深奥的东西:http://msdn.microsoft.com/en-us/magazine/jj883952.aspx和http://msdn.microsoft.com/en-us/magazine /dn342868.aspx&http://oredev.org/2013/wed-fri-conference/entity-framework-in-core-business-applications-that-leverage-ddd (11认同)
  • 复数视频培训视频非常擅长解释大概念,但是,与企业解决方案相比,您提供的示例过于简单(例如,存在DbContext定义的程序集的NuGet,或模块化程序集动态加载). DDD Bounded Context被你的最后一个例子完全打破了,其中定义了一个重复的DbContext来保存每个DbSet的重复声明.我很欣赏你受到这项技术的限制.我真的很喜欢你的视频,但这个让我想要更多. (7认同)
  • 我的目标是大局观.在大型应用程序中重新编写程序包的问题与ef视频完全脱节.重新"破"的例子......嗯?也许更好的把它带到私人convo,因为对我的课程的批评非常超出这个论坛的范围(并且可能是不合适的).我想SO可以让你直接联系我. (5认同)

Fra*_*nia 48

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

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

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

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

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

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

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

我想到了一种情况,其中多个上下文可能有用.可以使用单独的上下文来修复实际包含多个域的数据库的物理问题.理想情况下,上下文与域一对一,与数据库一对一.换句话说,如果一组表不以任何方式与在一个给定的数据库中的其他表,他们或许应该被划到了一个单独的数据库.我意识到这并不总是实用的.但是,如果一组表是如此不同,你会感觉很舒服分离到一个单独的数据库(但不选择),那么我可以看到的情况下,使用一个单独的上下文,但仅仅是因为居然有两个不同领域.

我对你的想法很感兴趣.

  • 我必须在此同意,尤其是在定位现有数据库时.我现在正在研究这个问题,到目前为止我的直觉是:1.在多个环境中拥有相同的物理表是一个坏主意.2.如果我们无法确定一个表属于一个或另一个上下文,那么这两个上下文不够明显,不能在逻辑上分开. (6认同)
  • 对我来说,如果您的集合需要了解其他集合,您似乎并不会一直使用DDD。如果您需要引用某些内容,则有两个原因:它们处于同一汇总中,这意味着它们必须在同一事务中进行更改,否则就不行,并且您的界限不正确。 (4认同)
  • 我认为,在进行CQRS时,你不会在上下文之间有任何关系(每个视图都可以有自己的上下文),所以当一个人想要拥有多个上下文时,这个警告并不适用于所有情况.而不是加入和引用,为每个上下文使用数据复制. - 这并不否定这个答案的用处虽然:) (3认同)

Mar*_*tin 42

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

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

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

        // Fluent API configuration
    }   
}
Run Code Online (Sandbox Code Playgroud)

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

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

add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS
Run Code Online (Sandbox Code Playgroud)


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

根据这篇MSDN文章" 章节 - 针对同一数据库的多个模型 ",即使只有一个MigrationHistory表存在,EF 6也可能会处理这种情况,因为在表中有一个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";
    }
}
Run Code Online (Sandbox Code Playgroud)


摘要

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

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

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


Ila*_*lan 6

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

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

  • 你可以从组合的上下文中调用`OtherContext.OnModelCreating()`而不是剪切和粘贴吗? (5认同)

Cho*_*oco 6

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

    ApplicationDbContext forumDB = new ApplicationDbContext();
    MonitorDbContext monitor = new MonitorDbContext();
Run Code Online (Sandbox Code Playgroud)

只需在主上下文中定义属性:(用于创建和维护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);
    }
}
Run Code Online (Sandbox Code Playgroud)

MonitorContext:在这里公开单独的实体

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

    }
    public DbSet<Diagnostic> Diagnostics { get; set; }
    // add more here
}
Run Code Online (Sandbox Code Playgroud)

诊断模型:

public class Diagnostic
{
    [Key]
    public Guid DiagnosticID { get; set; }
    public string ApplicationName { get; set; }
    public DateTime DiagnosticTime { get; set; }
    public string Data { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这有很大帮助。“辅助”上下文不需要声明共享表。只需手动添加它的`DbSet&lt;x&gt;` 定义。我在与 EF Designer 所做的匹配的部分类中执行此操作。 (2认同)

Vic*_*cia 5

当我遇到这个设计时,我的直觉告诉我同样的事情。

我正在开发一个代码库,其中一个数据库有三个 dbContext。3 个 dbcontext 中的 2 个依赖于 1 个 dbcontext 的信息,因为它提供管理数据。此设计对查询数据的方式施加了限制。我遇到了这个问题,您无法跨 dbcontexts 加入。相反,您需要做的是查询两个单独的 dbcontext,然后在内存中进行联接或迭代两者以获取两者的组合作为结果集。问题在于,您现在不是查询特定结果集,而是将所有记录加载到内存中,然后对内存中的两个结果集进行联接。它确实可以减慢速度。

我会问这个问题“只是因为你可以,不是吗?

请参阅这篇文章,了解我遇到的与此设计相关的问题。 指定的 LINQ 表达式包含对与不同上下文关联的查询的引用

  • 我曾在一个大型系统上工作过,其中有多个上下文。我发现的一件事是,有时您必须在多个上下文中包含相同的 DbSet。一方面,这打破了一些纯度问题,但它确实允许您完成查询。对于需要读取某些管理表的情况,您可以将它们添加到 DbContext 基类中,并在应用程序模块上下文中继承它们。您“真正的”管理上下文的目的可能会被重新定义为“为管理表提供维护”,而不是提供对它们的所有访问权限。 (3认同)
  • 对于它的价值,我总是反复思考它是否值得。一方面,对于单独的上下文,对于只想在一个模块上工作的开发人员来说,需要了解的东西更少,并且您感觉定义和使用自定义投影更安全(因为您不担心它会对其他模块产生影响)模块)。另一方面,当您需要跨上下文共享数据时,您确实会遇到一些问题。 (2认同)
  • 您不必在两者中都包含实体,您始终可以获取 ids 并对不同的 context 进行第二次查询。对于小型系统来说,这很糟糕,对于具有许多开发人员的大型数据库/系统来说,多表结构的一致性是一个比 2 个查询更大、更困难的问题。 (2认同)

OzB*_*Bob 5

灵感来自 [@JulieLerman 的 DDD MSDN Mag 文章 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());
  }
}
Run Code Online (Sandbox Code Playgroud)
public class BaseContext<TContext>
  DbContext where TContext : DbContext
{
  static BaseContext()
  {
    Database.SetInitializer<TContext>(null);
  }
  protected BaseContext() : base("DPSalesDatabase")
  {}
}   
Run Code Online (Sandbox Code Playgroud)

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