实体框架:一个数据库,多个DbContext。这是一个坏主意吗?[关闭]


213

迄今为止,我的印象是a DbContext表示您的数据库,因此,如果您的应用程序使用一个数据库,则只需要一个DbContext

但是,一些同事希望将功能区域划分为不同的DbContext类。

我相信这是一个好地方-希望保持代码更清洁-但它似乎不稳定。我的直觉告诉我这是一个坏主意,但不幸的是,我的直觉并不能满足设计决策的要求。

所以我在寻找:

A)为什么这可能不是一个好主意的具体例子;

B)保证一切都会顺利进行。


Answers:


168

一个数据库可以有多个上下文。例如,如果您的数据库包含多个数据库模式,并且您想将它们中的每一个作为独立的自包含区域来处理,则该功能很有用。

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

使用多种上下文类型时,还会遇到其他问题,例如共享实体类型以及它们从一种上下文传递到另一种上下文等。通常,它可以使您的设计更加简洁并分离不同的功能区域,但是它具有成本增加。


21
如果应用程序具有多个实体/表,则每个应用程序使用单个上下文可能会很昂贵。因此,根据架构,具有多个上下文也可能有意义。
DarthVader

7
由于我不赞成复数观点,因此我发现朱莉·莱尔曼(Julie Lerman )撰写的这篇很棒的文章(她的评论)在本次问答之后写得很好,但非常恰当:msdn.microsoft.com/en-us/magazine/jj883952.aspx
Dave T.13年

我建议实体框架通过命名约定在同一数据库中支持多个dbcontext。因此,我仍在为模块化应用程序编写自己的ORM。很难相信它会迫使单个应用程序使用单个数据库。尤其是在Web场中,数据库数量有限
自由意志

另外,我意识到您只能通过PM Console在项目中的一个上下文中启用迁移。
Piotr Kwiatek

9
@PiotrKwiatek不确定在您的评论和现在之间是否已更改,但Enable-Migrations -ContextTypeName MyContext -MigrationsDirectory Migrations\MyContextMigrations现在可以使用。
Zack

60

大约四年前,我写了这个答案,但我的看法没有改变。但是自那时以来,微服务领域有了重大发展。我在最后添加了微服务的特定说明...

我会反对这个想法,并以实际经验来支持我的投票。

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

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

但是实际上,随着我们应用程序的增长,我们的许多表在我们各种上下文中共享关系。例如,在上下文1中对表A的查询还需要在上下文2中联接表B。

这给我们留下了很多糟糕的选择。我们可以在各种上下文中复制表。我们尝试过了。这产生了一些映射问题,包括要求每个实体具有唯一名称的EF约束。因此,我们最终在不同的上下文中获得了名为Person1和Person2的实体。有人可能会说这对我们来说是很差的设计,但是尽管我们尽了最大的努力,但实际上这是我们的应用程序在现实世界中增长的方式。

我们还尝试查询两个上下文以获得所需的数据。例如,我们的业务逻辑将从上下文1中查询一半,而从上下文2中查询另一半。这存在一些主要问题。代替对单个上下文执行一个查询,我们不得不在不同的上下文中执行多个查询。这会带来实际的性能损失。

最后,好消息是很容易去除多个上下文。上下文旨在成为轻量级对象。因此,我认为对于多种情况而言,性能并不是一个很好的论据。在几乎所有情况下,我都相信单个上下文会更简单,更简单,并且可能会表现更好,并且您无需实施大量变通办法即可使其工作。

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

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

我对你的想法感兴趣。


8
我必须在这里达成一致,尤其是在针对现有数据库的情况下。我现在正在解决这个问题,到目前为止,我的直觉是:1.在多个环境中拥有相同的物理表是一个坏主意。2.如果我们不能确定一个表属于一个上下文或另一个上下文,则这两个上下文之间的区别不大,无法在逻辑上分开。
jkerak

3
我会争辩说,在执行CQRS时,上下文之间不会有任何关系(每个视图可能具有其自己的上下文),因此此警告并不适用于可能希望具有多个上下文的每种情况。代替联接和引用,对每个上下文使用数据复制。-但是,这并不否认此答案的有用性:)
urbanhusky

1
我感到你深深地面对着痛苦!:/我也认为,简单起见,更好的选择是上下文。
ahmet

