34

What is the best way to know if the code block is inside TransactionScope?
Is Transaction.Current a realiable way to do it or there are any subtleties?
Is it possible to access internal ContextData.CurrentData.CurrentScope (in System.Transactions) with reflection? If yes, how?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
nightcoder
  • 13,149
  • 16
  • 64
  • 72

3 Answers3

47

Transaction.Current should be reliable; I've just checked, at this works fine with suppressed transactions, too:

Console.WriteLine(Transaction.Current != null); // false
using (TransactionScope tran = new TransactionScope())
{
    Console.WriteLine(Transaction.Current != null); // true
    using (TransactionScope tran2 = new TransactionScope(
          TransactionScopeOption.Suppress))
    {
        Console.WriteLine(Transaction.Current != null); // false
    }
    Console.WriteLine(Transaction.Current != null); // true
}
Console.WriteLine(Transaction.Current != null); // false
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
7

Here is more reliable way (as I said, Transaction.Current can be set manually and it doesn't always mean we are really in TransactionScope). It's also possible to get this information with reflection, but emiting IL works 100 times faster than reflection.

private Func<TransactionScope> _getCurrentScopeDelegate;

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>));
}

[Test]
public void IsInsideTransactionScopeTest()
{
  Assert.IsFalse(IsInsideTransactionScope);
  using (new TransactionScope())
  {
    Assert.IsTrue(IsInsideTransactionScope);
  }
  Assert.IsFalse(IsInsideTransactionScope);
}
nightcoder
  • 13,149
  • 16
  • 64
  • 72
  • 2
    I wonder if you have changed your definition of "reliable" after using this code in production for four years. – Jeremy Rosenberg May 02 '13 at 17:35
  • If Transaction.Current is not reliable, why didn't .Net Devs left it readonly? Have you looked at its implementation? – Akash Kava Jul 27 '13 at 09:12
  • 3
    It appears in .Net 4.5 "CurrentData" has been renamed to "TLSCurrentData" – Chris McKelt Dec 12 '13 at 01:02
  • @JeremyRosenberg I concur. Today is the second time i've encountered voodoo code reflecting on framework internals with magic strings only for them to change and blow ups ensue. – Phil Cooper Jan 10 '14 at 11:05
  • @ChrisMcKelt where did you see this? Is there any reference online to the change? – Phil Cooper Jan 10 '14 at 13:37
  • After we upgraded an app from 4.0 to 4.5 a function stopped working. After debugging it we saw it was reflecting on a string called "CurrentData" - changing this to "TLSCurrentData" ensured the function started working again (this is just an internal dev util tool - I wouldn't recommend this technique for any production code) – Chris McKelt Jan 11 '14 at 06:52
  • Doesn't seem to work once TransactionScopeAsyncFlowOption is Enabled. – Nigel Thorne Jul 29 '15 at 04:54
  • This method returns true in a nested TransactionScope(TransactionScopeOption.Suppress) and it should return false – jpsimard-nyx Dec 02 '15 at 19:27
2

There is updated version using Expressions which do not need System.Transactions reference.

internal static class TransactionScopeHelper
{
    static Func<object?> _getCurrentScopeDelegate = GetTransactionScopeFunc();

    public static bool IsInsideTransactionScope
    {
        get
        {
            var ts = _getCurrentScopeDelegate();
            return ts != null;
        }
    }

    static Func<object?> GetTransactionScopeFunc()
    {
        var assembly = AppDomain.CurrentDomain.GetAssemblies()
            .FirstOrDefault(a => a.GetName().Name == "System.Transactions");

        if (assembly != null)
        {
            var t = assembly.GetType("System.Transactions.ContextData");
            var currentDataProperty = t.GetProperty("TLSCurrentData", BindingFlags.NonPublic | BindingFlags.Static);
            if (currentDataProperty != null)
            {
                var body   = Expression.MakeMemberAccess(null, currentDataProperty);
                var lambda = Expression.Lambda<Func<object?>>(body);
                return lambda.Compile();
            }
        }

        return () => null;
    }
}
Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32