环境事务中的 TransactionScope 错误不会回滚事务

TransactionScope error in ambient transaction does not rollback the transaction

我使用这样的环境事务:


using(TransactionScope tran = new TransactionScope()) {
    CallAMethod1();//INSERT
    CallAMethod2();//INSERT
    tran.Complete();
}

方法CallAMethod2();returnsaffected rows =-264 因此它无法插入,但是第一个插入已提交!

我想知道如何使用 ambient transaction 以及如果第二种方法有多个需要内部事务的操作,我应该将这些操作放在内部事务中吗? 像这样:

     DAL_Helper.Begin_Transaction();

              //------Fill newKeysDictioanry

                affectedRow = DBUtilities.InsertEntityWithTrans("table2", newKeysDictioanry, DAL_Helper);

                if (affectedRow == 1)
                {
                    if (!string.IsNullOrEmpty(sp_confirm))
                    {
                        result_dt = UserTransactionDAL.Run_PostConfirm_SP(sp_PostConfirm, OBJ.ValuesKey, DAL_Helper);
                        if (result_dt.Rows.Count > 0 && result_dt.Rows[0][0].ToString() == "0")
                        {
                            DAL_Helper.current_trans.Commit();

                            if (DAL_Helper.connectionState == ConnectionState.Open)
                            {
                                DAL_Helper.Close_Connection();
                            }
                            return 1;// affectedRow;
                        }
                        else
                        {
                            DAL_Helper.current_trans.Rollback();
                            if (DAL_Helper.connectionState == ConnectionState.Open)
                            {
                                DAL_Helper.Close_Connection();
                            }
                            return -2; 
                        }
                    }
//etc

1) 您需要检查是否调用了tran.Complete();。如果调用 tran.Complete();,则认为 TransactionScope 已成功完成。

来自MSDN

When your application completes all work it wants to perform in a transaction, you should call the Complete method only once to inform that transaction manager that it is acceptable to commit the transaction. Failing to call this method aborts the transaction.

调用tran.Complete();是为了通知交易管理器完成交易。实际上,事务管理器不会跟踪您的 Db 适配器,也不知道连接中的操作是成功还是失败。您的应用程序必须通过调用 Complete

来告知它

How does TransactionScope roll back transactions?

要使您的交易失败,只需确保您没有在代码中调用 tran.Complete();

If no exception occurs within the transaction scope (that is, between the initialization of the TransactionScope object and the calling of its Dispose method), then the transaction in which the scope participates is allowed to proceed. If an exception does occur within the transaction scope, the transaction in which it participates will be rolled back.

在你的情况下,如果你认为操作失败,也许你可以在你的 CallAMethod2(); 中抛出一个异常,所以 tran.Complete();not 调用并且事务被回滚。

2) 您可以检查的第二件事是您的连接是否在事务中登记。如果未登记连接,TransactionScope 不会回滚。可能的问题是:

  • 如果连接在进入事务范围之前就存在,则不会登记:Does TransactionScope work with pre-existing connections?
  • 您的基础连接不支持自动登记。

在这些情况下,您可以尝试手动获取连接(从上面的 link 中提取):

connection.EnlistTransaction(Transaction.Current)

关于你的第二个问题:

what if the second method has more than one action which need internal transaction , should i put these actions in internal transaction ?

我想说这实际上取决于您是否将 CallAMethod2(); 视为 自动 操作,这意味着您可以在别处直接调用它而无需将其包装在事务中。大多数情况下,创建内部事务是有意义的,因为事务可以嵌套。对于您的情况,建议在 CallAMethod2(); 中也使用 TransactionScope,我们在创建新事务范围时有一些选项:

The TransactionScope class provides several overloaded constructors that accept an enumeration of the type TransactionScopeOption, which defines the transactional behavior of the scope. A TransactionScope object has three options:

Join the ambient transaction, or create a new one if one does not exist.

Be a new root scope, that is, start a new transaction and have that transaction be the new ambient transaction inside its own scope.

Not take part in a transaction at all. There is no ambient transaction as a result.

选择哪一个取决于您的应用。就您而言,我想您可以选择第一个选项。以下是来自 MSDN

的示例
void RootMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          SomeMethod();
          scope.Complete();
     }
}

void SomeMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          scope.Complete();
     }
}

阅读 Khanh TO 所说的内容。如果您的连接是在外部事务范围之外打开的,则不会征用该连接。

这就是为什么第二次调用失败时第一次调用没有回滚的原因。您将必须获得您的连接:

using (TransactionScope tran = new TransactionScope(TransactionScopeOption.Required))
{
   connection.EnlistTransaction(Transaction.Current);
   CallAMethod1();//INSERT
   CallAMethod2();//INSERT
   tran.Complete();
}

您可以使用内部和外部范围进行事务:

string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
var option = new TransactionOptions
{
     IsolationLevel = IsolationLevel.ReadCommitted,
     Timeout = TimeSpan.FromSeconds(60)
};
using (var scopeOuter = new TransactionScope(TransactionScopeOption.Required, option))
{
    using (var conn = new SqlConnection(connectionString))
    {
        using (SqlCommand cmd = conn.CreateCommand())
        {
            cmd.CommandText="INSERT INTO Data(Code, FirstName)VALUES('A-100','Mr.A')";
            cmd.Connection.Open();
            cmd.ExecuteNonQuery();
        }
    }
    using (var scopeInner = new TransactionScope(TransactionScopeOption.Required, option))
    {
        using (var conn = new SqlConnection(connectionString))
        {
            using (SqlCommand cmd = conn.CreateCommand())
            {
                cmd.CommandText="INSERT INTO Data(Code, FirstName) VALUES('B-100','Mr.B')";
                cmd.Connection.Open();
                cmd.ExecuteNonQuery();
            }
        }
        scopeInner.Complete();
    }
    scopeOuter.Complete();
}