Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Rollback automatic in a "Using" scope with C# SQL Server calls?

When you create 'using' blocks for your SQL Connection, Transaction, and Command, it is well known that the connection, transaction, or command that the using block is associated with is disposed on its own properly after you leave the using block.

If an exception occurs in one of these blocks though, for instance in the command block - Would the transaction be rolled back on its own, or do developers need to do a try catch inside of the command 'using' block, and add a rollback transaction statement in the catch for this try?

like image 796
Chris Avatar asked Aug 22 '13 19:08

Chris


People also ask

Which action will cause an automatic rollback?

Automatic rollback happens when a query fails to execute for any reason.

How does rollback command work?

ROLLBACK is the SQL command that is used for reverting changes performed by a transaction. When a ROLLBACK command is issued it reverts all the changes since last COMMIT or ROLLBACK.

How do I rollback a transaction scope?

If you want to rollback a transaction, you should not call the Complete method within the transaction scope. For example, you can throw an exception within the scope. The transaction in which it participates in will be rolled back.

Is rollback a transaction control statement?

ROLLBACK in SQL is a transactional control language that is used to undo the transactions that have not been saved in the database. The command is only been used to undo changes since the last COMMIT.


2 Answers

The transaction is rolled back automatically as long as you haven't successfully called Commit. So your using blocks can look something like this, and the transaction will be rolled back if an exception is thrown before the Commit.

using (IDbConnection connection = ...)
{
    connection.Open();
    using (IDbTransaction transaction = connection.BeginTransaction())
    {
        using (IDbCommand command = ...)
        {
            command.Connection = connection;
            command.Transaction = transaction;
            ...
        }
        ...
        transaction.Commit();
    }
}
like image 153
Joe Avatar answered Nov 15 '22 18:11

Joe


It's not guaranteed to get disposed. The Dispose(bool) method of the SqlTransaction will in fact roll it back conditionally:

// System.Data.SqlClient.SqlTransaction
protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        SNIHandle target = null;
        RuntimeHelpers.PrepareConstrainedRegions();
        try
        {
            target = SqlInternalConnection.GetBestEffortCleanupTarget(this._connection);
            if (!this.IsZombied && !this.IsYukonPartialZombie)
            {
                this._internalTransaction.Dispose();
            }
        }
        catch (OutOfMemoryException e)
        {
            this._connection.Abort(e);
            throw;
        }
        catch (StackOverflowException e2)
        {
            this._connection.Abort(e2);
            throw;
        }
        catch (ThreadAbortException e3)
        {
            this._connection.Abort(e3);
            SqlInternalConnection.BestEffortCleanup(target);
            throw;
        }
    }
    base.Dispose(disposing);
}

and if you notice, it would only happen if this._internalTransaction.Dispose(); got called. The problem here is that if GetBestEffortCleanupTarget throws an exception it won't get cleaned up.

In your case, as long as an exception isn't thrown as already stated, you will fall into the category of being Zombied and so it will then actually issue a Rollback call in the _internalTransaction.Dispose() call.

Finally, if this is called with false it will most certainly not get disposed.

Now, unless I'm really missing something here I'm a bit appalled at how fragile this code is.

An interesting note is that I think the MSDN documentation is actually wrong because it states, for the Rollback() method:

The transaction can only be rolled back from a pending state (after BeginTransaction has been called, but before Commit is called). The transaction is rolled back in the event it is disposed before Commit or Rollback is called.

like image 36
Mike Perrenoud Avatar answered Nov 15 '22 19:11

Mike Perrenoud