无法执行事务操作,因为有待处理的请求在工作

The transaction operation cannot be performed because there are pending requests working

背景

我有一些代码可以打开 sql 连接,开始事务并对数据库执行一些操作。此代码从数据库创建一个对象(出队),获取一些值并将其保存回来。整个操作需要在事务中进行。所有代码在没有事务的情况下都能完美运行。

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    var transaction = connection.BeginTransaction();
    try
    {                       
        var myObject = foo.Dequeue(connection, transaction);

        var url = myObj.GetFilePathUri(connection, transaction);

        //some other code that sets object values

        myObj.SaveMessage(connection, transaction);
        transaction.Commit(); //error here
    }
    catch(Exception ex)
    {                    
        transaction.Rollback();
        //logging                
    }
    finally
    {
        //cleanup code
    }
}

dequeue方法代码

public foo Dequeue(SqlConnection connection, SqlTransaction transaction)
{
    using (var command = new SqlCommand(DEQUEUE_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
    {
        var reader = command.ExecuteReader();
        if (reader.HasRows)
        {
            reader.Read();
            ID = (Guid) reader["ID"];
            Name = reader["Name"].ToString();
            return this;
        }
        return null;
    }
}

获取路径代码

public string GetFilePathUri(SqlConnection connection, SqlTransaction transaction)
{
    using (var command = new SqlCommand(FILEPATH_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
    {
        var reader = command.ExecuteReader();
        if (reader.HasRows)
        {
            reader.Read();
            return reader["Path"].ToString();
        }
        return "";
    }
}

保存代码

public void SaveMessage(SqlConnection connection, SqlTransaction transaction)
{
    using (var command = new SqlCommand(SAVE_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
    {
        command.Parameters.Add("@ID", SqlDbType.UniqueIdentifier).Value = ID;
        command.Parameters.Add("@Name", SqlDbType.VarChar).Value = Name;
        //other object params here
        command.ExecuteNonQuery();
    }
}

问题

调用 transaction.Commit() 时,出现以下错误:

The transaction operation cannot be performed because there are pending requests working on this transaction.

我做错了什么?

编辑:快速编辑说我已经在 SO 上阅读了关于这个问题的其他问题,但找不到任何与 ADO.net

相关的问题

我以前遇到过这个问题,问题是 reader 需要关闭。 试试这个:

public foo Dequeue(SqlConnection connection, SqlTransaction transaction)
{
    using (var command = new SqlCommand(DEQUEUE_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
    {
        var reader = command.ExecuteReader();
        if (reader.HasRows)
        {
            reader.Read();
            ID = (Guid) reader["ID"];
            Name = reader["Name"].ToString();
            reader.Close();//Closing the reader
            return this;
        }
        return null;
    }
}


public string GetFilePathUri(SqlConnection connection, SqlTransaction    transaction)
{
    string filePathUri = "";
    using (var command = new SqlCommand(FILEPATH_SPROC, connection) {CommandType = CommandType.StoredProcedure, Transaction = transaction})
    {
        var reader = command.ExecuteReader();
        if (reader.HasRows)
        {
            reader.Read();
            filePathUri = reader["Path"].ToString();
        }
        reader.Close();//Closing the reader
    }
    return filePathUri;
}

当我忘记在执行数据库调用的异步方法上使用 await 时,我遇到了这个问题 - 在交易 运行 时正在处理连接,因为程序没有等待查询在尝试处理所有内容之前完成。

我们 运行 解决了这个问题,虽然接受的答案可能有效,但这并不是最正确的做法——但是,它确实给了我们一个线索我们做错了什么。

在我们的例子中,有更多数据需要读取。这是问题的简化版本(使用 Dapper):

public Foo GetFooById(int fooId, IDbTransaction transaction = null) {
  var sql = @"
    SELECT * FROM dbo.Foo    WHERE FooID = @FooID;
    SELECT * FROM dbo.FooBar WHERE FooID = @FooID;
    SELECT * FROM dbo.FooBaz WHERE FooID = @FooID;
  ";

  using var data = await _connection.QueryMultipleAsync(sql, new { fooId }, transaction);
  var foo = data.ReadSingle<Foo>();
  foo.Bars = data.Read<Bar>();
  // Oops, didn't read FooBaz.
  return foo;
}

即使 data 被处置,它也会在 t运行 操作上留下一个“待处理的请求”,然后在调用 transaction.Commit() 时爆炸。

解决方案是从 data 中读取 FooBaz 条记录,或者删除未读的 SELECT.

对于以后谁来这里,我遇到了 IQueryableAutoMapper 组合的问题。

我传递了一个 IQueryable 用于映射到 Mapper.Map<>(myData),因为我认为既然它将数据从 class 映射到另一个,它也会实现它。

显然,我错了,在尝试 transaction.Commit() 时抛出了问题中提到的问题。

解决方案

就像在 Mapper.Map<>(myData.ToList()) 中使用 .ToList() 强制实现一样简单。

只是浪费了一个小时才发现,我希望这会为其他人节省一些时间。