2016 年 1 月

第 31 卷,第 1 期

数据点 - EF7 迁移: 不是新推出的功能,但绝对经过改进

作者:Julie Lerman | 2016 年 1 月

Julie Lerman在 Entity Framework 7 的作用下,Code First 迁移发生了一些巨变。虽然基本概念和迁移步骤没有变化,但是工作流变得更加简洁直接了,并且现在可以与源控件很好地配合使用。

EF7 带来两种风格的迁移:一种是您所熟悉的用于 NuGet 和大多数 .NET 项目的迁移;另一种是作为 DNX 运行时的一部分运行的迁移,用于 ASP.NET 5 应用程序。

我在我的一个 Pluralsight 课程 (bit.ly/1MThS4J) 中对这些进行了大致介绍。尽管我在该课程中所用的 beta 4 版本与今天所用的 Release Candidate 1 (RC1) 之间内容上有些许差异,不过该课程仍然是实际了解迁移并掌握一些差异的重要资源。

但是随着 RC1(对于未来的 RTM,被认为功能完善)的发布,是时候好好地了解一下 EF7 迁移了。首先,我将会带大家熟悉一下 Visual Studio 的 Package Manger Console 中的 Windows PowerShell 命令。然后,再讨论这些命令在 DNX 和 ASP.NET 5 中的具体使用。本文假设您对 EF6 或更早版本中的数据库初始化和迁移功能已有一些了解。

最具魔力的数据库初始化—使用迁移

支持魔力代价昂贵且有局限性,会带来很大包袱。并且对于 DbInitializer,它制造出很多困惑。因此,EF DbInitializer 连同 CreateData­baseIfNotExists 的默认行为现在已是过去式了。但是,会有逻辑让您可以明确创建和删除数据库(EnsureDeleted、Ensure­Created),我在集成测试中将会乐于使用这些逻辑。

EF7 设计为依赖迁移。它们现在应该是您的默认工作流。

存在“enable-migrations”命令是因为迁移是为 EF 设计的,需要构建一些基础架构才能使用它们。该命令没有安装任何新的 API;它只是在您的项目中添加了一个项目文件夹和新的 DbMigrationConfiguration 类。对于 EF7,相关逻辑是内部的,您不必明确告知 EF 您想要使用迁移。

记住,EF7 是可组合的。并不是所有人都想要或需要迁移,因此,如果您想要进行迁移,您需要通过安装包含迁移命令的程序包来选择使用这些命令:

install-package entityframework.commands -pre

EF7 迁移命令

使用 Windows PowerShell Get-Help cmdlet 会显示当前的(自 RC1 起)迁移命令,如图 1 所示。对于 RTM,该列表应该是一样的。

通过 NuGet Package Manager Get-Command 列出的迁移命令
图 1 通过 NuGet Package Manager Get-Command 列出的迁移命令

另请注意,Update-Database 不再包含“-script”参数。顾名思义,它只更新数据库。和往常一样,建议不要在生产数据库上使用该命令。图 2 显示了关于 Update-Database 命令的详细信息。

Update-Database 语法和参数
图 2 Update-Database 语法和参数

若要创建脚本,您可以使用新的 Script-Migration 命令。创建幂等脚本不再有什么秘诀(如 EF6 中所介绍)。幂等是一个参数,您在图 3 中可以看到。

Script-Migration 参数
图 3 Script-Migration 参数

迁移历史记录和模型快照的巨大改变

EF7 中已经删除了自动迁移。这使管理迁移变得更加简单,并且现在还支持源控件。

为了便于迁移确定需要执行哪些操作,必须有先前迁移的历史记录。每次运行 add-migration 命令时,API 都会查看历史信息。以前,迁移将每次迁移的模型快照的编码版本存储在数据库中名为 _MigrationHistory 的表(最新)中。EF 常常用它来指出迁移需要完成的操作。每次运行 add-migration 时,就意味着数据库要通读该历史记录,这也是出现很多问题的缘由。

对于 EF7,(通过 add-migration)创建迁移时,除了 Migrations 文件夹中比较熟悉的迁移文件之外,EF7 还会创建一个文件用于存储当前的模型快照。该模型采用用于配置的 Fluent API 语法进行描述。如果您对模型进行了更改后添加迁移,则会通过完整模型的当前描述对快照进行修改。图 4 显示了一个迁移文件,该文件引入了一个名为“MyProperty”的新字符串。

图 4 完整的 newStringInSamurai 迁移类

public partial class newStringInSamurai : Migration
  {
    protected override void Up(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.AddColumn<string>(
        name: "MyProperty",
        table: "Samurai",
        nullable: true);
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.DropColumn(name: "MyProperty", table: "Samurai");
    }
  }

这是更新的 ModelSnapshot 类的部分,其中您可以看到 “MyProperty”现在是 Samurai 类描述的一部分:

modelBuilder.Entity("EF7Samurai.Domain.Samurai", b =>
  {
    b.Property<int>("Id")
      .ValueGeneratedOnAdd();
    b.Property<string>("MyProperty");
    b.Property<string>("Name");
    b.HasKey("Id");
  });

