使用现有数据库的 Code First 迁移

注意

仅限 EF4.3 及更高版本 - 此页面中讨论的功能、API 等已引入 Entity Framework 4.1。 如果使用的是早期版本,则部分或全部信息不适用。

本文介绍如何将 Code First 迁移与现有数据库一起使用,该数据库并非由实体框架创建。

注意

本文假设你了解如何在基本场景中使用 Code First 迁移。 如果你不知道,则需要先阅读 Code First 迁移,再继续。

步骤 1:创建模型

第一步是创建一个面向现有数据库的 Code First 模型。 对现有数据库使用 Code First 主题提供了有关如何执行此操作的详细指南。

注意

在对模型作出任何数据库架构需与之同步的更改前,请务必遵循本主题中的其余步骤。 以下步骤要求模型与数据库架构保持同步。

步骤 2:启用迁移

下一步是启用迁移。 可通过在包管理器控制台中运行 Enable-Migrations 命令来执行此操作

此命令将在解决方案中创建一个名为 Migrations 的文件夹,并在其中放置一个名为 Configuration 的类。 Configuration 类是你配置应用程序迁移的位置,有关此类的详细信息,请参阅 Code First 迁移主题。

步骤 3:添加初始迁移

创建迁移并应用到本地数据库后,你可能还想要将这些更改应用于其他数据库。 例如,你的本地数据库可能是一个测试数据库,你最终可能还希望将更改应用于生产数据库和/或其他开发人员测试数据库。 此步骤有两个选项,应选择哪个选项取决于任何其他数据库的架构是否为空或当前是否与本地数据库的架构匹配。

  • 选项一:使用现有架构作为起点。 如果将来将应用迁移的其他数据库具有与本地数据库当前相同的架构,应采用此方法。 例如,如果本地测试数据库当前与生产数据库的 v1 匹配,并且你稍后将应用这些迁移以将生产数据库更新到 v2,则可以使用该选项。
  • 选项二:使用空数据库作为起点。 如果将来将应用迁移的其他数据库为空(或尚不存在),应采用此方法。 例如,如果开始使用测试数据库开发应用程序但不使用迁移,并且你稍后希望从头开始创建生产数据库,则可以使用该选项。

选项一:使用现有架构作为起点

Code First 迁移使用存储在最近迁移中的模型快照来检测模型的更改(可以在团队环境中的 Code First 迁移中找到关于此的详细信息)。 由于我们将假设数据库已拥有当前模型的架构,因此我们将生成一个空(无操作)迁移,该迁移将当前模型作为快照。

  1. 在包管理器控制台中运行 Add-Migration InitialCreate –IgnoreChanges 命令。 这将创建一个以当前模型作为快照的空迁移。
  2. 在包管理器控制台中运行 Update-Database 命令。 这会将 InitialCreate 迁移应用到数据库。 由于实际迁移不包含任何更改,因此它只会向 __MigrationsHistory 表添加一行,指示已应用此迁移。

选项二:使用空数据库作为起点

在这个场景中,我们需要迁移能够从头开始创建整个数据库,包括本地数据库中已存在的表。 我们将生成一个 InitialCreate 迁移,其中包含用于创建现有架构的逻辑。 然后,我们将使现有数据库看起来就像已应用此迁移一样。

  1. 在包管理器控制台中运行 Add-Migration InitialCreate 命令。 这将创建一个迁移以创建现有架构。
  2. 注释掉新创建迁移的 Up 方法中的所有代码。 这样便可以将迁移“应用”到本地数据库,而无需尝试重新创建所有已经存在的表等。
  3. 在包管理器控制台中运行 Update-Database 命令。 这会将 InitialCreate 迁移应用到数据库。 由于实际迁移不包含任何更改(因为已暂时将其注释掉),因此它只会向 __MigrationsHistory 表添加一行,指示已应用此迁移。
  4. 取消注释 Up 方法中的代码。 这意味着,当此迁移应用于将来的数据库时,本地数据库中已经存在的架构将由迁移创建。

注意事项

在对现有数据库使用迁移时,需要注意一些事项。

默认/计算名称可能与现有架构不匹配

迁移功能在为迁移搭建基架时会显式指定列和表的名称。 但是,在应用迁移时,迁移功能会计算其他数据库对象的默认名称。 这包括索引和外键约束。 面向现有架构时,这些计算名称可能与数据库中实际存在的名称不匹配。

下面是需要注意这种情况的一些示例:

如果使用了步骤 3 中的“选项一:使用现有架构作为起点”:

  • 如果模型未来的变化需要更改或删除以不同方式命名的数据库对象之一,则需要修改已搭建基架的迁移以指定正确的名称。 迁移 API 有一个可选的 Name 参数,可用于执行此操作。 例如,现有架构可能有一个 Post 表,该表的 BlogId 外键列有一个名为 IndexFk_BlogId 的索引。 但是,默认情况下,迁移会期望将此索引命名为 IX_BlogId。 如果对模型进行更改导致删除此索引,则需要修改已搭建基架的 DropIndex 调用以指定 IndexFk_BlogId 名称。

如果使用了步骤 3 中的“选项二:使用空数据库作为起点”:

  • 尝试对本地数据库运行初始迁移的 Down 方法(即恢复到空数据库)可能会失败,因为迁移将尝试使用不正确的名称删除索引和外键约束。 这只会影响本地数据库,因为其他数据库将使用初始迁移的 Up 方法从头开始创建。 如果要将现有的本地数据库降级为空状态,手动执行该操作最为简单,通过删除数据库或删除所有表即可实现。 在此次初始降级后,所有数据库对象都将使用默认名称重新创建,因此该问题不会再次出现。
  • 如果模型未来的变化需要更改或删除以不同方式命名的数据库对象之一,这将不适用于现有的本地数据库,因为名称与默认值不匹配。 但是,它适用于“从头开始”创建的数据库,因为它们将使用迁移选择的默认名称。 可以在本地现有数据库上手动进行这些更改,也可以考虑让迁移从头开始重新创建数据库,就像在其他计算机上一样。
  • 使用初始迁移的 Up 方法创建的数据库可能与本地数据库略有不同,因为将使用索引和外键约束的计算默认名称。 默认情况下,迁移将在外键列上创建索引,因此最终可能会得到额外的索引,在原始本地数据库中可能并非如此。

并非所有数据库对象都在模型中表示

迁移不会处理不属于模型的数据库对象。 这可能包括视图、存储过程、权限、不属于模型的表、附加索引等。

下面是需要注意这种情况的一些示例:

  • 无论你在“第 3 步”中选择了哪个选项,如果模型将来的变化需要更改或删除这些附加对象,Migrations 将不知道要进行这些更改。 例如,如果你删除了一个包含附加索引的列,迁移将不知道要删除该索引。 你将需要手动将其添加到已搭建基架的迁移。
  • 如果使用“选项二:使用空数据库作为起点”,则初始迁移的 Up 方法将不会创建这些附加对象。 如果愿意,可以修改 Up 和 Down 方法来处理这些附加对象。 对于迁移 API 本机不支持的对象(例如视图),可以使用 Sql 方法运行原始 SQL 来创建/删除它们。