Question

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?

Was it helpful?

Solution 2

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.

OTHER TIPS

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();
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top