【问题标题】:TransactionScope error in ambient transaction does not rollback the transaction环境事务中的 TransactionScope 错误不会回滚事务
【发布时间】:2015-03-27 07:10:06
【问题描述】:

我使用这样的环境事务:


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

方法CallAMethod2(); 返回affected 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

【问题讨论】:

  • 你知道数据库是不是用事务创建的吗? Informix (Dynamic Server) 支持不带事务的未记录数据库,以及带事务的记录、缓冲记录和 MODE ANSI。错误 -264 无法写入临时文件;我不知道这是否表明运行数据库服务器的机器有问题。
  • @JonathanLeffler:不知道,为什么第二次插入操作无故没有成功时事务没有回滚
  • 使用 (varambientTxn = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)){...}

标签: c# asp.net transactions informix transactionscope


【解决方案1】:

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

来自MSDN

当您的应用程序完成它想要在一个 交易,你应该只调用一次 Complete 方法来通知 该事务管理器可以接受提交 交易。未能调用此方法会中止事务。

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

来告知它

How does TransactionScope roll back transactions?

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

如果事务范围内没有发生异常(即在 TransactionScope 对象的初始化和调用 它的 Dispose 方法),然后是作用域所在的事务 允许继续进行。如果异常确实发生在 事务范围,其参与的事务将 被回滚。

在您的情况下,如果您认为操作失败,也许您可​​以在 CallAMethod2(); 中抛出异常,因此 不 调用 tran.Complete(); 并回滚事务。

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

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

connection.EnlistTransaction(Transaction.Current)

关于你的第二个问题:

如果第二种方法有多个需要内部的操作怎么办? 事务,我应该把这些动作放在内部事务中吗?

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

TransactionScope 类提供了几个重载的构造函数 接受 TransactionScopeOption 类型的枚举,其中 定义范围的事务行为。事务范围 对象有三个选项:

加入环境事务,如果不存在则创建一个新事务。

成为一个新的根作用域,即开始一个新事务并让该事务成为它自己作用域内的新环境事务。

根本不参与交易。结果没有环境事务。

选择哪一个取决于您的应用程序。在你的情况下,我想你可以选择第一个选项。以下是来自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();
     }
}

【讨论】:

  • 您的意思是:我不必处理事务内部的异常,以便在出现异常时回滚?
  • @just_name:我没有说你不处理 CallAMethod2(); 中的异常。异常处理因情况而异。我刚刚说过,在你的情况下,如果异常冒泡,它将跳过tran.Complete();,但我不确定这对你的情况是否有意义,因为我不知道你的应用程序逻辑。关键是如果你想回滚你的事务,你需要确保tran.Complete();没有被调用。
  • 那么如果第二种方法发生错误,如何确保tran.Complete();不被调用?
  • @just_name:有很多方法可以做。例如:你可以检查返回值,你可以抛出一个异常,....这真的取决于你的应用程序。
【解决方案2】:

您可以使用范围内和外的事务:

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();
}

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-04
      • 1970-01-01
      • 1970-01-01
      • 2014-05-30
      • 2011-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多