14

I got following error once in my application.

This SQLTransaction has completed; it is no longer usable

Stack Trace is attached below – It says about Zombie Check and Rollback.

What is the mistake in the code?

Note: This error came only once.

UPDATE

From MSDN - SqlTransaction.Rollback Method

A Rollback generates an InvalidOperationException if the connection is terminated or if the transaction has already been rolled back on the server.

From Zombie check on Transaction - Error

One of the most frequent reasons I have seen this error showing up in various applications is, sharing SqlConnection across our application.

CODE

public int SaveUserLogOnInfo(int empID)
{
        int? sessionID = null;
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            connection.Open();
            SqlTransaction transaction = null;
            try
            {
                transaction = connection.BeginTransaction();
                sessionID = GetSessionIDForAssociate(connection, empID, transaction);

                    //Other Code

                //Commit
                transaction.Commit();
            }
            catch
            {
                //Rollback
                if (transaction != null)
                {
                    transaction.Rollback();
                    transaction.Dispose();
                    transaction = null;
                }

                //Throw exception
                throw;
            }
            finally
            {
                if (transaction != null)
                {
                    transaction.Dispose();
                }
            }
        }

        return Convert.ToInt32(sessionID,CultureInfo.InvariantCulture);

   }

Stack Trace

enter image description here


REFERENCE:

  1. What is zombie transaction?
  2. Zombie check on Transaction - Error
  3. SqlTransaction has completed
  4. http://forums.asp.net/t/1579684.aspx/1
  5. "This SqlTransaction has completed; it is no longer usable."... configuration error?
  6. dotnet.sys-con.com - SqlClient Connection Pooling Exposed
  7. Thread abort leaves zombie transactions and broken SqlConnection

Community
  • 1
  • 1
LCJ
  • 22,196
  • 67
  • 260
  • 418
  • 1
    What is the exception that causes your code to reach the 'catch'? – Maarten Mar 08 '13 at 12:13
  • 1
    In this case you should use a 'using' statement for your transaction. See http://stackoverflow.com/questions/1127830/why-use-a-using-statement-with-a-sqltransaction – Maarten Mar 08 '13 at 12:14
  • 1
    @Maarten to be fair, the OP *does* make sure it gets disposed; but I agree that *not* using `using` makes it over-complex – Marc Gravell Mar 08 '13 at 12:15

5 Answers5

8

You should leave some of the work to compiler, to wrap that in a try/catch/finally for you.

Also, you should expect that Rollback can occasionally throw an exception, if a problem occurs in Commit stage, or if a connection to server breaks. For that reason you should wrap it in a try/catch.

try
{
    transaction.Rollback();
}
catch (Exception ex2)
{
    // This catch block will handle any errors that may have occurred 
    // on the server that would cause the rollback to fail, such as 
    // a closed connection.
    Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
    Console.WriteLine("  Message: {0}", ex2.Message);
}

This is copied exactly from MSDN documentation page for Rollback method.

I see that you're worried that you have a zombie transaction. In case you pasted, it doesn't sound like you have a problem. You're transaction has been completed, and you should no longer have anything to do with it. Remove references to it if you hold them, and forget about it.


From MSDN - SqlTransaction.Rollback Method

A Rollback generates an InvalidOperationException if the connection is terminated or if the transaction has already been rolled back on the server.

Rethrow a new exception to tell user that data may not have been saved, and ask her to refresh and review

LCJ
  • 22,196
  • 67
  • 260
  • 418
Nikola Radosavljević
  • 6,871
  • 32
  • 44
  • There is a critical warning coming in code analysis, if I swallow exception. That means if I don't `throw` the `catch` exception. – LCJ Mar 08 '13 at 12:20
  • Then you have a problem with your analysis policies. I can't believe that you don't allow actual handling of exception? Do you throw all exceptions out to user? – Nikola Radosavljević Mar 08 '13 at 12:21
  • Yes, I need to log the exception and throw it to the front end. I throw my custom exception, usually, in the client application. – LCJ Mar 08 '13 at 12:23
  • 1
    In that case, rethrow a new exception to tell user that data may not have been saved, and ask her to refresh and review. Simply put, this error may happen whatever you do, only question you need to ask is do you and how do you present it to the user. – Nikola Radosavljević Mar 08 '13 at 12:35
7

Note: This error came only once.

then it is very hard to say much; it could be simply that the // Other Code etc simply took to long, and the entire thing got killed. Maybe your connection died, or an admin deliberately killed it because you were blocking.

What is the mistake in the code?

over-complicating it; it can be much simpler:

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    using(var transaction = connection.BeginTransaction())
    {
        try
        {
            sessionID = GetSessionIDForAssociate(connection, empID, transaction);
            //Other Code
            transaction.Commit();
         }
         catch
         {
            transaction.Rollback();
            throw;
         }
    }
}

