添加迁移后,需要将其部署并应用到数据库。 有多种策略可用于执行此操作,其中一些更适合生产环境,而另一些更适合开发生命周期。
Note
无论部署策略是什么,都应检查生成的迁移并进行测试,然后再将其应用于生产数据库。 迁移可能会在意图是对列进行重命名时删除该列,或者在应用于数据库时因各种原因而失败。
选择部署策略
对于自动部署,请使用 迁移捆绑包。 捆绑包是在 CI 中生成的部署项目,稍后无需.NET SDK、EF Core 工具或应用程序的源代码即可执行。 在应用 SQL 之前,必须查看、修改、存档或交给 DBA 时,请改用 SQL 脚本 。
对于本地开发, dotnet ef database update 或 Update-Database 通常是最简单的选项。 Aspire 项目应使用 Aspire EF Core 迁移集成 来协调本地迁移执行并发布捆绑包或脚本。
| Strategy | 建议用途 | 执行前查看 SQL | 执行时需要 SDK 和源 | 使用 EF 迁移锁定 | 运行 EF 种子设定委托 |
|---|---|---|---|---|---|
| SQL 脚本 | DBA 控制的或审查封闭的部署 | Yes | No | No | No |
| 迁移捆绑包 | 自动化部署 | No | No | Yes | Yes |
| EF 命令行工具 | 本地开发和测试 | No | Yes | Yes | Yes |
| 运行时迁移 | 接受启动迁移权衡的应用程序 | No | No | Yes | Yes |
EF Core 9 及更高版本使用迁移锁定。 同步操作和工具调用 UseSeeding;异步操作调用 UseAsyncSeeding。
对有权更改架构的部署使用单独的标识。 应用程序在运行时使用的标识通常只具有应用程序读取和写入数据所需的权限。
SQL 脚本
当部署过程要求在执行之前检查或更改生成的 SQL 时,建议使用 SQL 脚本。 此策略的优点包括:
- 可以检查 SQL 脚本的准确性;这一点很重要,因为将架构更改应用于生产数据库是一项可能导致数据丢失的潜在危险操作。
- 在某些情况下,可以根据生产数据库的特定需求调整这些脚本。
- SQL 脚本可以与部署技术结合使用,甚至可以在 CI 过程中生成。
- SQL 脚本可以提供给 DBA,并且可以单独管理和存档。
基本用法
以下命令将生成一个从空白数据库到最新迁移的 SQL 脚本:
dotnet ef migrations script
默认情况下,该命令将脚本写入标准输出。 使用 --output (或 -o) 创建具有可预测名称的部署项目:
dotnet ef migrations script --idempotent --output artifacts/migrations.sql
使用 From(to 隐含)
以下命令将生成一个从给定迁移到最新迁移的 SQL 脚本。
dotnet ef migrations script AddNewTables
使用 From 和 To
以下命令将生成一个从指定 from 迁移到指定 to 迁移的 SQL 脚本。
dotnet ef migrations script AddNewTables AddAuditTable
可以使用比 from 更新的 to 来生成回退脚本。
Warning
请记下潜在的数据丢失方案。
脚本生成接受以下两个参数,以指示应生成的迁移范围:
- 应当在运行该脚本之前将from迁移作为应用到数据库的最后一个迁移。 如果未应用任何迁移,请指定
0(默认值)。 - to 迁移是运行该脚本后应用到数据库的最后一个迁移。 它默认为项目中的最后一个迁移。
迁移脚本更新现有数据库。 在应用脚本之前,请通过基础结构部署或数据库管理过程预配数据库本身。 数据库创建通常需要不同的连接、提升的权限和提供程序特定的配置。
幂等 SQL 脚本
上面生成的 SQL 脚本只能用于将架构从一个迁移更改为另一个迁移;你需要适当地应用脚本,并且仅应用于处于正确迁移状态的数据库。 EF Core 还支持生成幂等脚本,此类脚本将在内部检查已经应用哪些迁移(通过迁移历史记录表),并且只应用缺少的迁移。 如果不确知应用到数据库的最后一个迁移,或者需要部署到多个可能分别处于不同迁移的数据库,此类脚本非常有用。
幂等脚本支持取决于数据库提供程序。 例如,SQLite 目前不支持生成幂等迁移脚本。
以下步骤将生成幂等迁移:
dotnet ef migrations script --idempotent
命令行工具
EF 命令行工具可用于将迁移应用到数据库。 这种方法对于迁移的本地开发和测试很有效,但不适合管理生产数据库:
- 该工具会直接应用 SQL 命令,不给开发人员检查或修改的机会。 这在生产环境中可能会很危险。
- .NET SDK 和 EF 工具必须安装在生产服务器上,并且需要项目的源代码。
以下命令将数据库更新为最新迁移:
dotnet ef database update
以下命令将数据库更新为给定迁移的状态:
dotnet ef database update AddNewTables
请注意,这也可用于回滚到较早的迁移。
Warning
请记下潜在的数据丢失方案。
有关通过命令行工具应用迁移的详细信息,请参阅 EF Core 工具参考。
环境和配置
这些工具执行应用程序代码来构造 DbContext. 因此,提供程序选择、连接字符串和模型配置可能取决于应用程序环境。 EF Core 设计时工具在两者ASPNETCORE_ENVIRONMENTDOTNET_ENVIRONMENT均未设置时使用Development环境。
在生成部署项目和执行捆绑包时显式设置环境。 例如,在 PowerShell 中:
$env:ASPNETCORE_ENVIRONMENT = 'Production'
dotnet ef migrations bundle --output artifacts\efbundle.exe
$env:ASPNETCORE_ENVIRONMENT = 'Production'
.\efbundle.exe --connection $env:DEPLOYMENT_CONNECTION_STRING
或在 POSIX 兼容的 shell 中:
ASPNETCORE_ENVIRONMENT=Production \
dotnet ef migrations bundle --output artifacts/efbundle
ASPNETCORE_ENVIRONMENT=Production \
./efbundle --connection "$DEPLOYMENT_CONNECTION_STRING"
这也可以防止捆绑包意外加载开发用户机密。 dotnet/efcore#36188 跟踪捆绑包的更安全的默认环境。 Visual Studio发布体验中的环境选择由 dotnet/efcore#11950 跟踪。
不要将生产连接字符串存储在源代码管理中,也不会将它们嵌入捆绑包中。 从部署系统的机密存储提供部署连接。 部署标识应具有架构权限;通常不应使用普通应用程序标识。
Bundles
迁移捆绑包是单文件可执行文件,可以用于将迁移应用到数据库。 它们解决了 SQL 脚本和命令行工具的一些缺点:
- 执行 SQL 脚本需要额外的工具。
- 这些工具的事务处理和出错时继续行为不一致,有时是意外的。 如果在应用迁移时发生故障,这会使你的数据库处于未定义状态。
- 捆绑包可以作为 CI 过程的一部分生成,并在以后作为部署过程的一部分轻松执行。
- 可以在不安装 .NET SDK 或 EF 工具(甚至.NET运行时(如果是自包含的情况下))的情况下执行程序包,并且不需要项目的源代码。
- 捆绑包使用 EF Core 的迁移锁定并运行配置的
UseSeeding逻辑。
与 SQL 脚本不同,捆绑包当前不提供检查它将执行的 SQL 或列出其包含的迁移的方法。 如果部署需要 SQL 评审,请改为生成脚本。 dotnet/efcore#25872 跟踪捆绑检查改进。
以下将生成一个包:
dotnet ef migrations bundle --output artifacts/efbundle
下面生成适用于 Linux 的自包含包:
dotnet ef migrations bundle --self-contained --target-runtime linux-x64 --output artifacts/efbundle
有关创建捆绑包的详细信息,请参阅 EF Core 工具参考。
efbundle
生成的可执行文件默认命名为 efbundle。 它可用于将数据库更新到最新迁移。 这相当于运行 dotnet ef database update 或 Update-Database。
Arguments:
| Argument | Description |
|---|---|
<MIGRATION> |
目标迁移。 如果为“0”,则还原所有迁移。 默认为上一次迁移。 |
Options:
| Option | Short | Description |
|---|---|---|
--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={;'$Credential;'here'}'
若要回滚数据库,请传递应保留应用的迁移。 传递 0 将还原所有迁移:
.\efbundle.exe PreviousMigration --connection 'Data Source=(local)\MSSQLSERVER;Initial Catalog=Blogging;Integrated Security=True'
.\efbundle.exe 0 --connection 'Data Source=(local)\MSSQLSERVER;Initial Catalog=Blogging;Integrated Security=True'
Warning
回滚执行 Down 比目标更新的每个迁移的操作,并可能导致数据丢失。 在对生产数据使用之前,请查看并测试回滚行为。
配置的种子设定代码在降级后运行。 它必须容忍目标迁移的架构,包括目标 0时缺少的应用程序架构。
Warning
如果上下文配置读取 appsettings.json,请将所需的设置文件与捆绑包一起复制。 配置文件是从捆绑包的执行目录解析的。 不要在这些文件中放置生产机密;通过安全配置源或 --connection 选项提供它们。
容器和部署作业
在生成过程中生成捆绑包,并在数据库正常运行后将其作为一次性部署作业运行。 不要安装 SDK 或在应用程序映像中运行 dotnet ef ,并且不要让每个应用程序副本从其入口点运行迁移。 配置部署平台,使其在成功退出后不重启迁移容器。
对于 Aspire 应用程序, AddEFMigrations 可以在本地开发期间协调迁移。 在发布期间, PublishAsMigrationBundle 可以发出捆绑包或容器映像,并且可以 PublishAsMigrationScript 发出 SQL 脚本。 有关 Azure 容器应用、Docker Compose 和 Kubernetes 的单次作业配置,请参阅 Aspire 中的应用 EF Core 迁移。
迁移捆绑包示例演示了两个 SQLite 迁移:幂等种子设定、前向应用程序和回滚安全种子设定。
迁移工具包示例
捆绑包需要迁移才能包括在内。 这些是按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 update 和 Update-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>
Tip
--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>
Note
这次所有三个迁移都被应用,因为之前它们都还未应用于生产数据库。
在运行时应用迁移
应用程序本身可以以编程方式应用迁移(通常是在启动期间)。 EF Core 9 及更高版本使用数据库范围的锁保护迁移执行,因此,对于喜欢简单部署且可以容忍启动迁移行为的应用程序,这可以接受。 查看最低特权凭据、协调推出或高可用性时,仍首选单独的迁移部署步骤。
请考虑以下权衡:
- 对于 9 之前的 EF 版本,如果应用程序的多个实例正在运行,则两个应用程序可能会尝试同时应用迁移并失败(或者更糟糕的是,导致数据损坏)。
- 同样,如果一个应用程序正在访问数据库,而另一个应用程序正在迁移它,这可能会导致严重的问题。
- 应用程序必须具有提升的访问权限才能修改数据库架构。 在生产环境中限制应用程序的数据库权限通常是一种很好的做法。
- 出现问题时,能够回滚已应用的迁移很重要。 其他策略可以轻松提供此功能,并且开箱即用。
- 程序会直接应用 SQL 命令,不给开发人员检查或修改的机会。 这在生产环境中可能会很危险。
若要以编程方式应用迁移,请调用 context.Database.MigrateAsync()。 例如,典型的 ASP.NET 应用程序可以执行以下操作:
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await db.Database.MigrateAsync();
}
host.Run();
}
请注意,MigrateAsync() 构建于 IMigrator 服务之上,可用于更高级的方案。 请使用 myDbContext.GetInfrastructure().GetService<IMigrator>() 进行访问。
Warning
- 在生产环境中使用此方法之前,请仔细考虑。 需要审核和批准时,首选自动化或 SQL 脚本的迁移捆绑包。
- 请勿在
EnsureCreatedAsync()前调用MigrateAsync()。EnsureCreatedAsync()会绕过迁移创建架构,这会导致MigrateAsync()失败。
迁移锁定
从 EF Core 9 开始,MigrateAsync 和 Migrate 在应用任何迁移之前会自动获取一个数据库范围的锁。 这可以防止数据库损坏,这可能会导致多个应用程序实例并发运行迁移,这是 在运行时应用迁移时的常见方案。 该锁在迁移执行期间(包括任何种子设定代码)一直保持,并在操作完成时自动释放。
使用以下任一方法应用迁移时,迁移锁定适用:
-
dotnet ef database update(.NET CLI) -
Update-Database(程序包管理器 控制台) - 迁移捆绑包
- MigrateAsync 和 Migrate (运行时迁移)
SQL 脚本 不受迁移锁定的影响,因为它们在 EF Core 外部应用。
Note
从 EF Core 9 开始,当模型相较于上一次迁移存在待处理的更改时,调用 Migrate() 或 MigrateAsync() 将引发异常(警告事件 ID RelationalEventId.PendingModelChangesWarning)。 若要在部署之前检测此条件,请使用 dotnet ef migrations has-pending-model-changes CI/CD 管道中的命令。 如有必要,可以通过(忽略ConfigureWarnings)禁止RelationalEventId.PendingModelChangesWarning显示警告,但在生产方案中通常不建议这样做。 更多信息,请参阅重大变更说明。
Warning
锁定机制因数据库提供程序而异,并且可能涉及特定于提供程序的问题。 例如,SQLite 提供程序使用锁表, 如果进程意外终止,该表可能会被放弃。 请始终查阅提供商的文档了解详细信息。
局限性
- 不支持将MigrateAsync包装在显式事务中。 有关详细信息,请参阅 在显式事务中应用迁移时引发的异常。
- 在 SQLite 上,放弃的迁移锁可以 阻止后续迁移。