尽管检查了已应用的迁移,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
我正在使用 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