应用迁移

添加迁移后,需要将其部署并应用到数据库。 有多种策略可用于执行此操作,其中一些更适合生产环境,而另一些更适合开发生命周期。

注意

无论部署策略是什么,都应检查生成的迁移并进行测试,然后再将其应用于生产数据库。 迁移可能会在意图是对列进行重命名时删除该列,或者在应用于数据库时因各种原因而失败。

SQL 脚本

建议通过生成 SQL 脚本,将迁移部署到生产数据库。 此策略的优点包括:

  • 可以检查 SQL 脚本的准确性;这一点很重要,因为将架构更改应用于生产数据库是一项可能导致数据丢失的潜在危险操作。
  • 在某些情况下,可以根据生产数据库的特定需求调整这些脚本。
  • SQL 脚本可以与部署技术结合使用,甚至可以在 CI 过程中生成。
  • SQL 脚本可以提供给 DBA,并且可以单独管理和存档。

基本用法

以下命令将生成一个从空白数据库到最新迁移的 SQL 脚本:

dotnet ef migrations script

使用 From(to 隐含)

以下命令将生成一个从给定迁移到最新迁移的 SQL 脚本。

dotnet ef migrations script AddNewTables

使用 From 和 To

以下命令将生成一个从指定 from 迁移到指定 to 迁移的 SQL 脚本。

dotnet ef migrations script AddNewTables AddAuditTable

可以使用比 to 新的 from 来生成回退脚本。

警告

请记下潜在的数据丢失方案。

脚本生成接受以下两个参数,以指示应生成的迁移范围:

  • from 迁移应是运行该脚本前应用到数据库的最后一个迁移。 如果未应用任何迁移,请指定 0(默认值)。
  • to 迁移是运行该脚本后应用到数据库的最后一个迁移。 它默认为项目中的最后一个迁移。

幂等 SQL 脚本

上面生成的 SQL 脚本只能用于将架构从一个迁移更改为另一个迁移;你需要适当地应用脚本,并且仅应用于处于正确迁移状态的数据库。 EF Core 还支持生成幂等脚本,此类脚本将在内部检查已经应用哪些迁移(通过迁移历史记录表),并且只应用缺少的迁移。 如果不确知应用到数据库的最后一个迁移,或者需要部署到多个可能分别处于不同迁移的数据库,此类脚本非常有用。

以下命令将生成幂等迁移:

dotnet ef migrations script --idempotent

命令行工具

EF 命令行工具可用于将迁移应用到数据库。 这种方法对于迁移的本地开发和测试很有效,但不适合管理生产数据库:

  • 该工具会直接应用 SQL 命令,不给开发人员检查或修改的机会。 这在生产环境中可能会很危险。
  • .NET SDK 和 EF 工具必须安装在生产服务器上,并且需要项目的源代码。

注意

每个迁移都应用于其自己的事务中。 有关此领域将来可能的增强功能的讨论,请参阅 GitHub 问题 #22616

以下命令将数据库更新为最新迁移:

dotnet ef database update

以下命令将数据库更新为给定迁移:

dotnet ef database update AddNewTables

请注意,这也可用于回滚到较早的迁移。

警告

请记下潜在的数据丢失方案。

有关通过命令行工具应用迁移的详细信息,请参阅 EF Core 工具参考

捆绑包

迁移捆绑包是单文件可执行文件,不能用于将迁移应用到数据库。 它们解决了 SQL 脚本和命令行工具的一些缺点:

  • 执行 SQL 脚本需要额外的工具。
  • 这些工具的事务处理和出错时继续行为不一致,有时是意外的。 如果在应用迁移时发生故障,这会使你的数据库处于未定义状态。
  • 捆绑包可以作为 CI 过程的一部分生成,并在以后作为部署过程的一部分轻松执行。
  • 可以在不安装 .NET SDK 或 EF 工具(甚至是 .NET 运行时,如果是自包含)的情况下执行捆绑包,并且不需要项目的源代码。

下面生成一个捆绑包:

dotnet ef migrations bundle

下面生成适用于 Linux 的自包含捆绑包:

dotnet ef migrations bundle --self-contained -r linux-x64

有关创建捆绑包的详细信息,请参阅 EF Core 工具参考

efbundle

生成的可执行文件默认命名为 efbundle。 它可用于将数据库更新到最新迁移。 这相当于运行 dotnet ef database updateUpdate-Database

参数:

参数 说明
<MIGRATION> 目标迁移。 如果为“0”,则还原所有迁移。 默认为上一次迁移。

选项:

选项 Short 说明
--connection <CONNECTION> 用于连接到数据库的连接字符串。 默认为 AddDbContext 或 OnConfiguring 中指定的值。
--verbose -v 显示详细输出。
--no-color 请勿为输出着色。
--prefix-output 具有级别的前缀输出。

