无法访问SqlTransaction对象以在catch块中回滚

时间:2010-05-26 10:36:52

标签: c# sql transactions ado.net rollback

我遇到了问题,我发现的所有文章或例子似乎都不关心它。

我想在事务中执行一些数据库操作。我想做的与大多​​数例子非常相似:

using (SqlConnection Conn = new SqlConnection(_ConnectionString))
{
    try
    {
        Conn.Open();
        SqlTransaction Trans = Conn.BeginTransaction();

        using (SqlCommand Com = new SqlCommand(ComText, Conn))
        {
            /* DB work */
        }
    }
    catch (Exception Ex)
    {
        Trans.Rollback();
        return -1;
    }
}

但问题是SqlTransaction Transtry块内声明。因此,catch()块中无法访问它。大多数示例只是在Conn.Open()块之前执行Conn.BeginTransaction()try,但我认为这有点冒险,因为两者都会抛出多个异常。

我错了,还是大多数人都忽略了这种风险? 如果发生异常,能够回滚的最佳解决方案是什么?

7 个答案:

答案 0 :(得分:56)

using (var Conn = new SqlConnection(_ConnectionString))
{
    SqlTransaction trans = null;
    try
    {
        Conn.Open();
        trans = Conn.BeginTransaction();

        using (SqlCommand Com = new SqlCommand(ComText, Conn, trans))
        {
            /* DB work */
        }
        trans.Commit();
    }
    catch (Exception Ex)
    {
        if (trans != null) trans.Rollback();
        return -1;
    }
}

或者你可以更清洁,更轻松地使用它:

using (var Conn = new SqlConnection(_ConnectionString))
{
    try
    {
        Conn.Open();
        using (var ts = new System.Transactions.TransactionScope())
        {
            using (SqlCommand Com = new SqlCommand(ComText, Conn))
            {
                /* DB work */
            }
            ts.Complete();
        }
    }
    catch (Exception Ex)
    {     
        return -1;
    }
}

答案 1 :(得分:8)

我不喜欢键入类型并将变量设置为null,所以:

try
{
    using (var conn = new SqlConnection(/* connection string or whatever */))
    {
        conn.Open();

        using (var trans = conn.BeginTransaction())
        {
            try
            {
                using (var cmd = conn.CreateCommand())
                {
                    cmd.Transaction = trans;
                    /* setup command type, text */
                    /* execute command */
                }

                trans.Commit();
            }
            catch (Exception ex)
            {
                trans.Rollback();
                /* log exception and the fact that rollback succeeded */
            }
        }
    }
}
catch (Exception ex)
{
    /* log or whatever */
}

如果你想切换到MySql或其他提供商,你只需修改1行。

答案 2 :(得分:6)

使用此

using (SqlConnection Conn = new SqlConnection(_ConnectionString))
{
    SqlTransaction Trans = null;
    try
    {
        Conn.Open();
        Trans = Conn.BeginTransaction();

        using (SqlCommand Com = new SqlCommand(ComText, Conn))
        {
            /* DB work */
        }
    }
    catch (Exception Ex)
    {
        if (Trans != null)
            Trans.Rollback();
        return -1;
    }
}

顺便说一句 - 你没有在成功处理的情况下提交

答案 3 :(得分:3)

using (SqlConnection Conn = new SqlConnection(_ConnectionString))
{
    try
    {
        Conn.Open();
        SqlTransaction Trans = Conn.BeginTransaction();

        try 
        {
            using (SqlCommand Com = new SqlCommand(ComText, Conn))
            {
                /* DB work */
            }
        }
        catch (Exception TransEx)
        {
            Trans.Rollback();
            return -1;
        }
    }
    catch (Exception Ex)
    {
        return -1;
    }
}

答案 4 :(得分:1)

Microsoft示例,将begin trans置于try / catch see this msdn link之外。我假设BeginTransaction方法应该抛出异常或者开始一个事务但是从不两者(尽管文档没有说这是不可能的)。

但是,您可能更善于使用TransactionScope来管理很多(不那么)繁重的工作:this link

答案 5 :(得分:1)

SqlConnection conn = null;
SqlTransaction trans = null;

try
{
   conn = new SqlConnection(_ConnectionString);
   conn.Open();
   trans = conn.BeginTransaction();
   /*
    * DB WORK
    */
   trans.Commit();
}
catch (Exception ex)
{
   if (trans != null)
   {
      trans.Rollback();
   }
   return -1;
}
finally
{
   if (conn != null)
   {
      conn.Close();
   }
}

答案 6 :(得分:1)

当我在2018年底第一次发现这个问题时,我并不认为当时投票最高的答案可能存在错误,但是事实确实如此。我首先想到的只是简单地评论答案,然后我又想用自己的参考文献来支持我的主张。以及我所做的测试(基于.Net Framework 4.6.1和.Net Core 2.1。)

