Check if an existing transactionscope is active
Asked Answered
P

2

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.

Pussy answered 12/12, 2012 at 10:8 Comment(2)
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 #1964434Sufism
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.Pussy
P
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.

Puglia answered 22/3, 2013 at 20:45 Comment(4)
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 insteadBartel
@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.Puglia
Only way I could see it happening is if you called TransactionScope.Complete() then trying to use it for more transactional activityBartel
@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 ?Leatherwood
L
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>));
    }
}
Lillia answered 12/12, 2012 at 10:15 Comment(3)
See this https://mcmap.net/q/436800/-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.Moisten
@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.Puglia
@Puglia "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.Moisten

© 2022 - 2024 — McMap. All rights reserved.