到目前为止,我的印象是DbContext
用于表示您的数据库,因此,如果您的应用程序使用一个数据库,您只需要一个DbContext
。
然而,一些同事想将功能区域拆分成单独的DbContext
类。
我认为这源于善意——希望保持代码更加清晰——但它似乎不稳定。我的直觉告诉我这是个坏主意,但不幸的是,我的直觉并不足以成为设计决策的充分条件。
所以我正在寻找:
A)具体例子说明这可能是个坏主意;
B)保证这将会很好地解决问题。
到目前为止,我的印象是DbContext
用于表示您的数据库,因此,如果您的应用程序使用一个数据库,您只需要一个DbContext
。
然而,一些同事想将功能区域拆分成单独的DbContext
类。
我认为这源于善意——希望保持代码更加清晰——但它似乎不稳定。我的直觉告诉我这是个坏主意,但不幸的是,我的直觉并不足以成为设计决策的充分条件。
所以我正在寻找:
A)具体例子说明这可能是个坏主意;
B)保证这将会很好地解决问题。
你可以为单个数据库拥有多个上下文。这在数据库包含多个数据库架构且你想将它们作为独立的区域处理时非常有用。
问题在于当你想使用Code First创建你的数据库时,应用程序中只能有一个上下文来完成。通常解决方法是添加一个额外的上下文,其中包含所有实体,仅用于数据库创建。你真正的应用程序上下文只包含实体的子集,必须将其数据库初始化器设置为 null。
在使用多个上下文类型时,你还会遇到其他问题,例如共享实体类型及其从一个上下文传递到另一个上下文等。一般而言,这是可能的,它可以使你的设计更加清晰,并将不同的功能区域分离,但它会增加额外的复杂性成本。
Enable-Migrations -ContextTypeName MyContext -MigrationsDirectory Migrations\MyContextMigrations
已经可以使用了。 - Zack我大约四年前写过这篇答案,我的观点没有改变。但是自那以后,微服务领域出现了重大的发展。我在结尾处添加了微服务特定的注释...
我将反对这个想法,并用真实世界的经验来支持我的观点。
我被引进到一个有五个上下文的单个数据库的大型应用程序中。最终,我们除了一个上下文之外,删除了所有上下文-回归到一个上下文。
起初,多个上下文的想法似乎是一个好主意。我们可以将数据访问分成不同的领域,并提供几个干净轻量级的上下文。听起来像DDD,对吧?这将简化我们的数据访问。另一个论点是性能,因为我们只访问所需的上下文。
但是,在实践中,随着我们的应用程序的增长,我们的许多表格在我们的各种上下文之间共享关系。例如,在上下文1中查询表A也需要加入上下文2中的表B。
这使我们面临几个糟糕的选择。我们可以在各种情况下复制这些表。我们尝试过这样做。这创建了几个映射问题,包括EF约束,要求每个实体都有一个唯一的名称。因此,在不同的上下文中,我们最终得到了名为Person1和Person2的实体。有人可能会认为这是我们设计不良的结果,但是尽管我们尽了最大努力,但实际上我们的应用程序就是这样在现实世界中发展的。
我们还尝试查询两个上下文以获取所需的数据。例如,我们的业务逻辑从上下文1和上下文2分别查询部分所需内容。这产生了一些主要问题。与单个上下文执行一次查询不同,我们必须在不同的上下文中执行多个查询。这具有真正的性能惩罚。
最后,好消息是,轻松剥离多个上下文。上下文旨在成为轻量级对象。所以我认为性能不是多个上下文的好论点。在几乎所有情况下,我认为一个单一的上下文更简单,更少复杂,并且可能表现更好,而且您不必实现一堆解决方法才能使其起作用。
我想到了一种使用多个上下文(context)的情况。可以使用一个单独的上下文来解决数据库中实际包含多个域名(domain)的物理问题。理想情况下,一个上下文应该是一对一地与一个域名相关联,而这个域名又应该是一对一地与一个数据库相关联。换句话说,如果一组表与给定数据库中的其他表没有任何关系,它们可能应该被拆分到一个单独的数据库中。我意识到这并不总是切实可行的,但如果一组表如此不同以至于您感到舒适将它们分开成一个单独的数据库(但您选择不这样做),那么我可以看出使用单独的上下文的理由,仅因为实际上有两个独立的域。
关于微服务(micro-services),仍然需要使用一个单一的上下文(context)。但是,对于微服务,每个服务都将拥有其自己的上下文,其中仅包括与该服务相关的数据库表。换句话说,如果服务x访问表1和2,而服务y访问表3和4,则每个服务将拥有其自己独特的上下文,其中只包括特定于该服务的表。
我很想听听您的想法。
通过设置默认模式区分上下文
在EF6中,您可以拥有多个上下文,在您的DbContext
派生类的OnModelCreating
方法中(其中包含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
表,每个上下文一个。
所以您对一个上下文所做的更改不会影响另一个上下文。
在添加迁移时,在add-migration
命令的参数中指定您的配置类的完全限定名称(派生自DbMigrationsConfiguration
):
add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS
关于上下文键的简短说明
根据这篇 MSDN 文章 "第-章-针对同一数据库的多个模型" ,即使只存在一个 MigrationHistory
表,EF 6 也可能会处理这种情况,因为表中有一个ContextKey列以区分迁移。
然而,我更喜欢通过像上面解释的那样指定默认架构来拥有多个 MigrationHistory
表。
使用单独的迁移文件夹
在这种情况下,您可能还希望在项目中使用不同的“迁移”文件夹。 您可以使用 MigrationsDirectory
属性设置正确的 DbMigrationsConfiguration
派生类:
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();
只需在主上下文中限定属性:(用于创建和维护数据库) 注意:只需使用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);
}
}
监视上下文:在此处公开单独实体
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 设计器相匹配的部分类中执行此操作。 - Glen Little受@JulieLerman 2013年MSDN杂志上的DDD文章的启发
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")
{}
}
_context.Shipments.Select(shipment => shipment.OtherContexts).ToList()
?我需要如何将这个baseContext添加到DI中呢? - SNO提醒:如果您要组合多个上下文,请确保将各种功能从您的各个RealContexts.OnModelCreating()
剪切并粘贴到您的单个CombinedContext.OnModelCreating()
中。
我刚刚浪费了时间寻找为什么我的级联删除关系没有被保存,最后才发现我没有将modelBuilder.Entity<T>()....WillCascadeOnDelete();
代码从我的实际上下文传递到我的组合上下文。
OtherContext.OnModelCreating()
,而不是剪切和粘贴代码。 - Alex当我看到这个设计时,我的直觉也告诉我同样的事情。
我正在处理一个代码库,其中有三个dbContext与一个数据库相关联。其中3个dbcontexts中的2个是依赖于1个dbcontext提供的管理数据的信息的。这种设计对查询数据的方式施加了限制。我遇到了这样的问题:您不能跨越dbcontexts进行连接。相反,您需要查询两个单独的dbcontexts,然后在内存中进行连接或迭代这两个结果集以获取这两个结果集的组合作为结果集。问题在于,现在您不仅要查询特定的结果集,而且还要将所有记录加载到内存中,然后在内存中针对两个结果集进行连接。这可能会使速度变慢。
我想问的问题是“你能做到吗,你应该这样做吗?”
查看此文章,了解我遇到与此设计相关的问题。
指定的LINQ表达式包含与不同上下文相关联的查询的引用
另一条“智慧”的建议。我有一个面向互联网和内部应用程序的数据库。我为每个界面都设置了上下文,这可以帮助我保持严格的安全隔离。
我有一个有两个数据库的解决方案。一个是除用户信息外的域数据。另一个仅用于用户信息。这种分割主要是由欧盟 通用数据保护条例 驱动的。通过拥有两个数据库,只要用户数据保持在一个安全的地方,我就可以自由地移动域数据(例如从Azure到我的开发环境)。
现在对于用户数据库,我已经通过EF实现了两个模式。一个是AspNet Identity框架提供的默认模式。另一个是我们自己实现的其他与用户相关的内容。我更喜欢这种解决方案,而不是扩展ApsNet模式,因为我可以轻松处理将来对AspNet Identity的更改,并且同时分离使程序员清楚,“我们自己的用户信息”进入我们定义的特定用户模式。
public class MovieDBContext : DbContext
{
public MovieDBContext()
: base("DefaultConnection")
{
}
public DbSet<Movie> Movies { get; set; }
}