1
我唯一反对的观点是,关于身份,我指出我完全同意。尤其是在水平扩展的情况下,几乎在引入负载平衡的所有情况下都需要分离标识层。至少,这就是我所发现的。
巴里(Barry)

5
对我来说,如果您的集合需要了解其他集合,您似乎并不会一直使用DDD。如果您需要引用某些内容,则有两个原因:它们在同一汇总中,这意味着必须在同一笔交易中进行更改,否则就不行,并且您的界限不正确。
Simons0n

54

该线程刚刚在StackOverflow上冒泡,因此我想提供另一个“ B)保证,这一切都将正常” :)

我正是通过DDD有界上下文模式来做到这一点的。我已经在我的书《 Programming Entity Framework:DbContext》中写过它,它是我在Pluralsight上的一门课程中的一个50分钟模块的焦点-> http://pluralsight.com/training/Courses/TableOfContents/efarchitecture


7
多元视觉培训视频非常擅长于解释大概念,但是,恕我直言,与企业解决方案相比,您给出的示例过于琐碎(例如,存在具有DbContext定义的程序集的NuGet或动态加载的模块化程序集)。最后一个示例完全破坏了DDD绑定上下文,在该示例中,定义了重复的DbContext来保存每个DbSet的重复声明。非常感谢您受到技术限制。我真的很喜欢您的视频,但这使我想要更多。
维克多·罗密欧2012年

5
我的目标是大局。大应用程序中有关Nuget软件包的问题完全与EF视频无关。再说“残破”的例子吧?也许最好将其私有化,因为对我的课程的批评对这个论坛来说超出了范围(并且可能不合适)。我想让您直接与我联系。
朱莉·勒曼

57
让Julie分享关于OP的问题的信息真是太好了。取而代之的是,该职位只是为了促进对复数见解的付费订阅。如果是产品的插件,则至少可以使用指向建议解决方案信息的链接(DDD有界上下文模式)。“ DDD”是“编程实体框架:DBContext”的p.222中描述的内容吗?因为我(没有索引)查找'DDD'甚至是'Bounded Context',并且无法找到...等不及要为EF6进行新的修订...
富有同情心的Narcissist 2014年

12
抱歉,我没有在尝试推广,只是在OP中添加了“希望保证”。Ladislav和其他人在细节方面做得很好。因此,我只是想尝试一些我已经创建的东西,其深度要远远超过我可能要依靠的基础。下面是在那里我已经介绍了一些东西深入的其它资源:msdn.microsoft.com/en-us/magazine/jj883952.aspxmsdn.microsoft.com/en-us/magazine/dn342868.aspxoredev.org / 2013 / wed-fri-conference /…
朱莉·勒曼

@JulieLerman的代码建议的摘要,请参阅我的答案stackoverflow.com/a/36789520/1586498
OzBob

46

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

在EF6中,您可以有多个上下文,只需在派生类的OnModelCreating方法DbContext(Fluent-API配置所在的位置)中为默认数据库架构指定名称即可。这将在EF6中起作用:

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

        // Fluent API configuration
    }   
}

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

添加迁移时,请DbMigrationsConfigurationadd-migration命令中指定配置类的全限定名称(源自)作为参数:

add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS


关于上下文关键字的简短说明

根据此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";
    }
}


摘要

总而言之,您可以说所有内容都清晰地分开了:上下文,项目中的Migration文件夹和数据库中的表。

如果存在一组实体是一个较大主题的一部分,但彼此之间(通过外键)不相关,那么我将选择这种解决方案。

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


当需要更新不同上下文中的2个实体时,您会怎么做?
sotn

我将创建一个新的(服务)类,该类既了解上下文,又考虑此类的好名字和职责,并使用其方法之一进行此更新。
马丁

7

实现以下目的的简单示例:

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

只需在主上下文中确定属性的范围:(用于创建和维护数据库)注意:只需使用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中将所有实体标记为受保护,然后根据需要为方案的每次分离创建其他上下文。

它们都使用相同的连接字符串,但是它们使用单​​独的连接,因此不要交叉事务并且要注意锁定问题。通常,您在设计上的分离,因此无论如何都不会发生。