鉴于OP的约束,应该在连接内声明事务,这使我们可以使用其他答案中已经提到的2种不同的实现:

使用TransactionScope

using (SqlConnection conn = new SqlConnection(conn2))
{
    try
    {
        conn.Open();
        using (TransactionScope ts = new TransactionScope())
        {
            conn.EnlistTransaction(Transaction.Current);
            using (SqlCommand command = new SqlCommand(query, conn))
            {
                command.ExecuteNonQuery();
                //TESTING: throw new System.InvalidOperationException("Something bad happened.");
            }
            ts.Complete();
        }
    }
    catch (Exception)
    {
        throw;
    }
}

使用SqlTransaction

using (SqlConnection conn = new SqlConnection(conn3))
{
    try
    {
        conn.Open();
        using (SqlTransaction ts = conn.BeginTransaction())
        {
            using (SqlCommand command = new SqlCommand(query, conn, ts))
            {
                command.ExecuteNonQuery();
                //TESTING: throw new System.InvalidOperationException("Something bad happened.");
            }
            ts.Commit();
        }
    }
    catch (Exception)
    {
        throw;
    }
}

您应该注意,在SqlConnection中声明TransactionScope时,自动将连接对象加入事务,而必须使用conn.EnlistTransaction(Transaction.Current);

明确地加入该对象。

测试并证明
我已经在SQL Server数据库中准备了一个简单的表:

SELECT * FROM [staging].[TestTable]

Column1
-----------
1

.NET中的更新查询如下:

string query = @"UPDATE staging.TestTable
                    SET Column1 = 2";

在command.ExecuteNonQuery()之后,会引发异常:

command.ExecuteNonQuery();
throw new System.InvalidOperationException("Something bad happened.");

以下是完整的示例供您参考:

string query = @"UPDATE staging.TestTable
                    SET Column1 = 2";

using (SqlConnection conn = new SqlConnection(conn2))
{
    try
    {
        conn.Open();
        using (TransactionScope ts = new TransactionScope())
        {
            conn.EnlistTransaction(Transaction.Current);
            using (SqlCommand command = new SqlCommand(query, conn))
            {
                command.ExecuteNonQuery();
                throw new System.InvalidOperationException("Something bad happened.");
            }
            ts.Complete();
        }
    }
    catch (Exception)
    {
        throw;
    }
}

如果执行测试,则在TransactionScope完成之前将引发异常,并且更新不会应用于表(事务性回滚),并且值保持不变。这是每个人都期望的预期行为。

Column1
-----------
1

如果我们忘记使用conn.EnlistTransaction(Transaction.Current);在交易中加入连接,会发生什么?

重新运行该示例会再次引发异常,并且执行流会立即跳转到catch块。尽管从未ts.Complete();被调用,但表值已更改:

Column1
-----------
2

由于事务作用域是在SqlConnection之后声明的,因此连接不知道该作用域,也不会隐式加入所谓的ambient transaction

对数据库书呆子的更深入分析

要深入研究,如果执行在command.ExecuteNonQuery();之后且在引发异常之前暂停,我们可以在数据库(SQL Server)上查询事务,如下所示:

SELECT tst.session_id, tat.transaction_id, is_local, open_transaction_count, transaction_begin_time, dtc_state, dtc_status
  FROM sys.dm_tran_session_transactions tst
  LEFT JOIN sys.dm_tran_active_transactions tat
  ON tst.transaction_id = tat.transaction_id
  WHERE tst.session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE program_name = 'TransactionScopeTest')

请注意,可以通过连接字符串中的“应用程序名称”属性来设置会话program_name: Application Name=TransactionScopeTest;

当前存在的交易正在展开:

session_id  transaction_id       is_local open_transaction_count transaction_begin_time  dtc_state   dtc_status
----------- -------------------- -------- ---------------------- ----------------------- ----------- -----------
113         6321722              1        1                      2018-11-30 09:09:06.013 0           0

在没有conn.EnlistTransaction(Transaction.Current);的情况下,没有事务绑定到活动连接,因此在事务上下文中不会发生更改:

session_id  transaction_id       is_local open_transaction_count transaction_begin_time  dtc_state   dtc_status
----------- -------------------- -------- ---------------------- ----------------------- ----------- -----------

说明.NET Framework与.NET Core
在使用.NET Core进行测试期间,我遇到了以下异常:

System.NotSupportedException: 'Enlisting in Ambient transactions is not supported.'

seems .NET Core(2.1.0)当前不支持TransactionScope方法,无论是在SqlConnection之前还是之后都初始化了Scope。