87

I've been having trouble using the TransactionScope to wrap multiple database queries into a transaction, I am using SqlBulkCopy with batchsize 500. When I increased the batch size to 1000 I am getting the error:

The transaction associated with the current connection has completed but has not been disposed. The transaction must be disposed before the connection can be used to execute SQL statements.

This is the code I am using:

using (var scope = new TransactionScope())
{
    using (var connection = (SqlConnection)customerTable.OpenConnection())
    {
        var table1BulkCopy = new SqlBulkCopy(connection)
        {
            BatchSize = BATCH_SIZE,
            DestinationTableName = TableName1
        };

        table1BulkCopy.WriteToServer(table1DataTable);

        var table2BulkCopy = new SqlBulkCopy(connection)
        {
            BatchSize = BATCH_SIZE,
            DestinationTableName = TableName2
        };

        table2BulkCopy.WriteToServer(table2DataTable);

        var table3BulkCopy = new SqlBulkCopy(connection)
        {
            BatchSize = BATCH_SIZE,
            DestinationTableName = TableName3
        };

        table1BulkCopy.WriteToServer(table3DataTable);

        var table4BulkCopy = new SqlBulkCopy(connection)
        {
            BatchSize = BATCH_SIZE,
            DestinationTableName = TableName4
        };

        table4BulkCopy.WriteToServer(table4DataTable);

        scope.Complete();
    }
}
Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
Pradeep
  • 2,639
  • 11
  • 36
  • 45

7 Answers7

157

This can happen when the transaction times out. You can increase the timeout for your transaction like this (use values appropriate for the expected length of your transaction). The code below is for 15 minutes:

using (TransactionScope scope = 
             new TransactionScope(TransactionScopeOption.Required, 
                                   new System.TimeSpan(0, 15, 0)))
  {
      // working code here
  }

This is why it could have worked for batchsize 500 and not for 1000.

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
Anthony Queen
  • 2,348
  • 3
  • 18
  • 21
  • 6
    I'm getting this with `Timeout = TransactionManger.MaximumTimeout`, so this is not the only way this error can occur. – Eric J. Feb 25 '14 at 20:26
  • This answer fixed an issue i was having. as @Eric J mentioned its not the only way this error can occur, but if your transaction times out you do get the rather unrelated message above. – b0redom Mar 13 '14 at 15:04
  • this solved my issue, turned out that the operation was too severe and the transaction was expired before all the save/update operations were done with in the block... – Niklas Nov 26 '18 at 07:16
  • had a similar problem with it, just that i had several nested transaction scopes and so it might not be enough to just put it at one place -> the parent transaction scope needs to be the same or bigger than the child's – CitrusO2 Feb 01 '19 at 11:54
  • if I have a batch with over 300k records and it takes hours to complete the task, then is it required to set large value as TimeSpan? – Marcin Janowski Jun 18 '19 at 13:19
  • 1
    The exception doesn't say that the reason was timing out. I'd like to find out of that's the reason. Is there a way to find out if the reason we get the exception is that the transaction timed out? (I know I could set it to infinity and assume no exception in testing means the problem was timeouts, but I'd like to *have* some timeout in prod and see what actually happens in the logs) – unhammer Apr 23 '21 at 08:23
27

I found that setting the timeout in the TransactionScope didn't work for me. I also needed to add the following config key to the end of the machine.config <configuration> tag to extend past the default max timeout of 10 minutes.

<system.transactions>
    <machineSettings maxTimeout="00:30:00" /> <!-- 30 minutes -->
</system.transactions>

Credit: http://thecodesaysitall.blogspot.com.au/2012/04/long-running-systemtransactions.html

Ravindra Gullapalli
  • 9,049
  • 3
  • 48
  • 70
