6

Under what circumstances can code wrapped in a System.Transactions.TransactionScope still commit, even though an exception was thrown and the outermost scope never had commit called?

There is a top-level method wrapped in using (var tx = new TransactionScope()), and that calls methods that also use TransactionScope in the same way.

I'm using typed datasets with associated tableadapters. Could it be that the commands in the adapter aren't enlisting for some reason? Do any of you know how one might check whether they are enlisting in the ambient TransactionScope or not?

Neil Barnwell
  • 41,080
  • 29
  • 148
  • 220

5 Answers5

10

The answer turned out to be because I was creating the TransactionScope object after the SqlConnection object.

I moved from this:

using (new ConnectionScope())
using (var transaction = new TransactionScope())
{
    // Do something that modifies data

    transaction.Complete();
}

to this:

using (var transaction = new TransactionScope())
using (new ConnectionScope())
{
    // Do something that modifies data

    transaction.Complete();
}

and now it works!

So the moral of the story is to create your TransactionScope first.

Neil Barnwell
  • 41,080
  • 29
  • 148
  • 220
  • 2
    It also shows the value of posting code ;-p We might have spotted it for you; never mind - sorted now. – Marc Gravell Nov 10 '09 at 16:53
  • 1
    Yeah but I couldn't do that in this case, as the app is too complex, and I hadn't replicated it in a smaller example. – Neil Barnwell Nov 10 '09 at 17:18
  • 4
    I think you can create the SqlConnection first (in case you need to pass it around to methods that all use TransactionScope. In the situation where you have an existing connection already and want to create a transaction scope, you can just call connection.ElistTransaction( Transaction.Current ) inside the using block for the scope. – Triynko May 21 '10 at 17:35
2

The obvious scenario would be where a new (RequiresNew) / null (Suppress) transaction is explicitly specified - but there is also a timeout/unbinding glitch that can cause connections to miss the transaction. See this earlier post (the fix is just a connection-string change), or full details.

Community
  • 1
  • 1
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I tried the connection string change earlier on while researching my problem and it didn't help in this case. I think I might have `RequiresNew` somewhere, so I'll take a look. – Neil Barnwell Nov 10 '09 at 14:04
  • I'm using typed datasets with associated tableadapters. Could it be that the commands in the adapter aren't enlisting for some reason? Do you know how one might check whether they are enlisting in the ambient TransactionScope or not? – Neil Barnwell Nov 10 '09 at 15:03
  • I found the answer in the end - to do with the order that transactionscope and connection objects are created. I've added my answer to this post. – Neil Barnwell Nov 10 '09 at 16:17
0

Be aware how TransactionScope works:
It sets property System.Transactions.Transaction.Current at the begining of using scope and then set it back to previous value at end of using scope.

Previous value depends on where given scope is declared. It can be inside another scope.


You can modify code like this:

using (var sqlConnection = new ConnectionScope())
using (var transaction = new TransactionScope())
{
    sqlConnection.EnlistTransaction(System.Transactions.Transaction.Current);
    // Do something that modifies data
    transaction.Complete();
}

I show this possibility for those who have their code more complicated and cannot simply change code to open DB connection first.

owerkop
  • 281
  • 3
  • 6
0

This example (C#, .NetFramework 4.7.1) shows how we can persist to the database even if the code is wrapped in a TransactionScope. The first insert will be rolled back, and the second insert will be inserted without transaction.

See my related post, where I ask for help in how to detect this.


using (var transactionScope = new TransactionScope())
{
    using (var connection = new SqlConnection("Server=localhost;Database=TestDB;Trusted_Connection=True"))
    {
        connection.Open();

        new SqlCommand($"INSERT INTO TestTable VALUES('This will be rolled back later')", connection).ExecuteNonQuery();

        var someNestedTransaction = connection.BeginTransaction();
        someNestedTransaction.Rollback();

        new SqlCommand($"INSERT INTO TestTable VALUES('This is not in a transaction, and will be committed')", connection).ExecuteNonQuery();
    }

    throw new Exception("Exiting.");

    transactionScope.Complete();
}

stalskal
  • 1,181
  • 1
  • 8
  • 16
-2
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
    {
        try
        {
            using (IDbConnection sqlConnection = GetDbconnection("SQL_CONNECTION_STRING"))
            {
                using (IDbConnection db2Connection = GetDbconnection("DB2_CONNECTION_STRING"))
                {
                    sqlConnection.Open();
                    db2Connection.Open();

                    string db2Query1 = "";  // INSERT QUERY
                    result = await db2Connection.QueryFirstOrDefaultAsync<bool>(db2Query1, new DynamicParameters(), commandType: CommandType.Text);
                    
                    string db2Query2 = "";  // INSERT QUERY
                    result = await db2Connection.QueryFirstOrDefaultAsync<bool>(db2Query2, new DynamicParameters(), commandType: CommandType.Text);

                    string procName = "SP_NAME";
                    DynamicParameters dynamicParams = new DynamicParameters();
                    dynamicParams.Add("Parameter1", "VALUE1", DbType.String);
                    GridReader sqlResult = await sqlConnection.QueryMultipleAsync(procName, dynamicParams, commandType: CommandType.StoredProcedure, commandTimeout: 30);
                    resultSet = await sqlResult.ReadAsync<T>();
                    int sqlExecResult = await sqlConnection.ExecuteAsync(procName, dynamicParams, commandTimeout: 30, commandType: CommandType.StoredProcedure);

                    scope.Complete();  // COMPLETE THE TRANSACTION
                    IsSuccess = true;
                }
            }
        }
        catch (Exception ex)
        {
            errorMessage = ex.Message;
            throw;
        }
    }

It is not working only for DB2 and I could see that once inserted it gets committed immediately.

But SQL Server rollback works fine. Until the call is complete() SQL server waits to commit but DB2 commits immediately.

Please let me know what is the issue in my code

Raseeth
  • 45
  • 1
  • 11
  • 1
    This does not really answer the question. If you have a different question, you can ask it by clicking [Ask Question](https://stackoverflow.com/questions/ask). To get notified when this question gets new answers, you can [follow this question](https://meta.stackexchange.com/q/345661). Once you have enough [reputation](https://stackoverflow.com/help/whats-reputation), you can also [add a bounty](https://stackoverflow.com/help/privileges/set-bounties) to draw more attention to this question. - [From Review](/review/low-quality-posts/34923098) – ChrisGPT was on strike Aug 30 '23 at 18:18