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)
@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, 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, TaskCompletionSource1 retry, DbConnectionOptions userOptions) at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource
1 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