15

As far as I understand, the "correct" way to use a TransactionScope is to always call transactionScope.Complete(); before exiting the using block. Like this:

using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted }))
{
    //...
    //I'm using this as a NOLOCK-alternative in Linq2sql.
    transactionScope.Complete();
}

However, I've seen that the code works without it, and even the answer I've learnt to use it from omits it. So my question is, must it be used or not?

ispiro
  • 26,556
  • 38
  • 136
  • 291
  • Complete basically sets a flag that says "I'm ok", that's about it. If you don't set it, and no code else sets it (don't forget this kind of transaction may be distributed), then the transaction will stay in doubt until the transaction manager decide what the outcome will eventually be. – Simon Mourier Dec 10 '18 at 08:56
  • TransactionScope was based originally on Microsoft Transaction Server (MTS). It became part of COM "services" (aka "Components Services" today). The root COM interface of this is IObjectContext. IObjectContext lies on two internal/invisible flags "commit" and "complete" for a "context/transaction/activity" and has methods to set/unset these two flags. calling transactionScope.Complete is the equivalent of calling IObjectContext.SetComplete(). – Simon Mourier Dec 10 '18 at 09:11

3 Answers3

20

So my question is, must it be used or not?

Complete must be used when updates are made in order to COMMIT the transaction. Otherwise, the transaction manger will issue a ROLLBACK and undo changes made.

With a read-only transaction like your example, I can think of no material difference with or without Complete. The COMMIT or ROLLBACK issued by the transaction manager will have the same net effect of releasing locks and resources held by the transaction in both cases.

Although it's not a requirement to invoke Complete in a read-only transaction, it's still a best practice IMHO. Consider the poor developer who later unwittingly adds data modification code to your transaction block without seeing Complete is missing.

Dan Guzman
  • 43,250
  • 3
  • 46
  • 71
  • With a read-only transaction (only select query), if we do not call the `Complete` method, Do we receive the `TransactionInDoubtException` exception ? – Kalpesh Rajai Oct 09 '19 at 05:36
  • 1
    @KalpeshRajai, I ran a quick test and did not get a `TransactionInDoubtException` exception but did not try with a distributed transaction. – Dan Guzman Oct 09 '19 at 12:07
4

Yes you need to use it, that is the way to inform the compiler that you completed your tasks sucessfully, from Microsoft docs on the TransactionScope class:

When your application completes all work it wants to perform in a transaction, you should call the Complete method only once to inform that transaction manager that it is acceptable to commit the transaction. Failing to call this method aborts the transaction.

Also for the Complete method:

Failing to call this method aborts the transaction, because the transaction manager interprets this as a system failure, or exceptions thrown within the scope of transaction. However, you should also note that calling this method does not guarantee a commit of the transaction. It is merely a way of informing the transaction manager of your status.

Diego Osornio
  • 835
  • 1
  • 10
  • 20
  • `Failing to call this method aborts the transaction.` But it doesn't. (A simple `db.TheTable.Count();`. But still...) – ispiro Dec 06 '18 at 19:06
  • before you exit the using block all the changes you made "exists" on the db, even tough they are not persisted yet, try the same inside the using block and then outside the using without the complete and you should be able to see the difference. – Diego Osornio Dec 06 '18 at 19:09
  • There's no way to check since I'm not changing the database, just querying it. – ispiro Dec 06 '18 at 19:11
  • 1
    Usually the transaction scope is used yo chain several transactions and to complete them all or none, so if you are just querying the db without any modification, then why do you need a ts?, can you create a simple example ? – Diego Osornio Dec 06 '18 at 19:15
  • See the comment in the code in the question, and the link in the question. I need it as a NOLOCK alternative that will work with Linq2sql. – ispiro Dec 06 '18 at 19:16
1

basically using statement is converted to this at compile time by the C# compiler

       TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted })
        try
        {
           //your works
        }
        finally
        {
            if (transactionScope != null)
                ((IDisposable)transactionScope).Dispose();
        }

So this is all you should expect from C# .... you just need to do your work with the TransactionScope

If the TransactionScope object created the transaction initially, the actual work of committing the transaction by the transaction manager occurs after the last line of code in the using block. If it did not create the transaction, the commit occurs whenever Commit is called by the owner of the Transaction object. At that point the transaction manager calls the resource managers and informs them to either commit or rollback, based on whether the Complete method was called on the TransactionScope object.

calling this method does not guarantee that the transaction wil be committed. It is merely a way of informing the transaction manager of your status. After calling the Complete method, you can no longer access the ambient transaction by using the Current property, and attempting to do so will result in an exception being thrown.

The using statement ensures that the Dispose method of the TransactionScope object is called even if an exception occurs. The Dispose method marks the end of the transaction scope. Exceptions that occur after calling this method may not affect the transaction. This method also restores the ambient transaction to it previous state.

A TransactionAbortedException is thrown if the scope creates the transaction, and the transaction is aborted. A TransactionInDoubtException is thrown if the transaction manager cannot reach a Commit decision. No exception is thrown if the transaction is committed.

hope this will clear for you

H-a-Neo
  • 152
  • 7