much less code to get wrong.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 3
    in your case you could save more lines, if you remove the try catch.. with using the "using" statement a transaction is automatically roll backed if an exception happenes.. see http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx – nWorx Mar 08 '13 at 12:24
  • Thanks. But I don't understand why the suggested code will resolve the problem. Is it because you are not calling `Dispose()` ? – LCJ Mar 08 '13 at 12:30
  • 4
    @Lijo since you don't have a reproducible example, it is impossible to talk much about "resolve the problem". My advice here is simply: you're making it very easy to introduce subtle bugs - which is risky, unnecessary, *and more work*. The unnecessary code that *never gets written* doesn't have any bugs in it. – Marc Gravell Mar 08 '13 at 12:39
  • 2
    I liked the point - `code that never gets written doesn't have any bugs in it` :-) – LCJ Mar 08 '13 at 12:41
  • 1
    @nWorx Just to be clear to anyone else reading this, an unhandled exception inside using(var tran = connection.BeginTransaction()) will cause tran to be rolled back when it is disposed at the end of the using block (note though that according to the [MSDN](https://msdn.microsoft.com/en-us/library/bf2cw321.aspx) docs this behaviour is specific to the DbTransaction.Dispose implementation). An exception itself does not cause tran to roll back. – Rhys Jones Aug 09 '17 at 12:51
2

I use code below can reproduce this error, I use 1000 tasks to execute Sql, after about 300 tasks Successfully Completed, lots of exception about timeout error start to occur on ExecuteNonQuery(),

then next error This SqlTransaction has completed will occur on transaction.RollBack(); and its call stack also contains ZombieCheck().

(If single program with 1000 tasks pressure not enough, you can execute multiple compiled exe file at the same time, or even use multi computers execute to one DataBase.)

So I guess one of the reason cause this error can be something wrong in Connection, then cause the transaction error happens as well.

Task[] tasks = new Task[1000];
for (int i = 0; i < 1000; i++)
{
    int j = i;
    tasks[i] = new Task(() =>
         ExecuteSqlTransaction("YourConnectionString", j)
        );
}

foreach (Task task in tasks)
{
    task.Start();
}       

/////////////    

public void ExecuteSqlTransaction(string connectionString, int exeSqlCou)
{

    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();

        SqlCommand command = connection.CreateCommand();
        SqlTransaction transaction;

        // Start a local transaction.
        transaction = connection.BeginTransaction();

        // Must assign both transaction object and connection
        // to Command object for a pending local transaction
        command.Connection = connection;
        command.Transaction = transaction;

        try
        {
            command.CommandText =
                "select * from Employee";
            command.ExecuteNonQuery();

            // Attempt to commit the transaction.
            transaction.Commit();

            Console.WriteLine("Execute Sql to database."
                + exeSqlCou);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
            Console.WriteLine("  Message: {0}", ex.Message);


            // Attempt to roll back the transaction.
            try
            {
                transaction.Rollback();
            }
            catch (Exception ex2)
            {
                // This catch block will handle any errors that may have occurred
                // on the server that would cause the rollback to fail, such as
                // a closed connection.
                Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
                Console.WriteLine("  Message: {0}", ex2.Message);

            }
        }
    }
}

Besides I find if I commit twice sequentailly will invoke this exception as well.

       transaction.Commit();
       transaction.Commit();

Or if Connection Closed before commit also invoke this error.

       connection.Close();
       transaction.Commit();

Update:

I find it strange that I create another new table and insert 500 thousand data to it,

then use 100000 tasks with select * from newtable sql, running 5 programs at the same time, this time the Timeout Error occur, but when transaction.Rollback() it didn't invoke the SQLTransaction has completed error.

but if the Timeout Error occur, jump into the catch block, and in the catch block do transaction.Commit() again, the SQLTransaction has completed error will happen.

yu yang Jian
  • 6,680
  • 7
  • 55
  • 80
1

I have experienced this error once and i was stuck and unable to know what is going wrong. Actually i was deleting a record and in the Stored procedure i was not deleting its child and specially the delete statement in Stored Procedure was inside the Transaction boundary. I removed that transaction code from stored procedure and got rid of getting this Error of “This SqlTransaction has completed; it is no longer usable.”

ARC
  • 1,061
  • 14
  • 33
0

This message is simply because that you wrote code that throws an exception after the transaction has been already committed successfully.Please try to check the code you wrote after the Commit method or you can handle it by using Try..Catch and finally Blocks :) .

Ahmed Elbatt
  • 1,028
  • 1
  • 14
  • 20