因此,和原来一样,各个迁移描述更改了哪些内容。但是现在,在项目中,您始终都有完整模型的描述。这就表示,当您添加新迁移时,EF 不需要转到数据库中来了解以前的模型是什么样。它需要的就在手边。并且更为重要的是,这与源控件配合得更好,因为它就在您的代码中,可以像任何其他文件那样由源控件区分和合并。数据库中没有隐藏内容,甚至您的同伴也可能无法访问。在 EF7 之前,没有什么好的方法来管理源控件中的迁移。您将可以在“Code First Migrations in Team Environments”(bit.ly/1OVO3qr) 中找到关于早于 EF7 的版本的迁移的相关指导。本文首先建议: “捧杯咖啡,您需要坐下来慢慢品读整篇文章。”

对于 EF7,数据库中的迁移历史记录表(图 5)现在仅存储迁移的 ID 以及创建迁移的 EF 版本。并且,这相较于先前的版本是个很大的改变,先前的版本将快照本身存储在此表中。Update-Database 将检查此表来查看已应用哪些迁移并应用缺失的那些迁移,即使这些迁移不连续。只要此操作需要触碰数据库,在此方案中对数据库进行快速预先检查的迁移就不会出现问题。

显示添加迁移的表的 dbo_EFMigrationsHistory
图 5 显示添加迁移的表的 dbo_EFMigrationsHistory

您再也不需要处理当某个迁移的前一个迁移尚未应用至数据库时无法添加该迁移这样无意义的事了。那是自动迁移的负面影响,导致我们很多人陷入一系列错综复杂的困惑之中。现在,您可以根据需要在您的解决方案中构建迁移,然后待一切就绪后更新数据库。

鉴于 EF7 存储在项目中的模型快照,您不应单单从项目中删除迁移文件,因为这会导致快照不正确。因此,随着对迁移工作流的更改,还出现了新的命令:remove-migration。Remove-migration 设计用于在您很可能刚刚添加而后又改变主意的迁移应用至数据库之前对其进行撤销。该命令在您的项目中执行两项任务: 它删除最新的迁移文件,然后更新 Migrations 文件夹中的快照文件。有趣的是,这不会基于模型重建快照。相反,它基于随每个快照一起创建的 Designer.cs 文件中收藏的模型快照修改快照。我对此进行了测试,发现如果我未更改模型(即,我未从 Samurai 类中删除不想要的属性),仍会根据迁移修复快照。但是,也请不要忘记对您的模型进行修复。否则,模型和快照将会失去同步,且很可能您的数据库架构也将与模型失去同步。

顺便说一下,负责迁移的 EF 团队成员 Brice Lambson 与我分享了一点:如果您是手动删除迁移文件,就在下次调用 remove-­migration 时将会看到,并在不删除另一迁移文件的情况下修复快照。

就像 add-migration 一样,remove-­migration 对数据库没有影响。但是,它会检查数据库的迁移历史记录文件来了解是否已应用迁移。如果已应用,那么您不能使用 remove-migration,至少目前还不能。

我知道这听起来似乎有点晕。尽管如此,但我仍然喜欢 remove-migration,因为只要您改变主意,备用方案都只会一味向前继续操作并添加迁移。这对于其他团队成员或者是未来的您来说可能有点晕。并且,只需很低的价格便可实现允许迁移参与到源控件中这一优势。

为了进一步说明,图 6 提供了解除不想要的迁移的相关指导。该图表假设“Migration1”已应用至数据库,“Migration2”已创建但从未应用至数据库。

图 6 处理不想要的迁移

数据库状态 命令 命令执行的任务
Migration2 之后 Update-Database 运行 Remove-Migration

1.确认 Migration2 不在历史记录表中

2.删除 Migration2.cs

3.修复 Snapshot.cs 以反映先前的迁移

Migration2 之后 Update-Database 运行

Update-Database Migration1

Remove-Migration

1.针对 Migration2 的“Drop”方法运行 SQL

2.从历史记录表中删除 Migration2 行

1.删除 Migration2.cs

2.修复 Snapshot.cs 以反映先前的迁移

如果您需要撤销多个迁移,请先调用 update-database,后跟上次正常迁移的名称。然后可以调用 remove-migration 来回滚迁移和快照,一次一个。

所有这些都重点关注迁移,别忘了这些迁移是对模型更改的反映。所以,最后提醒您一下:您必须手动撤销对模型的更改,然后再删除反映这些更改的任何迁移。

通过 Scaffold-DbContext 进行反向工程

先前版本的 EF 提供了将数据库反向工程到 Code First 模型的功能。Microsoft 通过 Entity Framework Power Tools 提供该功能,并且自 EF6 开始,通过 EF Designer 提供该功能。此外,还有一些第三方工具,例如非常受欢迎的(免费)扩展 EntityFramework Reverse POCO Generator (reversepoco.com)。因为 EF7 没有设计器,所以创建了 scaffold 命令来执行此任务。

下面是 Scaffold-DbContext 的参数:

Scaffold-DbContext [-Connection] <String> [-Provider] <String>
                   [-OutputDirectory <String>] [-ContextClassName <String>]
                   [-Tables <String>] [-DataAnnotations] [-Project <String>]
                   [<CommonParameters>]

