【问题标题】:Transaction Scope not rolling back with async/await事务范围不使用 async/await 回滚
【发布时间】:2018-02-21 22:54:46
【问题描述】:

我在使用异步/等待时让我的事务范围回滚时遇到问题。在没有事务范围的情况下一切正常,但是每当我故意导致异常(插入时重复主键以进行第二次迭代)时,不会发生回滚(用于更新)或任何与事务相关的错误。

  • 我还应该注意,除非“OLE DB Services=-4”在连接字符串中,我收到错误: “'Microsoft.ACE.OLEDB.12.0' 提供程序不支持 ITransactionLocal 接口。当前提供程序不支持本地事务。”

下面按钮事件处理程序中的代码只是测试事务范围的示例。主要目标是能够异步更新包含在事务中的循环中的多个表,因此我可以避免 UI 死锁并对循环期间可能发生的任何异常执行回滚。感谢您对我的问题的任何替代或建议,谢谢:)

private async void button1_Click(object sender, EventArgs e)
        {
            try
            {
                int customerCount = 150;  // First 150 rows of customer table
                TransactionScope transaction = null;

                using (OleDbConnection dbConn = new OleDbConnection(Provider = Microsoft.ACE.OLEDB.12.0; OLE DB Services=-4; Data Source = " + filePath))
                {
                    dbConn.Open();
                    using (transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
                    {
                        for (int i = 0; i < customerCount; i++)
                        {

                            // Update field indicating customer made an invoice
                            var taskName = sql.executeAsync("UPDATE Customer SET lastInvoiceDate = @date WHERE customerID = @custID", dbConn,
                                new OleDbParameter("@date", DateTime.Today),
                                new OleDbParameter("@custID", i));

                            // Insert new invoice - Breaks here
                            var taskInsert = sql.executeAsync("INSERT INTO Invoice VALUES (1, 'thisisatestinvoice', '$100.50')", dbConn);


                            await Task.WhenAll(taskName, taskInsert);
                        }
                    }

                    // All updates executed properly
                    transaction.Complete();
                }
            }
            catch (AggregateException exception)
            {
                foreach (Exception ex in exception.InnerExceptions)
                {
                    MessageBox.Show(ex.Message);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

public async Task executeAsync(string dbQuery, OleDbConnection dbConn, params OleDbParameter[] parameters)
        {
            var dbComm = new OleDbCommand(dbQuery, dbConn);

            if (parameters != null)
                dbComm.Parameters.AddRange(parameters);

            await dbComm.ExecuteNonQueryAsync().ConfigureAwait(false);
        }

【问题讨论】:

  • 那么你是如何故意造成异常的呢?对我来说看起来不错,除非我错过了什么:)
  • 在您的示例中,您首先回滚事务(通过处理它),然后调用 Complete。在关闭连接之前完成它。还尝试一个一个地运行 sql 命令(而不是并行运行并等待所有)。还要在使用中包装连接 - 现在如果发生异常,您不会关闭它。
  • 对不起,伙计们,我添加了更多上下文并更新了帖子以显示更多我的意图。基本上发生的事情是事务和查询将在第一次正确完成,但是当我尝试引发异常并启动回滚时(我在第二次迭代中得到一个重复的主键值异常),更新不会滚动返回。

标签: c# transactions async-await ms-access-2010 oledb


【解决方案1】:

我无法让事务范围正常工作,我也不完全确定问题出在哪里,我认为这是因为我使用的是 ACE.OLEDB.12.0,但我找到了 OleDbTransaction 的另一个替代方案如果发生任何故障,将回滚。

private async void button1_Click(object sender, EventArgs e)
    {
        try
        { 
            using (OleDbConnection dbConn = new OleDbConnection(SQLWrapper.CONNECT_STRING))
            {
                dbConn.Open();
                OleDbTransaction dbTrans = dbConn.BeginTransaction();

                var taskName = sql.executeAsync("UPDATE Config SET Busname = @name", dbConn, dbTrans,
                     new OleDbParameter("@name", "name"));

                var taskInsert = sql.executeAsync("INSERT INTO Callout VALUES (16, 'ryanistesting')", dbConn, dbTrans);

                await Task.WhenAll(taskName, taskInsert);
                dbTrans.Commit();
            }
        }
    }

    public async Task executeAsync(string dbQuery, OleDbConnection dbConn, OleDbTransaction dbTrans, params OleDbParameter[] parameters)
    {
        using (var dbComm = new OleDbCommand(dbQuery, dbConn))
        {
            if (parameters != null)
                dbComm.Parameters.AddRange(parameters);

            if (dbTrans != null)
                dbComm.Transaction = dbTrans;

            await dbComm.ExecuteNonQueryAsync().ConfigureAwait(false);
        }
    }

【讨论】:

    【解决方案2】:

    也许你现在已经知道答案了。同样的问题发生在我身上。问题是,当使用 ConfigureAwait(false) 时,您是在告诉运行时从线程池中选择任何空闲线程,而不是等待调用异步方法时使用的前一个线程。不同的线程与不同的上下文一起使用,在相同的事务范围下创建另一个连接,这意味着只有一个连接。然后事务被提升为分布式事务。这是我的经验,我不得不放弃最好使用异步的想法,并通过不使用 configureAwait(false) 或 Task.WhenAll 来等待 previos 线程完成。希望有帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-20
      • 1970-01-01
      相关资源
      最近更新 更多