迄今为止,我的印象是 aDbContext
用于表示您的数据库,因此,如果您的应用程序使用一个数据库,那么您只需要一个DbContext
.
但是,一些同事希望将功能区域分成单独的DbContext
类。
我相信这来自一个好地方——保持代码更清洁的愿望——但它似乎不稳定。我的直觉告诉我这是一个坏主意,但不幸的是,我的直觉并不是设计决策的充分条件。
所以我正在寻找:
A)为什么这可能是一个坏主意的具体例子;
B) 保证这一切都会顺利进行。
迄今为止,我的印象是 aDbContext
用于表示您的数据库,因此,如果您的应用程序使用一个数据库,那么您只需要一个DbContext
.
但是,一些同事希望将功能区域分成单独的DbContext
类。
我相信这来自一个好地方——保持代码更清洁的愿望——但它似乎不稳定。我的直觉告诉我这是一个坏主意,但不幸的是,我的直觉并不是设计决策的充分条件。
所以我正在寻找:
A)为什么这可能是一个坏主意的具体例子;
B) 保证这一切都会顺利进行。
单个数据库可以有多个上下文。例如,如果您的数据库包含多个数据库模式并且您希望将它们中的每一个都作为单独的自包含区域来处理,那么它可能很有用。
问题是当您想首先使用代码来创建数据库时 - 只有应用程序中的单个上下文可以做到这一点。这样做的技巧通常是一个包含所有实体的附加上下文,该上下文仅用于创建数据库。仅包含实体子集的真实应用程序上下文必须将数据库初始化程序设置为 null。
在使用多种上下文类型时,您还会看到其他问题 - 例如共享实体类型及其从一个上下文传递到另一个上下文等。通常,它可以使您的设计更加简洁并分离不同的功能区域,但它有额外复杂性的成本。
我大约四年前写了这个答案,我的观点没有改变。但从那时起,微服务方面有了重大发展。我在最后添加了微服务特定的注释......
我会反对这个想法,用现实世界的经验来支持我的投票。
我被带到一个大型应用程序中,该应用程序有五个用于单个数据库的上下文。最后,我们最终删除了除一个之外的所有上下文 - 恢复为单个上下文。
起初,多个上下文的想法似乎是个好主意。我们可以将我们的数据访问分成多个域并提供几个干净的轻量级上下文。听起来像 DDD,对吧?这将简化我们的数据访问。另一个论点是关于性能的,因为我们只访问我们需要的上下文。
但在实践中,随着我们的应用程序的增长,我们的许多表在不同的上下文中共享关系。例如,在上下文 1 中对表 A 的查询也需要在上下文 2 中连接表 B。
这给我们留下了几个糟糕的选择。我们可以在各种上下文中复制这些表。我们试过这个。这产生了几个映射问题,包括要求每个实体具有唯一名称的 EF 约束。所以我们最终在不同的上下文中得到了名为 Person1 和 Person2 的实体。有人可能会说这对我们来说是糟糕的设计,但尽管我们尽了最大努力,但这就是我们的应用程序在现实世界中实际增长的方式。
我们还尝试查询这两个上下文以获取我们需要的数据。例如,我们的业务逻辑会从上下文 1 中查询它需要的一半,从上下文 2 中查询另一半。这有一些主要问题。我们必须跨不同的上下文执行多个查询,而不是针对单个上下文执行一个查询。这会带来真正的性能损失。
最后,好消息是剥离多个上下文很容易。上下文旨在成为一个轻量级对象。所以我不认为性能是多种情况下的一个很好的论据。在几乎所有情况下,我相信单个上下文更简单、不那么复杂,并且可能会表现得更好,而且您不必实现一堆变通方法来让它工作。
我想到了一种情况,其中多个上下文可能有用。可以使用单独的上下文来解决实际包含多个域的数据库的物理问题。理想情况下,上下文与域是一对一的,与数据库是一对一的。换句话说,如果一组表与给定数据库中的其他表没有任何关系,那么它们可能应该被拉出到一个单独的数据库中。我意识到这并不总是实用的。但是,如果一组表是如此不同,以至于您愿意将它们分成单独的数据库(但您选择不这样做),那么我可以看到使用单独上下文的情况,但这只是因为实际上有两个单独的域。
关于微服务,一个单一的上下文仍然是有意义的。但是,对于微服务,每个服务都有自己的上下文,其中仅包括与该服务相关的数据库表。换句话说,如果服务 x 访问表 1 和 2,并且服务 y 访问表 3 和 4,则每个服务将具有其自己的唯一上下文,其中包括特定于该服务的表。
我对你的想法很感兴趣。
通过设置默认模式来区分上下文
OnModelCreating
在 EF6 中,您可以有多个上下文,只需在派生类的方法中指定默认数据库架构的名称DbContext
(Fluent-API 配置所在的位置)。这将在 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 文章“章节 - 针对同一数据库的多个模型”,即使只有一个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";
}
}
概括
总而言之,您可以说一切都清晰地分开:项目中的上下文、迁移文件夹和数据库中的表。
如果有一组实体是更大主题的一部分,但彼此不相关(通过外键),我会选择这样的解决方案。
如果实体组彼此之间没有任何关系,我将为它们中的每一个创建一个单独的数据库,并在不同的项目中访问它们,可能在每个项目中使用一个单一的上下文。
实现以下的简单示例:
ApplicationDbContext forumDB = new ApplicationDbContext();
MonitorDbContext monitor = new MonitorDbContext();
只需在主要上下文中限定属性:(用于创建和维护数据库)注意:只需使用受保护的:(实体未在此处公开)
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 中将所有实体标记为受保护,然后根据需要为每个模式分离创建额外的上下文。
它们都使用相同的连接字符串,但是它们使用单独的连接,因此不要交叉事务并注意锁定问题。通常,您的设计分离,因此无论如何都不应该发生。
提醒:如果您确实结合了多个上下文,请确保将各种上下文中的所有功能剪切并粘贴RealContexts.OnModelCreating()
到单个CombinedContext.OnModelCreating()
.
我只是浪费时间寻找为什么我的级联删除关系没有被保留,只是发现我没有将modelBuilder.Entity<T>()....WillCascadeOnDelete();
代码从我的真实上下文移植到我的组合上下文中。
灵感来自 [@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。杰伦
当我遇到这个设计时,我的直觉告诉我同样的事情。
我正在开发一个代码库,其中一个数据库有三个 dbContexts。3 个 dbcontext 中有 2 个依赖于来自 1 个 dbcontext 的信息,因为它提供管理数据。这种设计限制了您如何查询数据。我遇到了无法跨 dbcontexts 加入的问题。相反,您需要做的是查询两个单独的 dbcontexts,然后在内存中进行连接或迭代两者以获得两者的组合作为结果集。这样做的问题是,您现在不是查询特定结果集,而是将所有记录加载到内存中,然后对内存中的两个结果集进行连接。它真的可以减慢速度。
我会问这个问题“只是因为你可以,你应该吗?"
请参阅这篇文章了解我遇到的与此设计相关的问题。
指定的 LINQ 表达式包含对与不同上下文关联的查询的引用
还有一点“智慧”。我有一个面向互联网和内部应用程序的数据库。我对每张脸都有一个上下文。这有助于我保持纪律严明、安全的隔离。
我想分享一个案例,我认为在同一个数据库中拥有多个 DBContext 的可能性很有意义。
我有两个数据库的解决方案。一种是除用户信息外的域数据。另一个仅供用户参考。该部门主要由欧盟通用数据保护条例推动。通过拥有两个数据库,只要用户数据保存在一个安全的地方,我就可以自由地移动域数据(例如从 Azure 到我的开发环境)。
现在对于用户数据库,我已经通过 EF 实现了两个模式。一种是 AspNet Identity 框架提供的默认值。另一个是我们自己实现与用户相关的任何其他内容。与扩展 ApsNet 架构相比,我更喜欢此解决方案,因为我可以轻松处理未来对 AspNet 身份的更改,同时分离使程序员清楚地知道,“我们自己的用户信息”位于我们定义的特定用户架构中.
在代码优先中,您可以拥有多个 DBContext 和一个数据库。您只需在构造函数中指定连接字符串。
public class MovieDBContext : DbContext
{
public MovieDBContext()
: base("DefaultConnection")
{
}
public DbSet<Movie> Movies { get; set; }
}
嗯,花了很多时间来解决每个数据库模式的单独数据库上下文的问题,希望它可以帮助其他人......
我最近开始从事一个项目,该项目有一个具有 3 个模式的数据库(数据库优先方法),其中一个用于用户管理。每个单独的模式都有一个数据库上下文脚手架。当然,用户也与其他模式相关,例如。schema KB 有一个表 Topic,它有“创建者”、“最后修改者”等。FK 到身份模式、表 appuser。
这些对象是在 C# 中单独加载的,首先,从 1 个上下文加载主题,然后通过来自另一个 db 上下文的用户 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 返回了 error relation "AppUsers" does not exist
。Select 语句没有关于架构、字段名称等的正确信息。
我几乎放弃了,但后来我注意到运行时有一个开关“-d” dotnet ef dbcontext scaffold
。它是 -data-annotations 的缩写 - 使用属性来配置模型(如果可能)。如果省略,则仅使用 fluent API。指定此开关后,对象属性不是在 db context 中定义的OnModelCreating()
,而是在对象本身上定义的,带有属性。
通过这种方式,EF 获得了足够的信息来生成具有正确字段名称和架构的正确 SQL 语句。
TL;DR:单独的 DB 上下文不能很好地处理它们之间的关系 (FK),每个上下文只有关于其自身实体的信息。当指定“-data-annotations”开关时dotnet ef dbcontext scaffold
,这些信息不是存储在每个单独的上下文中,而是存储在数据库对象本身上。