迄今为止,我的印象是a DbContext
表示您的数据库,因此,如果您的应用程序使用一个数据库,则只需要一个DbContext
。
但是,一些同事希望将功能区域划分为不同的DbContext
类。
我相信这是一个好地方-希望保持代码更清洁-但它似乎不稳定。我的直觉告诉我这是一个坏主意,但不幸的是,我的直觉并不能满足设计决策的要求。
所以我在寻找:
A)为什么这可能不是一个好主意的具体例子;
B)保证一切都会顺利进行。
迄今为止,我的印象是a DbContext
表示您的数据库,因此,如果您的应用程序使用一个数据库,则只需要一个DbContext
。
但是,一些同事希望将功能区域划分为不同的DbContext
类。
我相信这是一个好地方-希望保持代码更清洁-但它似乎不稳定。我的直觉告诉我这是一个坏主意,但不幸的是,我的直觉并不能满足设计决策的要求。
所以我在寻找:
A)为什么这可能不是一个好主意的具体例子;
B)保证一切都会顺利进行。
Answers:
一个数据库可以有多个上下文。例如,如果您的数据库包含多个数据库模式,并且您想将它们中的每一个作为独立的自包含区域来处理,则该功能很有用。
问题是当您想首先使用代码创建数据库时-只有应用程序中的单个上下文才能做到这一点。诀窍通常是一个包含所有实体的附加上下文,该上下文仅用于创建数据库。仅包含实体子集的真实应用程序上下文必须将数据库初始化程序设置为null。
使用多种上下文类型时,还会遇到其他问题,例如共享实体类型以及它们从一种上下文传递到另一种上下文等。通常,它可以使您的设计更加简洁并分离不同的功能区域,但是它具有成本增加。
Enable-Migrations -ContextTypeName MyContext -MigrationsDirectory Migrations\MyContextMigrations
现在可以使用。
大约四年前,我写了这个答案,但我的看法没有改变。但是自那时以来,微服务领域有了重大发展。我在最后添加了微服务的特定说明...
我会反对这个想法,并以实际经验来支持我的投票。
我被带到一个大型应用程序,该应用程序具有单个数据库的五个上下文。最后,我们最终删除了除一个上下文以外的所有上下文-恢复为单个上下文。
起初,多个上下文的想法似乎是一个好主意。我们可以将数据访问分为多个域,并提供几个干净的轻量级上下文。听起来像DDD,对不对?这将简化我们的数据访问。关于性能的另一个论点是,我们仅访问所需的上下文。
但是实际上,随着我们应用程序的增长,我们的许多表在我们各种上下文中共享关系。例如,在上下文1中对表A的查询还需要在上下文2中联接表B。
这给我们留下了很多糟糕的选择。我们可以在各种上下文中复制表。我们尝试过了。这产生了一些映射问题,包括要求每个实体具有唯一名称的EF约束。因此,我们最终在不同的上下文中获得了名为Person1和Person2的实体。有人可能会说这对我们来说是很差的设计,但是尽管我们尽了最大的努力,但实际上这是我们的应用程序在现实世界中增长的方式。
我们还尝试查询两个上下文以获得所需的数据。例如,我们的业务逻辑将从上下文1中查询一半,而从上下文2中查询另一半。这存在一些主要问题。代替对单个上下文执行一个查询,我们不得不在不同的上下文中执行多个查询。这会带来实际的性能损失。
最后,好消息是很容易去除多个上下文。上下文旨在成为轻量级对象。因此,我认为对于多种情况而言,性能并不是一个很好的论据。在几乎所有情况下,我都相信单个上下文会更简单,更简单,并且可能会表现更好,并且您无需实施大量变通办法即可使其工作。
我想到了一种情况,其中多个上下文可能有用。可以使用单独的上下文来解决数据库中实际包含多个域的物理问题。理想情况下,上下文与域是一对一的,对数据库而言是一对一的。换句话说,如果一组表与给定数据库中的其他表完全没有关系,则应将它们拉到单独的数据库中。我意识到这并不总是可行的。但是,如果一组表是如此不同,以至于您可以轻松地将它们分成一个单独的数据库(但您选择不这样做),那么我可以看到使用单独上下文的情况,但这仅仅是因为实际上有两个单独的域。
关于微服务,一个单一的上下文仍然有意义。但是,对于微服务,每个服务将具有其自己的上下文,该上下文仅包括与该服务相关的数据库表。换句话说,如果服务x访问表1和2,服务y访问表3和4,则每个服务将具有其自己的唯一上下文,该上下文包括特定于该服务的表。
我对你的想法感兴趣。
该线程刚刚在StackOverflow上冒泡,因此我想提供另一个“ B)保证,这一切都将正常” :)
我正是通过DDD有界上下文模式来做到这一点的。我已经在我的书《 Programming Entity Framework:DbContext》中写过它,它是我在Pluralsight上的一门课程中的一个50分钟模块的焦点-> http://pluralsight.com/training/Courses/TableOfContents/efarchitecture
通过设置默认架构来区分上下文
在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
在一个数据库中可以有多个表,每个表一个。因此,您对一个上下文所做的更改不会与另一个上下文混淆。
添加迁移时,请DbMigrationsConfiguration
在add-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文件夹和数据库中的表。
如果存在一组实体是一个较大主题的一部分,但彼此之间(通过外键)不相关,那么我将选择这种解决方案。
如果实体组之间没有任何关系,我将为每个实体创建一个单独的数据库,并在不同的项目中访问它们,每个项目中可能只有一个上下文。
实现以下目的的简单示例:
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中将所有实体标记为受保护,然后根据需要为方案的每次分离创建其他上下文。
它们都使用相同的连接字符串,但是它们使用单独的连接,因此不要交叉事务并且要注意锁定问题。通常,您在设计上的分离,因此无论如何都不会发生。
DbSet<x>
定义。我在与EF设计器匹配的局部类中进行了此操作。
提醒:如果确实合并了多个上下文,请确保将所有功能都剪切并粘贴RealContexts.OnModelCreating()
到单个CombinedContext.OnModelCreating()
。
我只是浪费时间寻找为什么为什么不保留我的级联删除关系只是因为发现我没有将modelBuilder.Entity<T>()....WillCascadeOnDelete();
代码从我的真实上下文移植到合并的上下文中。
OtherContext.OnModelCreating()
从合并的上下文中调用吗?
当我遇到这种设计时,我的直觉告诉我了同样的事情。
我正在一个代码库中,其中一个数据库有三个dbContext。3个dbcontext中的2个依赖于1个dbcontext中的信息,因为它提供了管理数据。这种设计对如何查询数据施加了限制。我遇到了无法跨dbcontext加入的问题。取而代之的是,您需要查询两个单独的dbcontext,然后在内存中进行联接或遍历两者,以将两者作为结果集进行组合。这样做的问题是,现在不查询特定的结果集,而是将所有记录加载到内存中,然后对内存中的两个结果集进行联接。它确实可以减慢速度。
我会问一个问题只是因为可以,是吗?“
请参阅本文以了解与该设计有关的问题。
指定的LINQ表达式包含对与不同上下文关联的查询的引用
受到[@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
还有一点“智慧”。我有一个面向互联网和内部应用程序的数据库。每张脸我都有一个背景。这有助于我保持纪律严明的隔离。
我想分享一个案例,在该案例中,我认为在同一数据库中具有多个DBContext的可能性很有意义。
我有两个数据库的解决方案。一种是用于域数据,用户信息除外。另一个仅用于用户信息。该部门主要由《欧盟通用数据保护条例》推动。通过拥有两个数据库,只要用户数据保留在一个安全的位置,我就可以自由移动域数据(例如,从Azure到我的开发环境)。
现在,对于用户数据库,我已经通过EF实现了两种模式。一种是AspNet Identity框架提供的默认值。另一个是我们自己实现的任何其他与用户相关的东西。与扩展ApsNet模式相比,我更喜欢此解决方案,因为我可以轻松地处理对AspNet Identity的将来更改,同时,这种分离对程序员来说很清楚,“我们自己的用户信息”出现在我们定义的特定用户模式中。
呵呵,在每个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对象本身上。