Adam
  • 676
  • 1
  • 8
  • 13
  • 1
    Note that you *must* put this at the *end* of the config section, otherwise you will get an error from IIS. – Shaul Behr Jun 28 '15 at 17:28
  • If I read the docs on [MaxTimeout](https://learn.microsoft.com/en-us/dotnet/api/system.transactions.configuration.machinesettingssection.maxtimeout?redirectedfrom=MSDN&view=netframework-4.8#System_Transactions_Configuration_MachineSettingsSection_MaxTimeout) and [defaultSettings timeout](https://learn.microsoft.com/en-us/dotnet/api/system.transactions.configuration.defaultsettingssection.timeout?view=netframework-4.8) correctly, an unchanged `machine.config` used with no TransactionOptions gives a timeout of 1 minute (and you're allowed to increase to 10 using TransactionOptions) – unhammer Apr 23 '21 at 08:41
8

Move scope.Complete(); outside the connection block.

using (var scope = new TransactionScope())
{
  using (var connection = (SqlConnection)customerTable.OpenConnection())
   {
    //
   }
  scope.Complete();
}
KV Prajapati
  • 93,659
  • 19
  • 148
  • 186
  • Here more info why? It is very good practice to put the call as the last statement in the using block. [More info about the method](https://msdn.microsoft.com/es-es/library/system.transactions.transactionscope.complete(v=vs.110).aspx) – Angel May 04 '17 at 14:04
2

Pretty obvious problem with time out, but you don't get effect if you set TransactionOptions.Timeout higher. Even if you set TimeSpan.MaxValue you don't actually get a profit. It doesnt matter that the Timeout property of the TransactionOptions are set to a higher value, TransactionOptions.Timeout can not exceed maxTimeout property. You should set up some changes in machine.config.

Shortly you should find machine.config file %windir%\Microsoft.NET\Framework\yourversion\config\machine.config
And add this in <configuration> tag:

<system.transactions>
    <machineSettings maxTimeout="00:30:00"/>
</system.transactions>

Here you can set maxTimeout property to 30 minutes.
See following for more details http://thecodesaysitall.blogspot.com/2012/04/long-running-systemtransactions.html

Vortman
  • 31
  • 2
1

The full answer must be more full.

You must specify - where will be max transaction timeout determined - in the .Net code, or in the server config

<sectionGroup name="system.transactions".... 
    ...allowDefinition="MachineOnly"
</sectionGroup>

In this case you can set max timeout in the machine.config

<configuration>
 <system.transactions>
 <machineSettings maxTimeout="01:00:00" />
 </system.transactions>
</configuration> 

Or may be you want to override this behaivor in the application. Then in the machine.config you should set atribute the velue:

...allowDefinition="MachineToApplication"

This is a good arcticle: https://blogs.msdn.microsoft.com/ajit/2008/06/18/override-the-system-transactions-default-timeout-of-10-minutes-in-the-code/

Alexander Shapkin
  • 1,126
  • 1
  • 13
  • 11
0

Apply this :

TransactionScope(TransactionScopeOption.Required, new System.TimeSpan(0, 30, 0));
(0, 30, 0) = (hour,minute,seconds)

If you want to test, apply the Time Span with this (0, 0, 1), try and you will get the error. After that, increase seconds or minute or hour and try. it will work.

Elikill58
  • 4,050
  • 24
  • 23
  • 45
  • I changed the transaction timeout from the default to 30 minutes, but still suffered the Transaction completed but not disposed error after 15 minutes. – Zarepheth Jul 11 '23 at 20:03
-1

C# 9 Lang version. TransactionScopeOption.Suppress works like magic for me.

// TransactionScopeOption.Suppress works and fixed my issue
using TransactionScope Scope = new TransactionScope(TransactionScopeOption.Suppress);
try
{
    using var CentralConnection = SQLConnection("Other connection string here");
    using var LocalConnection = SQLConnection("Other connection string here");

    // Central
    using var cmd0 = new SqlCommand("OtherSQLCommandTextHere", CentralConnection)
    {
         CommandTimeout = 0 // I just add zero timeout here
    };
    cmd0.ExecuteNonQuery();

    // Local
    using var cmd1 = new SqlCommand("OtherSQLCommandTextHere", LocalConnection)
    {
         CommandTimeout = 0 // I just add zero timeout here
    };
    cmd1.ExecuteNonQuery();

    // The Complete method commits the transaction. If an exception has been thrown,
    // Complete is not called and the transaction is rolled back.
    Scope.Complete();
}
catch (Exception ex)
{
    Scope.Dispose();
    MessageBox.Show(ex.ToString());
}

public SqlConnection SQLConnection(string ISQLConnection)
{
     SqlConnection conn = null;
     try
     {
         conn = new SqlConnection(ISQLConnection);
         conn.Open();            
     }
     catch 
     {
         // skipping here error message
     }
     return conn;
 }
Sourcephy
  • 239
  • 4
  • 4
  • 2
    I hope you are fully aware of what `TransactionScopeOption.Suppress` does, because it is not without consequences. – Philip Borgström Apr 29 '21 at 14:00
  • @PhilipBorgström could you explain? – wodzu Aug 16 '21 at 06:48
  • 1
    "The ambient transaction context is suppressed when creating the scope. All operations within the scope are done without an ambient transaction context." https://social.msdn.microsoft.com/Forums/en-US/781c69f5-637f-4adb-a674-c065ea7ea69b/ambient-transactions?forum=csharplanguage https://www.codeproject.com/Articles/690136/All-About-TransactionScope – Elton Aug 26 '21 at 13:30