2
这很有帮助。“辅助”上下文不需要声明共享表。只需手动添加它的DbSet<x>定义。我在与EF设计器匹配的局部类中进行了此操作。
Glen Little

先生,您为我省去了很多麻烦!您提供了具体的解决方案,而不是接受的答案。非常感谢!
WoIIe

6

提醒:如果确实合并了多个上下文,请确保将所有功能都剪切并粘贴RealContexts.OnModelCreating()到单个CombinedContext.OnModelCreating()

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


6
除了剪切和粘贴外,您还可以只OtherContext.OnModelCreating()从合并的上下文中调用吗?
AlexFoxGill 2015年

4

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

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

我会问一个问题只是因为可以,是吗?

请参阅本文以了解与该设计有关的问题。 指定的LINQ表达式包含对与不同上下文关联的查询的引用


3
我曾在一个大型系统中工作,其中有多个上下文。我发现的一件事是,有时您必须在多个上下文中包含相同的DbSet。一方面,这打破了一些纯度方面的顾虑,但确实允许您完成查询。对于需要读取某些管理表的情况,可以将它们添加到DbContext基类中,并在应用程序模块上下文中继承它们。您“实际”管理上下文的目的可能被重新定义为“为管理表提供维护”,而不是提供对其的所有访问。
JMarsch 2013年

1
对于它的价值,我总是来回研究是否值得。一方面,在单独的上下文中,对于只想在一个模块上工作的开发人员知之甚少,而您定义和使用自定义投影会更安全(因为您不必担心它将对其他模块产生的影响)模块)。另一方面,当您需要跨上下文共享数据时,确实会遇到一些问题。
JMarsch 2013年

1
您不必都包含实体,您总是可以获得ID并对不同的上下文进行第二次查询。对于小型系统而言,这是不好的,对于具有许多开发人员的大型数据库/系统,多表结构的一致性是一个比2个查询更大,更困难的问题。
user1496062 2014年

4

受到[@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());
  }
}

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

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


2

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

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

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

是的,可以,但是如何从不同的数据库上下文中的不同实体进行查询?
Reza


1

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

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

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


2
我在答复中看不到任何问题。我不是在问一个唯一的问题!而是共享一个场景,使讨论的主题有意义。
freilebt

0

呵呵,在每个DB架构的单独DB上下文问题上花了很多时间,希望它能对其他人有所帮助...

我最近开始从事一个项目,该项目具有一个具有3个模式的数据库(数据库优先方法),其中一个用于用户管理。每个单独的架构都有一个数据库上下文。当然,用户也与其他模式有关。模式KB具有表Topic,该表具有“创建者”,“最后修改者”等。FK用于标识模式,表appuser。

这些对象分别在C#中加载,首先,从1个上下文加载了topic,然后从另一个数据库上下文中通过用户ID加载了用户-不好,必须解决此问题!(类似于使用EF 6在同一数据库中使用多个dbcontexts

首先,我尝试将丢失的FK指令从身份模式添加到KB模式,再添加到KB DB上下文中的EF modelBuilder。就像只有1个上下文一样,但我将其分为2个。

modelBuilder.Entity<Topic>(entity =>
{
  entity.HasOne(d => d.Creator)
    .WithMany(p => p.TopicCreator)
    .HasForeignKey(d => d.CreatorId)
    .HasConstraintName("fk_topic_app_users");

它没有用,因为kb db上下文没有有关用户对象的任何信息,postgres返回了错误 relation "AppUsers" does not exist。Select语句没有有关架构,字段名称等的正确信息。

我几乎放弃了,但是随后我发现运行时有一个开关“ -d” dotnet ef dbcontext scaffold。它是-data-annotations的缩写-使用属性(如果可能)配置模型。如果省略,则仅使用流畅的API。指定此开关后,将不在数据库上下文中定义对象属性OnModelCreating(),而是在具有属性的对象本身上。

这样,EF获得了足够的信息来生成具有适当字段名称和架构的适当SQL语句。

TL; DR:单独的数据库上下文不能很好地处理它们之间的关系(FK),每个上下文仅具有有关其自己实体的信息。当指定“ -data-annotations”时dotnet ef dbcontext scaffold,这些信息不会存储在每个单独的上下文中,而是存储在DB对象本身上。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.