10

I am using:

public class TransactionUtils
{
    public static TransactionScope CreateTransactionScope()
    {
        var TransactionOptions = new TransactionOptions();
        TransactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
        TransactionOptions.Timeout = TimeSpan.MaxValue;
        return new TransactionScope(TransactionScopeOption.Required, TransactionOptions);
    }
}

to create all my transactions. The problem I am facing is when I nest 2 TransactionUtils.CreateTransactionScope() I get an error: Time-out interval must be less than 2^32-2. Parameter name: dueTm. I am assuming this is because it's trying to attach the child transaction to the parent one and the combined timeouts are to large.

Is there a way to tell if a newly created transaction will be a nested one so I can avoid setting a timeout?

The alternative is to pass a parameter to CreateTransactionScope() so I can tell it that's it's nested and not set the timeout but I would rather find an automatic way of handling it.

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
webnoob
  • 15,747
  • 13
  • 83
  • 165
  • 1
    I think the max value it would pick would be from the machine config, do you really need the `TimeSpan.MaxValue` part ? Maybe this would help http://stackoverflow.com/questions/1963934/error-time-out-interval-must-be-less-than-232-2-parameter-name-duetm – V4Vendetta Dec 12 '12 at 10:19
  • I was thinking about this after I posted and I have changed the timeout to 30 seconds instead as this makes more sense (who would want an SQL transaction running for months? :) ). This "avoids" the issue but still nice to know I can check if a transaction is active. – webnoob Dec 12 '12 at 10:25

2 Answers2

35

There's a pretty simple answer:

System.Transactions.Transaction.Current != null

This works since Framework 2.0.

I'm not sure why the other answer goes to such depths to create IL that links to nonpublic fields.

enorl76
  • 2,562
  • 1
  • 25
  • 38
  • 3
    Having just investigated this (yay ILSpy) Current can throw an exception if you are inside a transaction, AND the TransactionScope is complete. Just so you allow for the fact that you might get an exception instead – Roger Willcocks Feb 18 '15 at 02:23
  • @RogerWillcocks good catch. I hadn't looked at the IL in a while, and I'm not sure if this was added post-2.0 framework or not. And... I'm scratching my head wondering why it would *ever* throw an exception... However, this answer is still better than building dynamic IL to inspect non-public fields, since medium trust environments would choke harder. – enorl76 Feb 19 '15 at 14:25
  • Only way I could see it happening is if you called TransactionScope.Complete() then trying to use it for more transactional activity – Roger Willcocks Feb 24 '15 at 22:26
  • 1
    @RogerWillcocks says `Transaction.Current` *throw an exception if you are inside a transaction, AND the TransactionScope is complete*. Any solution ***without*** `Transaction.Current` **throws an exception** ? – Kiquenet Mar 31 '17 at 11:53
1

Yep it is possible. I have the following code. I didn't make it myself but forgot where I got it. Kudos to the original author, but I do remember I just found it googling for it. I'm using it for .net 4.0, no idea how compatible this is with other versions (it depends on a specific class in a specific assembly).

Using the code below you can check if at some point in your code if you are executing 'inside' a transaction scope.

class TransactionScopeDetector {
    private Func<TransactionScope> _getCurrentScopeDelegate;

    public bool IsInsideTransactionScope {
        get {
            if (_getCurrentScopeDelegate == null) {
                _getCurrentScopeDelegate = CreateGetCurrentScopeDelegate();
            }

            TransactionScope ts = _getCurrentScopeDelegate();
            return ts != null;
        }
    }

    private Func<TransactionScope> CreateGetCurrentScopeDelegate() {
        DynamicMethod getCurrentScopeDM = new DynamicMethod(
          "GetCurrentScope",
          typeof(TransactionScope),
          null,
          this.GetType(),
          true);

        Type t = typeof(Transaction).Assembly.GetType("System.Transactions.ContextData");
        MethodInfo getCurrentContextDataMI = t.GetProperty(
          "CurrentData",
          BindingFlags.NonPublic | BindingFlags.Static)
          .GetGetMethod(true);

        FieldInfo currentScopeFI = t.GetField("CurrentScope", BindingFlags.NonPublic | BindingFlags.Instance);

        ILGenerator gen = getCurrentScopeDM.GetILGenerator();
        gen.Emit(OpCodes.Call, getCurrentContextDataMI);
        gen.Emit(OpCodes.Ldfld, currentScopeFI);
        gen.Emit(OpCodes.Ret);

        return (Func<TransactionScope>)getCurrentScopeDM.CreateDelegate(typeof(Func<TransactionScope>));
    }
}
Maarten
  • 22,527
  • 3
  • 47
  • 68
  • 1
    See this [http://stackoverflow.com/questions/980337/how-to-know-if-the-code-is-inside-transactionscope](http://stackoverflow.com/questions/980337/how-to-know-if-the-code-is-inside-transactionscope). While I don't know if it's the source, a comment worryingly states `CurrentData` has changed to `TLSCurrentData` in 4.5. – Phil Cooper Jan 10 '14 at 11:34
  • @Phil Cooper, How does it "wrongly state that CurrentData has changed"? Um, yes, in Framework 4.5 it HAS changed to TLSCurrentData. There's comments that confirm this in Framework 4.5. – enorl76 Mar 05 '14 at 21:58
  • @enorl76 "worryingly" (...in a way that causes unease or anxiety). I didn't see any facts then to back it up, but at the time would explain why something I was looking at blew up and needed to be changed/fixed. – Phil Cooper Mar 06 '14 at 14:03