SqlClient: TransactionScope in 2.1 throwing exception only during debugging in VS2017 15.7.0 Preview 6.0

While testing the .net core 2.1 implementation of TransactionScope I ran into the following issue.

We have a library that configures settings for DB Connections and Transaction scope (Data.Core). Other libraries call this library to execute queries using dapper.

When I execute the code without the debugger, everything runs fine. When I set a breakpoint in the unit test AND also in the method where the DB connection is opened. I then get the exception below.

Any ideas?

System.PlatformNotSupportedException
  HResult=0x80131539
  Message=This platform does not support distributed transactions.
  Source=System.Transactions.Local
  StackTrace:
   at System.Transactions.Distributed.DistributedTransactionManager.GetDistributedTransactionFromTransmitterPropagationToken(Byte[] propagationToken)
   at System.Transactions.TransactionInterop.GetDistributedTransactionFromTransmitterPropagationToken(Byte[] propagationToken)
   at System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx)
   at System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx)
   at System.Transactions.EnlistableStates.Promote(InternalTransaction tx)
   at System.Transactions.Transaction.Promote()
   at System.Transactions.TransactionInterop.ConvertToDistributedTransaction(Transaction transaction)
   at System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts)
   at System.Data.SqlClient.SqlInternalConnection.GetTransactionCookie(Transaction transaction, Byte[] whereAbouts)
   at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
   at System.Data.ProviderBase.DbConnectionPool.PrepareConnection(DbConnection owningObject, DbConnectionInternal obj, Transaction transaction)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.Open()
   at Data.Core.DataStore.get_Connection() in D:\TestApps\Repro\Debugger.Issue\Data.Core\DataStore.cs:line 37
   at Data.Core.Tests.ReproTest1.TestMethod1() in D:\TestApps\Repro\Debugger.Issue\Data.Core.Tests\ReproTest1.cs:line 14

This is the test method in Data.Core.Tests:

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Data.Core.Tests
{
    [TestClass]
    public class ReproTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            DataStore ds = new DataStore();
            using (var scope = ds.BeginTransactionScope())
            {
                // Set breakpoint on the following line. 1 of 2 breakpoints.
                var conn = ds.Connection;

                // The following will never be executed in debug

                // Run raw ADO.NET command in the transaction
                var command = conn.CreateCommand();
                command.CommandText = "DELETE FROM dbo.SomeTable";
                command.ExecuteNonQuery();
            }

        }
    }
}

This is DataStore class:

using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Transactions;

namespace Data.Core
{
    using IsolationLevel = System.Transactions.IsolationLevel;

    public class DataStore
    {

        public DataStore()
        {
            this.DatabaseConnectionString = "Data Source=.;Initial Catalog=SomeDB;Integrated Security=True";
        }

        /// <summary>
        /// Gets or sets the database connection string used to create the database connection.
        /// This cannot be null.
        /// </summary>
        private string DatabaseConnectionString { get; set; }

        /// <summary>
        /// Gets the connection. 
        /// </summary>
        public SqlConnection Connection
        {
            get
            {
                // Set breakpoint on the following line. 2 of 2 breakpoints.
                var connection =  new SqlConnection(this.DatabaseConnectionString);
                
                if (connection.State != ConnectionState.Open)
                {
                    // This line will throw exception
                    connection.Open();
                }

                // See if we need to join an existing transaction scope
                if (Transaction.Current != null)
                {
                    ((DbConnection) connection).EnlistTransaction(Transaction.Current);
                }

                return connection;
            }
        }

        /// <inheritdoc />
        public TransactionScope BeginTransactionScope()
        {
            return new TransactionScope(
                TransactionScopeOption.Required, 
                new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted });
        }
    }
}

I have attached a project to reproduce the error. Debugger.Issue.zip

[EDIT] Add C# syntax highlighting by @karelz

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 6
  • Comments: 31 (7 by maintainers)

Most upvoted comments

@David-Engel @DavoudEshtehari I wonder if this can be closed since there is no activity or reports of the same?

I had issues along this line, exact same error at one point. Regardless, I ended up getting what I wanted to work so have a poor implementation shared here: https://github.com/Alchemy86/DapperTransactionRunnerExample might be of help might not.

This looks to be an issue with System.Data, based on this part of the callstack:

at System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts) at System.Data.SqlClient.SqlInternalConnection.GetTransactionCookie(Transaction transaction, Byte[] whereAbouts) at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx) at System.Data.ProviderBase.DbConnectionPool.PrepareConnection(DbConnection owningObject, DbConnectionInternal obj, Transaction transaction) at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource1 retry, DbConnectionOptions userOptions) at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource1 retry) at System.Data.SqlClient.SqlConnection.Open()

System.Data is asking for the “ExportCookie” for the transaction. That can only be obtained after promoting the transaction.

System.Data should be trying to do a Transaction.EnlistPromotableSinglePhase first, before asking for the Export Cookie. But if EnlistPromotableSinglePhase returns “false”, that means that a call to EnlistDurable or another EnlistPromotableSinglePhase has already been made for the transaction. This could lead to System.Data trying to get the Export Cookie.

Adding label for area-System.Data.SqlClient and adding @saurabh500