尽管检查了已应用的迁移,EF Core 幂等迁移脚本仍失败

EF Core idempotent migration script fails despite check on applied migration

我正在使用 EF Core (3.1.15)。在之前的迁移中(也是在 3.1.15 中创建的),引用了一个后来被删除的列。幂等脚本会检查是否在数据库上执行了迁移(是的,引用仍然显示在 __EFMigrationsHistory table 中)。但是,由于列不存在,检查没有预期的结果和脚本。

Q:为什么不存在的列会导致 SQL 脚本的执行失败?

脚本创建于

dotnet-ef migrations script -i -o migrations.sql

自动化脚本的相关部分失败,其中 ReferenceToLedgerId 是在以后的迁移中删除的列:

IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20210612052003_CLedger')
BEGIN
    UPDATE LedgerTable SET LedgerId = ReferenceToLedgerId
END;

错误:

Msg 207, Level 16, State 1, Line 3
Invalid column name 'ReferenceToLedgerId'

当运行以下SQL查询时,结果按预期返回:

SELECT * 
FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20210612052003_CLedger'
MigrationId 产品版本
20210612052003_CLedger 3.1.15

数据库是 Azure SQL 数据库。脚本在本地 SQL 开发数据库上不会失败。从那以后应用了十几次迁移,直到现在脚本失败。

下面是创建特定脚本的调用:

migrationBuilder.Sql("UPDATE LedgerTable set LedgerId = ReferenceToLedgerId", true);

我尝试将 table 和列名称放在方括号中,但这没有任何区别(例如 [ReferenceToLedgerId]。当使用 SQLCMD 时,该脚本在 Azure DevOps 版本中失败并且在使用 Azure Data Studio 时也会失败,两者都访问 Azure SQL 数据库。


附加检查

我更改了脚本以进行快速检查:

PRINT '#Before IF'
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20210612052003_CLedger')
BEGIN
    PRINT '#Within IF'
    --UPDATE LedgerTable SET LedgerId = ReferenceToLedgerId
END;

PRINT '#After IF'

我得到以下结果:

Started executing query at Line 1 #Before IF #After IF Total execution time: 00:00:00.010

如果我取消对 UPDATE 语句的注释,它会再次失败。所以我只能得出结论,代码路径按预期工作,但服务器仍会检查该列是否存在。我不熟悉 SQL 以理解为什么会这样,或者为什么它只针对这一行失败,而该列本身在 SQL 脚本的其他行中被引用而没有失败。

该批处理在 SQL 服务器的每个版本上都会失败。例如

use tempdb
go
create table __EFMigrationsHistory(MigrationId nvarchar(200))
create table LedgerTable(LedgerId int)
go
insert into __EFMigrationsHistory(MigrationId) values (N'20210612052003_CLedger')
go
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20210612052003_CLedger')
BEGIN
    UPDATE LedgerTable SET LedgerId = ReferenceToLedgerId
END;

失败

Msg 207, Level 16, State 1, Line 8
Invalid column name 'ReferenceToLedgerId'.

因为批处理无法解析编译。在 TSQL 批处理中引用不存在的 table 或列是不合法的。

您可以使用动态 SQL 来解决这个问题,这样引用不存在的列的批处理就不会被解析和编译,除非正在应用迁移。

migrationBuilder.Sql("exec('UPDATE LedgerTable set LedgerId = ReferenceToLedgerId')", true);

这在此处记录:

Tip

Use the EXEC function when a statement must be the first or only one in a SQL batch. It might also be needed to work around parser errors in idempotent migration scripts that can occur when referenced columns don't currently exist on a table.

https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/operations