How to know if the code is inside TransactionScope?
Asked Answered
D

3

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?

Dissimilarity answered 11/6, 2009 at 10:14 Comment(0)
D
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);
}
Dissimilarity answered 3/7, 2009 at 15:35 Comment(8)
I wonder if you have changed your definition of "reliable" after using this code in production for four years.Darryldarryn
If Transaction.Current is not reliable, why didn't .Net Devs left it readonly? Have you looked at its implementation?Apothem
It appears in .Net 4.5 "CurrentData" has been renamed to "TLSCurrentData"Authorization
@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.Clement
@ChrisMcKelt where did you see this? Is there any reference online to the change?Clement
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)Authorization
Doesn't seem to work once TransactionScopeAsyncFlowOption is Enabled.Magnetohydrodynamics
This method returns true in a nested TransactionScope(TransactionScopeOption.Suppress) and it should return falseApc
Y
48

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
Yacht answered 11/6, 2009 at 10:20 Comment(2)
I mean that Transaction.Current property can be set even if we are not in TransactionScope.Dissimilarity
If the TransactionScope is Completed, but still not disposed, Syste.Transactions.Transaction.Curre throws an exceptionUninstructed
D
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);
}
Dissimilarity answered 3/7, 2009 at 15:35 Comment(8)
I wonder if you have changed your definition of "reliable" after using this code in production for four years.Darryldarryn
If Transaction.Current is not reliable, why didn't .Net Devs left it readonly? Have you looked at its implementation?Apothem
It appears in .Net 4.5 "CurrentData" has been renamed to "TLSCurrentData"Authorization
@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.Clement
@ChrisMcKelt where did you see this? Is there any reference online to the change?Clement
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)Authorization
Doesn't seem to work once TransactionScopeAsyncFlowOption is Enabled.Magnetohydrodynamics
This method returns true in a nested TransactionScope(TransactionScopeOption.Suppress) and it should return falseApc
S
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;
    }
}
Spitball answered 9/9, 2020 at 14:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.