TransactionScope error in ambient transaction does not rollback the transaction

前端 未结 3 440
臣服心动
臣服心动 2021-01-25 02:35

I use an ambient transaction like this :


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


        
相关标签:
3条回答
  • 2021-01-25 03:12

    Read what Khanh TO says. If your connection is opened outside the outer transaction scope the connection won't be enlisted.

    That is why the first call didn't rollback when the second failed. You will have to enlist your connection:

    using (TransactionScope tran = new TransactionScope(TransactionScopeOption.Required))
    {
       connection.EnlistTransaction(Transaction.Current);
       CallAMethod1();//INSERT
       CallAMethod2();//INSERT
       tran.Complete();
    }
    
    0 讨论(0)
  • 2021-01-25 03:21

    You can use scope inner and outer for transaction:

    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();
    }
    
    0 讨论(0)
  • 2021-01-25 03:32

    1) You need to check whether the tran.Complete(); is called. If the tran.Complete(); is called, the TransactionScope is considered completed successfully.

    From 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.

    The call to tran.Complete(); is to inform the Transaction Manager to complete the transaction. Actually, the Transaction Manager does not track your Db adapter and does not know if an operation in a connection was successful or failed. Your application has to let it know by calling Complete

    How does TransactionScope roll back transactions?

    To fail your transaction, just ensure that you don't call tran.Complete(); in your code:

    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.

    In your case, maybe you can throw an exception in your CallAMethod2(); if you think the operation has failed so the tran.Complete(); is not called and the transaction is rolled back.

    2) The second thing you can check is whether your connection is enlisted in the transaction. TransactionScope does not rollback if the connection is not enlisted. The possible problems are:

    • If the connection exists before entering the transaction scope, it won't enlist: Does TransactionScope work with pre-existing connections?
    • Your underlying connection does not support auto enlistment.

    In these cases, you can try enlisting your connection manually (extracted from the link above):

    connection.EnlistTransaction(Transaction.Current)
    

    Regarding your second question:

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

    I would say it really depends on whether you consider your CallAMethod2(); as an automic operation which means you can call it elsewhere directly without wrapping it inside a transaction. Most of the cases, it would make sense to create internal transactions as transactions can be nested. In your case, it's recommended to also use TransactionScope in your CallAMethod2();, we have some options when creating a new transaction scope:

    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.

    Which one to choose really depends on your application. In your case, I guess you could go with the first option. Below is an example from 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();
         }
    }
    
    0 讨论(0)
提交回复
热议问题