我是 Fluent API 配置的超级粉丝,所以我很高兴这些配置再次成为默认配置,您需要改用采用 DataAnnotations 标志的数据注释。下面是使用该参数从我现有的数据库进行反向工程的命令(我也可以选择要对哪些表构建基架,但这里就不再赘述了):

Scaffold-DbContext
   -Connection "Server = (localdb)\mssqllocaldb; Database=EF7Samurai;"
   -Provider entityframework.sqlserver
   -OutputDirectory "testRE"

图 7 所示,这导致在我的项目中添加了一个新的文件夹和一些文件。我需要对这些类执行更多的检查,因为这是一项非常重要的功能,但是我将留待以后检查。

Scaffold-DbContext 的结果
图 7 Scaffold-DbContext 的结果

迁移命令的 DNX 版本

您在 Package Manager Console 中运行的命令是 Windows PowerShell 命令,而 Windows PowerShell 当然也属于 Windows。ASP.NET 5(以及 EF7)一项最重要的功能是它们不再依赖于 Windows。DNX 是可以运行于 OS X 和 Linux 以及 Windows 之上的轻量级 .NET 执行环境。您可在 bit.ly/1Gu34wX 上进行详细了解。entityframework.commands 程序包还包含用于 DNX 环境的命令。让我们快速了解一下这些命令。

如果恰巧使用我将在此介绍的 Visual Studio 2015,则可以直接在 Package Manager Console 中执行这些命令。否则,请在您相关操作系统的命令行中执行这些命令。查看 Nate McMaster 的 Channel 9 视频,在该视频中,他在 Mac 上使用 EF7 和迁移。(bit.ly/1OSUCdo)

首先,您需要确定您的文件夹正确无误,即您的模型所在的项目的文件夹。默认的项目下拉列表与文件路径无关。使用 dir 命令查看您所在的位置,然后只需对正确的文件夹执行 cd 命令即可。在此,Tab 自动补全是您的好帮手。在图 8 中,尽管默认项目是 src\EF7WebAPI,但您仍然可以看到,为了在该目录中执行 dnx 命令,我需要明确将该目录更改为此项目的路径,其中包含我的 EF7 模型。

运行 DNX 命令前进入正确的目录
图 8 运行 DNX 命令前进入正确的目录

在该目录中,我可以执行命令。顺便说一下,每个命令都需要以 dnx 开头,您可以使用 dnx ef 列出所有可用的命令。注意,ef 是我在 project.json 中的设置的一个快捷方式。查看我的 Pluralsight 视频“Looking Ahead to Entity Framework 7”(bit.ly/PS_EF7Alpha),了解有关设置的更多信息。

核心命令包括 database、dbcontext 和 migrations;migrations 具有以下子命令:

add     Add a new migration
list    List the migrations
remove  Remove the last migration
script  Generate a SQL script from migrations

每个命令都有您可以通过添加 --help 来进行查询的参数,如下所示:

dnx ef migrations add --help

例如,如果迁移名称是 initial,则命令如下:

dnx migrations add initial

切记,这些正是您在本文前面所见到的命令,所以您将会找到允许您指定项目、DbContext 以及其他详细信息的参数。脚本命令具有前面所述的幂等参数。但是默认情况下,dnx 脚本命令的一个不同之处在于,脚本只列在命令窗口中。您需要明确指定输出 <file> 参数来将脚本推送至文件中。

以下命令会将迁移应用至数据库:

dnx ef database update

dbcontext 命令有两个子命令:dbcontext list 将列出可以在项目中发现的所有 DbContext。这里之所以有该命令,是因为您不能像使用 Windows PowerShell 中的命令时那样轻松让 tab-expansion 提供选项;scaffold 是您之前所学到的 DbContext-Scaffold 命令的 dnx 版本。

总体上好很多的 EF7 迁移体验

删除自动迁移后,EF7 中的迁移得到简化。尽管基本概念没有变化,但是工作流更加简洁了。消除在团队环境中使用迁移的障碍非常关键。我很喜欢通过将模型快照带入源代码对此进行修复的方式。并且,允许通过 scaffold 命令进行反向工程是不依赖设计器提供这一重要功能的绝佳方法。

命名机制变得更加严格后,EF 团队 7 月 23 号的设计会议记录 (bit.ly/1S813ro) 纳入了一个很不错的表,其中包含所有 Windows PowerShell 和 dnx 命令。我预计最终文档中甚至会有更简洁的版本,但我仍然觉得该版本很有用。

我是使用 EF7 的 RC1 版本撰写本文的,但是该版本被认为功能完善,所以很可能该信息将与 2016 年初伴随 ASP.NET 5 的 EF7 的发布同步。


Julie Lerman 是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 .NET 主题的演示。她的博客网址为 thedatafarm.com/blog,她是《Programming Entity Framework》(2009 年)以及 Code First 版和 DbContext 版(均由奥莱利媒体出版)的作者。通过 Twitter 关注她:@julielerman 并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。

衷心感谢以下 Microsoft 技术专家对本文的审阅: Brice Lambson