尝试更改迁移中的列数据时出现锁定问题

问题描述

我正在尝试向当前项目的迁移的 up-method 添加 sql 语句。该数据库一个 Ms Access 数据库。在运行时应用迁移。 情况如下: 我有一个基本的 Initial-create 迁移,在我的情况下假设已经应用。由于此应用程序的性质,我们有一个A,其中包含某种外键,但没有定义任何 sql 约束。这意味着外键关系是通过程序代码设计的,而不是在 sql 中意味着外键关系。键是一个字符串,如果没有外来元素,则值为空。 现在我们要添加一个新的迁移,它通过 sql-constraints 强制执行这种关系。这通过标准的 ef-core 迁移代码工作得很好,但是当迁移应用于非空数据库时问题就出现了。 sql 外键需要表 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 代码插入应用程序历史记录。为此,我们创建一个事务并简单地创建历史表并插入最初创建的表。这工作得很好,事务和命令都应该关闭。表A从未被这个函数触及。

解决方法

使用 migrationBuilder.Sql("UPDATE `A` SET `ForeignKeyColumn` = NULL WHERE `ForeignKeyColumn` = ''") 是正确的过程。

它应该执行得很好。

不幸的是,似乎有一个问题,当执行 UPDATE 语句(为您的新导航生成的)时,Jet 仍然持有 CREATE INDEX 命令中使用的表的锁属性并且是 Up() 迁移方法的一部分)。

这只是一个问题,如果两个语句都在同一个事务中执行(默认情况下迁移就是这种情况)。否则,不持有锁且 CREATE INDEX 语句成功。


修复此问题的最简单方法是将 migrationBuilder.Sql() 参数 suppressTransaction 设置为 true

这将在事务的其余部分之外执行该语句,并且不会锁定表:

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() 操作)。
  • 将这两个迁移应用到您的数据库。