如何使用Entity Framework Code First和DDD创建数据库?

5
为了使用Entity Framework实现领域驱动设计,我采用了Julie Lerman在TechEd North America 2013(http://channel9.msdn.com/Events/TechEd/NorthAmerica/2013/DEV-B336#fbid=4tnuPF6L-Jc)上介绍的方法。这种方法将EF实体类用作领域类。对于不同的有界上下文,领域实体类具有不同的属性,甚至可能有不同的名称,尽管它们在同一个表中存储数据。例如,“客户服务”有界上下文中的客户实际上是一个“客户”,但在“航运”有界上下文中,他是一个“收件人”,只有一部分客户属性。为每个有界上下文存在一个不同的EF上下文,该上下文仅包括有界上下文需要的实体的DbSet。通过覆盖OnModelCreating,我们甚至可以排除与有界上下文无关的引用实体。使用POCOs实现这一部分相当容易。
问题出在使用Code First创建数据库时。如果我们让Code First为每个不同的EF上下文创建数据库,那么我们最终会得到几个数据库。如果我们在EF上下文的构造函数中定义数据库名称,则数据库将与第一个使用的EF上下文一起创建,并且当使用第二个EF上下文时,我们会收到InvalidOperationException(指模型已更改)(缺少实体,缺少属性等)。如果我们使用迁移来更新数据库,可能会出现与迁移的正常使用混淆并导致无法正常工作的情况。
作为暂时解决方案,我使用单独的EF上下文仅用于数据库创建。这意味着我必须再次实现所有EF实体,只是为了这个目的。另一个问题是,我必须在应用程序启动时创建此EF上下文的实例,以确保数据库得到创建并(如有必要)迁移。
我相信还有其他解决方案。所以,请告诉我们如何解决这个问题。

在我看来,有界上下文和 EF DBContext 不同。有界上下文更像是逻辑表达,它是 DDD 的概念,而 DBContext 是 EF(实现)的概念。为什么不只使用一个数据库、一个上下文,然后使用仓储来处理有界上下文呢? - Stan Bashtavenko
@StanB:是的,有界上下文与DBContext非常不同。Julie Lerman使用了一种快捷方式,避免在域模型和Entity Framework模型中实现相同的类。我真的很喜欢这种方法,尽管它不是纯粹的DDD。如果我们使用分离的域类,我们将不得不在域和EF模型中实现双重类,并在它们之间进行映射。我目前尝试的一个可能的解决方案是在存储库中返回EF实体作为IQueryable,并在域服务中从中构建域实体。 - Jürgen Bayer
很抱歉,我仍然不理解问题。你所说的“实体类作为领域类”是什么意思?这是我使用EF/CodeFirst/DDD的方法。创建一个独立的、与持久化无关的程序集来存放你的领域对象。在那里定义IRepository接口。创建一个带有EF内容的独立程序集,并在其中实现存储库。存储库具有DBContext,加载并返回领域对象。在存储库中使用急切加载来实现你的有界上下文。 - Stan Bashtavenko
DDD 的分层架构规定,各层只能使用同一层或更低层的类型,而不应该依赖于更高层的类型。如果一个 EF 上下文使用了领域类,那么在我看来,这种情况就会出现。如果要完全遵循 DDD,我们需要在领域层中拥有单独的领域类。但是,由于这与我问题的基本问题无关,因此让我们不再进一步讨论这个话题。 - Jürgen Bayer
2个回答

2
实质上,我认为你需要一个主上下文,其中包含所有“表”的定义并驱动迁移。这个上下文用于创建数据库。
所有后续的“有界”上下文都应该在它们的构造函数中使用Database.SetInitializer(null),以防止它们篡改数据库模式。
此外,您的主上下文和“有界”上下文都应该继承自一个抽象基本上下文类,该类设置连接字符串等信息。
当您的应用程序启动时,您可以简单地尝试实例化您的主上下文,并确保它已迁移到最新版本。但是在您的实际应用程序中,您只使用实现了主上下文子集的“有界”上下文。
我意识到您已经在部分或全部执行了这些步骤,但我认为这是正确的方法。

这也是我想的,而且在我的项目中也是这样做的。看来这是唯一的出路。 - Jürgen Bayer

1
我同意Julie Lermans的建议。事实上,她已经推荐这种方法有一段时间了。 而且你不需要EF6来做到这一点。尽管使用EF6可以更容易地管理。
声明尽可能多的上下文。 关键是在上下文创建期间初始化数据库设置。 基本模式如下
例如
// when you wish to migrate
Database.SetInitializer(new MigrateDatabaseToLatestVersion<YOURCONTEXT, YourContentConfiguration>());
var connie = new YOURCONTEXT(.....);

//When you wish to access but NOT change the DB with a small context.
Database.SetInitializer(new ContextInitializerNone<BoundedMiniDbContext>());
var connie = new BoundedMiniDbContext(.....);

在这里,你的内容从DbContext继承而来,而YourContextConfig则继承自DbMigrationsConfiguration。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接