以下示例使用指定的用户名和密码将迁移应用于本地 SQL Server 实例。

.\efbundle.exe --connection 'Data Source=(local)\MSSQLSERVER;Initial Catalog=Blogging;User ID=myUsername;Password=myPassword'

警告

不要忘记将 appsettings.json 与捆绑包一起复制。 捆绑包依赖于执行目录中是否存在 appsettings.json。

迁移捆绑包示例

捆绑包需要迁移才能包括在内。 这些是按创建第一个迁移中所述,使用 dotnet ef migrations add 创建的。 准备好部署迁移后,请使用 dotnet ef migrations bundle 创建捆绑包。 例如:

PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations bundle
Build started...
Build succeeded.
Building bundle...
Done. Migrations Bundle: C:\local\AllTogetherNow\SixOh\efbundle.exe
PS C:\local\AllTogetherNow\SixOh>

输出是适用于目标操作系统的可执行文件。 在本例中,这是 Windows x64,因此我在本地文件夹中删除了 efbundle.exe。 运行此可执行文件将应用包含在其中的迁移:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
Applying migration '20210903083845_MyMigration'.
Done.
PS C:\local\AllTogetherNow\SixOh>

dotnet ef database updateUpdate-Database 一样,仅当迁移尚未应用时,才会将迁移应用于数据库。 例如,再次运行同一捆绑包将不会有任何反应,因为没有要应用的新迁移:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
No migrations were applied. The database is already up to date.
Done.
PS C:\local\AllTogetherNow\SixOh>

但是,如果对模型进行更改,并且使用 dotnet ef migrations add 生成了更多的迁移,则可以将这些迁移捆绑到新的可执行文件中,准备应用。 例如:

PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations add SecondMigration
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations add Number3
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations bundle --force
Build started...
Build succeeded.
Building bundle...
Done. Migrations Bundle: C:\local\AllTogetherNow\SixOh\efbundle.exe
PS C:\local\AllTogetherNow\SixOh>

提示

--force 选项可用于使用新的绑定覆盖现有绑定。

执行此新捆绑包会将这两个新迁移应用到数据库:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
Applying migration '20210903084526_SecondMigration'.
Applying migration '20210903084538_Number3'.
Done.
PS C:\local\AllTogetherNow\SixOh>

默认情况下,捆绑包使用应用程序配置中的数据库连接字符串。 但是,可以通过在命令行上传递连接字符串来迁移不同的数据库。 例如:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe --connection "Data Source=(LocalDb)\MSSQLLocalDB;Database=SixOhProduction"
Applying migration '20210903083845_MyMigration'.
Applying migration '20210903084526_SecondMigration'.
Applying migration '20210903084538_Number3'.
Done.
PS C:\local\AllTogetherNow\SixOh>

注意

这次所有三个迁移都已应用,因为它们都尚未应用于生产数据库。


在运行时应用迁移

应用程序本身可以以编程方式应用迁移(通常是在启动期间)。 这种方法对于迁移的本地开发和测试很有效,但不适合管理生产数据库,原因如下:

  • 如果应用程序的多个实例正在运行,这两个应用程序可能会尝试同时应用迁移并失败(更糟糕的情况是导致数据损坏)。
  • 同样,如果一个应用程序正在访问数据库,而另一个应用程序正在迁移它,这可能会导致严重的问题。
  • 应用程序必须具有提升的访问权限才能修改数据库架构。 在生产环境中限制应用程序的数据库权限通常是一种很好的做法。
  • 出现问题时,能够回滚已应用的迁移很重要。 其他策略可以轻松提供此功能,并且开箱即用。
  • 程序会直接应用 SQL 命令,不给开发人员检查或修改的机会。 这在生产环境中可能会很危险。

若要以编程方式应用迁移,请调用 context.Database.Migrate()。 例如,典型的 ASP.NET 应用程序可以执行以下操作:

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();

    using (var scope = host.Services.CreateScope())
    {
        var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        db.Database.Migrate();
    }

    host.Run();
}

请注意,Migrate() 构建于 IMigrator 服务之上,可用于更高级的方案。 请使用 myDbContext.GetInfrastructure().GetService<IMigrator>() 进行访问。

警告

  • 在生产环境中使用此方法之前,请仔细考虑。 经验表明,此部署策略的简单性会被其产生的问题抵消掉。 请考虑改从迁移生成 SQL 脚本。
  • 请勿在 Migrate() 前调用 EnsureCreated()EnsureCreated() 会绕过迁移创建架构,这会导致 Migrate() 失败。