向上迁移时尝试更改列数据时出现锁定问题
Locking issue when trying to alter column data in migration up
我正在尝试将 sql 语句添加到我当前项目的迁移的 up-method 中。数据库是Ms Access 数据库。迁移在 运行 时间内得到应用。
情况如下:
我有一个基础 Initial-create
迁移,在我的例子中假定它已经应用。由于此应用程序的性质,我们有一个 table A,它包含某种外键,但没有定义任何 sql-约束。这意味着外键关系是通过程序代码设计的,而不是 sql 表示外键关系。键是一个字符串,如果没有外来元素,则值为空。
现在我们要添加一个新的迁移,它通过 sql-constraints 强制执行这种关系。这通过标准的 ef-core 迁移代码工作得很好,但是当迁移应用于非空数据库时就会出现问题。 sql 外键需要 table A 中的所有空字符串为空(否则我们会得到一个异常)
看似简单的解决方案是在新迁移的 up-method 中添加以下语句:
UPDATE A SET ForeignKeyColumn = NULL WHERE ForeignKeyColumn & \"\" = \"\""
但这会导致以下异常:
System.Data.OleDb.OleDbException (0x80040E14): The database engine could not lock table 'A' because it is already in use by another person or process.
at System.Data.OleDb.OleDbCommand.ExecuteCommandTextErrorHandling(OleDbHResult hr)
at System.Data.OleDb.OleDbCommand.ExecuteCommandTextForSingleResult(tagDBPARAMS dbParams, Object& executeResult)
at System.Data.OleDb.OleDbCommand.ExecuteCommandText(Object& executeResult)
at System.Data.OleDb.OleDbCommand.ExecuteCommand(CommandBehavior behavior, Object& executeResult)
at System.Data.OleDb.OleDbCommand.ExecuteReaderInternal(CommandBehavior behavior, String method)
at System.Data.OleDb.OleDbCommand.ExecuteNonQuery()
at EntityFrameworkCore.Jet.Data.JetCommand.ExecuteNonQueryCore()
at EntityFrameworkCore.Jet.Data.JetCommand.<>c.<ExecuteNonQuery>b__40_0(Int32 _, JetCommand command)
at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
at EntityFrameworkCore.Jet.Data.JetCommand.ExecuteNonQuery()
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteNonQuery(RelationalCommandParameterObject parameterObject)
at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
at X.Infrastructure.Setup.Migrate(IFactory`1 pDatabaseContextFactory, String pDatabasePath)
at X.COM.XCOMWrapper.Setup(ISettingsProvider pSettingsProvider)
然而,如果我们从迁移代码中删除这条 sql 语句并按如下方式执行,在调用 context.Database.Migrate()
之前:
var dbConnection = context.Database.GetDbConnection();
dbConnection.Open();
using (var transaction = dbConnection.BeginTransaction())
{
var updateForeignKeyReferences= dbConnection.CreateCommand();
updateForeignKeyReferences.CommandText = "UPDATE A SET ForeignKeyColumn = NULL WHERE ForeignKeyColumn & \"\" = \"\"";
updateForeignKeyReferences.ExecuteNonQuery();
transaction.Commit();
}
dbConnection.Close();
它工作得很好。
我在 up-method 中使用 sql 代码的方法是完全错误的吗?可能的原因是什么?最重要的是,我该如何解决这个问题?第二种方法是我目前解决这个问题的方法,但我担心这意味着在很长一段时间内 运行 我无法使用迁移机制,必须寻求自定义解决方案(或其他框架)。我宁愿坚持使用 ef 核心。
重要:
此应用程序与遗留应用程序一起使用,我们必须在初始启动时通过 sql 代码插入应用程序历史记录。为此,我们创建一个事务并简单地创建历史记录 table 并插入最初创建的 table。这工作得很好,交易和命令都应该关闭。 table A 从未被此函数触及。
使用 migrationBuilder.Sql("UPDATE `A` SET `ForeignKeyColumn` = NULL WHERE `ForeignKeyColumn` = ''")
是正确的步骤。
它应该可以正常执行。
不幸的是,似乎有一个问题,当 CREATE INDEX
语句被执行时(有已为您的新导航 属性 生成并且是 Up()
迁移方法的一部分)。
这只是一个问题,如果两个语句都在同一个事务中执行(默认情况下迁移就是这种情况)。否则,不持有锁并且 CREATE INDEX
语句成功。
解决这个问题的最简单方法是将 migrationBuilder.Sql()
参数 suppressTransaction
设置为 true
。
这将在事务的其余部分之外执行语句,并且不会锁定 table:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"UPDATE `A` SET `ForeignKeyColumn` = NULL WHERE `ForeignKeyColumn` = ''",
suppressTransaction: true);
migrationBuilder.CreateIndex(/* ... */);
migrationBuilder.AddForeignKey(/* ... */);
}
另一种方式,能够在事务内部执行UPDATE
语句,就是在自己的专用迁移中执行命令:
- 添加空迁移。将您的
migrationBuilder.Sql()
调用添加到此迁移的空 Up()
方法中。
- 添加实际迁移(包含
CreateIndex()
和 AddForeignKey()
操作)。
- 将两个迁移应用到您的数据库。
我正在尝试将 sql 语句添加到我当前项目的迁移的 up-method 中。数据库是Ms Access 数据库。迁移在 运行 时间内得到应用。
情况如下:
我有一个基础 Initial-create
迁移,在我的例子中假定它已经应用。由于此应用程序的性质,我们有一个 table A,它包含某种外键,但没有定义任何 sql-约束。这意味着外键关系是通过程序代码设计的,而不是 sql 表示外键关系。键是一个字符串,如果没有外来元素,则值为空。
现在我们要添加一个新的迁移,它通过 sql-constraints 强制执行这种关系。这通过标准的 ef-core 迁移代码工作得很好,但是当迁移应用于非空数据库时就会出现问题。 sql 外键需要 table A 中的所有空字符串为空(否则我们会得到一个异常)
看似简单的解决方案是在新迁移的 up-method 中添加以下语句:
UPDATE A SET ForeignKeyColumn = NULL WHERE ForeignKeyColumn & \"\" = \"\""
但这会导致以下异常:
System.Data.OleDb.OleDbException (0x80040E14): The database engine could not lock table 'A' because it is already in use by another person or process.
at System.Data.OleDb.OleDbCommand.ExecuteCommandTextErrorHandling(OleDbHResult hr)
at System.Data.OleDb.OleDbCommand.ExecuteCommandTextForSingleResult(tagDBPARAMS dbParams, Object& executeResult)
at System.Data.OleDb.OleDbCommand.ExecuteCommandText(Object& executeResult)
at System.Data.OleDb.OleDbCommand.ExecuteCommand(CommandBehavior behavior, Object& executeResult)
at System.Data.OleDb.OleDbCommand.ExecuteReaderInternal(CommandBehavior behavior, String method)
at System.Data.OleDb.OleDbCommand.ExecuteNonQuery()
at EntityFrameworkCore.Jet.Data.JetCommand.ExecuteNonQueryCore()
at EntityFrameworkCore.Jet.Data.JetCommand.<>c.<ExecuteNonQuery>b__40_0(Int32 _, JetCommand command)
at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
at EntityFrameworkCore.Jet.Data.JetCommand.ExecuteNonQuery()
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteNonQuery(RelationalCommandParameterObject parameterObject)
at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
at X.Infrastructure.Setup.Migrate(IFactory`1 pDatabaseContextFactory, String pDatabasePath)
at X.COM.XCOMWrapper.Setup(ISettingsProvider pSettingsProvider)
然而,如果我们从迁移代码中删除这条 sql 语句并按如下方式执行,在调用 context.Database.Migrate()
之前:
var dbConnection = context.Database.GetDbConnection();
dbConnection.Open();
using (var transaction = dbConnection.BeginTransaction())
{
var updateForeignKeyReferences= dbConnection.CreateCommand();
updateForeignKeyReferences.CommandText = "UPDATE A SET ForeignKeyColumn = NULL WHERE ForeignKeyColumn & \"\" = \"\"";
updateForeignKeyReferences.ExecuteNonQuery();
transaction.Commit();
}
dbConnection.Close();
它工作得很好。 我在 up-method 中使用 sql 代码的方法是完全错误的吗?可能的原因是什么?最重要的是,我该如何解决这个问题?第二种方法是我目前解决这个问题的方法,但我担心这意味着在很长一段时间内 运行 我无法使用迁移机制,必须寻求自定义解决方案(或其他框架)。我宁愿坚持使用 ef 核心。
重要: 此应用程序与遗留应用程序一起使用,我们必须在初始启动时通过 sql 代码插入应用程序历史记录。为此,我们创建一个事务并简单地创建历史记录 table 并插入最初创建的 table。这工作得很好,交易和命令都应该关闭。 table A 从未被此函数触及。
使用 migrationBuilder.Sql("UPDATE `A` SET `ForeignKeyColumn` = NULL WHERE `ForeignKeyColumn` = ''")
是正确的步骤。
它应该可以正常执行。
不幸的是,似乎有一个问题,当 CREATE INDEX
语句被执行时(有已为您的新导航 属性 生成并且是 Up()
迁移方法的一部分)。
这只是一个问题,如果两个语句都在同一个事务中执行(默认情况下迁移就是这种情况)。否则,不持有锁并且 CREATE INDEX
语句成功。
解决这个问题的最简单方法是将 migrationBuilder.Sql()
参数 suppressTransaction
设置为 true
。
这将在事务的其余部分之外执行语句,并且不会锁定 table:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"UPDATE `A` SET `ForeignKeyColumn` = NULL WHERE `ForeignKeyColumn` = ''",
suppressTransaction: true);
migrationBuilder.CreateIndex(/* ... */);
migrationBuilder.AddForeignKey(/* ... */);
}
另一种方式,能够在事务内部执行UPDATE
语句,就是在自己的专用迁移中执行命令:
- 添加空迁移。将您的
migrationBuilder.Sql()
调用添加到此迁移的空Up()
方法中。 - 添加实际迁移(包含
CreateIndex()
和AddForeignKey()
操作)。 - 将两个迁移应用到您的数据库。