diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index efc9d50ac1..bb77ffba3e 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -620,15 +620,15 @@
Microsoft\Data\SqlClient\SqlSymmetricKeyCache.cs
+
+ Microsoft\Data\SqlClient\SqlTransaction.cs
+
Microsoft\Data\SqlClient\SqlUdtInfo.cs
Microsoft\Data\SqlClient\SqlUtil.cs
-
- Microsoft\Data\SqlClient\SqlTransaction.Common.cs
-
Microsoft\Data\SqlClient\SSPI\NegotiateSSPIContextProvider.cs
@@ -724,7 +724,6 @@
-
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs
deleted file mode 100644
index aef40c173b..0000000000
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs
+++ /dev/null
@@ -1,304 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.ComponentModel;
-using System.Data.Common;
-using Microsoft.Data.Common;
-using Microsoft.Data.SqlClient.Diagnostics;
-
-namespace Microsoft.Data.SqlClient
-{
- ///
- public sealed partial class SqlTransaction : DbTransaction
- {
- private static readonly SqlDiagnosticListener s_diagnosticListener = new();
-
- ////////////////////////////////////////////////////////////////////////////////////////
- // PUBLIC METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
-
- ///
- public override void Commit()
- {
- using (DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionCommitScope(_isolationLevel, _connection, InternalTransaction))
- {
- ZombieCheck();
-
- using (TryEventScope.Create("SqlTransaction.Commit | API | Object Id {0}", ObjectID))
- {
- SqlStatistics statistics = null;
- TdsParser bestEffortCleanupTarget = null;
-
- SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlTransaction.Commit | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId);
-#if NETFRAMEWORK
- RuntimeHelpers.PrepareConstrainedRegions();
-#endif
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- statistics = SqlStatistics.StartTimer(Statistics);
-
- _isFromAPI = true;
-
- _internalTransaction.Commit();
- }
- catch (System.OutOfMemoryException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
-#if NETFRAMEWORK
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
-#endif
- throw;
- }
- catch (SqlException ex)
- {
- diagnosticScope.SetException(ex);
- // GitHub Issue #130 - When a timeout exception has occurred on transaction completion request,
- // this connection may not be in reusable state.
- // We will abort this connection and make sure it does not go back to the pool.
- if (ex.InnerException is Win32Exception innerException && innerException.NativeErrorCode == TdsEnums.SNI_WAIT_TIMEOUT)
- {
- _connection.Abort(ex);
- }
- throw;
- }
- catch (Exception ex)
- {
- diagnosticScope.SetException(ex);
- throw;
- }
- finally
- {
- SqlStatistics.StopTimer(statistics);
- _isFromAPI = false;
- }
- }
- }
- }
-
- ///
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- TdsParser bestEffortCleanupTarget = null;
-#if NETFRAMEWORK
- RuntimeHelpers.PrepareConstrainedRegions();
-#endif
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- if (!IsZombied && !Is2005PartialZombie)
- {
- _internalTransaction.Dispose();
- }
- }
- catch (System.OutOfMemoryException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- _connection.Abort(e);
-#if NETFRAMEWORK
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
-#endif
- throw;
- }
- }
- base.Dispose(disposing);
- }
-
- ///
- public override void Rollback()
- {
- using (DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope(_isolationLevel, _connection, InternalTransaction, null))
- {
- if (Is2005PartialZombie)
- {
- // Put something in the trace in case a customer has an issue
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlTransaction.Rollback | ADV | Object Id {0}, partial zombie no rollback required", ObjectID);
- _internalTransaction = null; // 2005 zombification
- }
- else
- {
- ZombieCheck();
-
- SqlStatistics statistics = null;
- using (TryEventScope.Create("SqlTransaction.Rollback | API | Object Id {0}", ObjectID))
- {
- SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlTransaction.Rollback | API | Correlation | Object Id {0}, ActivityID {1}, Client Connection Id {2}", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId);
-
- TdsParser bestEffortCleanupTarget = null;
-#if NETFRAMEWORK
- RuntimeHelpers.PrepareConstrainedRegions();
-#endif
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- statistics = SqlStatistics.StartTimer(Statistics);
-
- _isFromAPI = true;
-
- _internalTransaction.Rollback();
- }
- catch (System.OutOfMemoryException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
-#if NETFRAMEWORK
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
-#endif
- throw;
- }
- catch (Exception ex)
- {
- diagnosticScope.SetException(ex);
- throw;
- }
- finally
- {
- SqlStatistics.StopTimer(statistics);
- _isFromAPI = false;
- }
- }
- }
- }
- }
-
- ///
- public override void Rollback(string transactionName)
- {
- using (DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope(_isolationLevel, _connection, InternalTransaction, transactionName))
- {
- ZombieCheck();
-
- using (TryEventScope.Create(SqlClientEventSource.Log.TryScopeEnterEvent("SqlTransaction.Rollback | API | Object Id {0}, Transaction Name='{1}', ActivityID {2}, Client Connection Id {3}", ObjectID, transactionName, ActivityCorrelator.Current, Connection?.ClientConnectionId)))
- {
- SqlStatistics statistics = null;
- TdsParser bestEffortCleanupTarget = null;
-#if NETFRAMEWORK
- RuntimeHelpers.PrepareConstrainedRegions();
-#endif
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- statistics = SqlStatistics.StartTimer(Statistics);
-
- _isFromAPI = true;
-
- _internalTransaction.Rollback(transactionName);
- }
- catch (System.OutOfMemoryException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- diagnosticScope.SetException(e);
- _connection.Abort(e);
-#if NETFRAMEWORK
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
-#endif
- throw;
- }
- catch (Exception ex)
- {
- diagnosticScope.SetException(ex);
- throw;
- }
- finally
- {
- SqlStatistics.StopTimer(statistics);
- _isFromAPI = false;
- }
- }
- }
- }
-
- ///
- public override void Save(string savePointName)
- {
- ZombieCheck();
-
- SqlStatistics statistics = null;
- using (TryEventScope.Create("SqlTransaction.Save | API | Object Id {0} | Save Point Name '{1}'", ObjectID, savePointName))
- {
- TdsParser bestEffortCleanupTarget = null;
-#if NETFRAMEWORK
- RuntimeHelpers.PrepareConstrainedRegions();
-#endif
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- statistics = SqlStatistics.StartTimer(Statistics);
-
- _internalTransaction.Save(savePointName);
- }
- catch (System.OutOfMemoryException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- _connection.Abort(e);
-#if NETFRAMEWORK
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
-#endif
- throw;
- }
- finally
- {
- SqlStatistics.StopTimer(statistics);
- }
- }
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index e1ddaa6926..05d4626119 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -550,9 +550,6 @@
Microsoft\Data\SqlClient\Server\SqlRecordBuffer.cs
-
- Microsoft\Data\SqlClient\SqlTransaction.Common.cs
-
Microsoft\Data\SqlClient\Server\ValueUtilsSmi.cs
@@ -793,6 +790,9 @@
Microsoft\Data\SqlClient\SqlSymmetricKeyCache.cs
+
+ Microsoft\Data\SqlClient\SqlTransaction.cs
+
Microsoft\Data\SqlClient\SqlUdtInfo.cs
@@ -890,7 +890,6 @@
-
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs
deleted file mode 100644
index b08f109e65..0000000000
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs
+++ /dev/null
@@ -1,257 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.ComponentModel;
-using System.Data.Common;
-using System.Runtime.CompilerServices;
-using Microsoft.Data.Common;
-
-namespace Microsoft.Data.SqlClient
-{
- ///
- public sealed partial class SqlTransaction : DbTransaction
- {
- ////////////////////////////////////////////////////////////////////////////////////////
- // PUBLIC METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
-
- ///
- public override void Commit()
- {
- SqlConnection.ExecutePermission.Demand(); // MDAC 81476
-
- ZombieCheck();
-
- using (TryEventScope.Create(" {0}", ObjectID))
- {
- SqlStatistics statistics = null;
- TdsParser bestEffortCleanupTarget = null;
-
- SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current);
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- statistics = SqlStatistics.StartTimer(Statistics);
-
- _isFromAPI = true;
-
- _internalTransaction.Commit();
- }
- catch (System.OutOfMemoryException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- _connection.Abort(e);
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
- throw;
- }
- catch (SqlException e)
- {
- // GitHub Issue #130 - When a timeout exception has occurred on transaction completion request,
- // this connection may not be in reusable state.
- // We will abort this connection and make sure it does not go back to the pool.
- if (e.InnerException is Win32Exception innerException && innerException.NativeErrorCode == TdsEnums.SNI_WAIT_TIMEOUT)
- {
- _connection.Abort(e);
- }
- throw;
- }
- finally
- {
- _isFromAPI = false;
-
- SqlStatistics.StopTimer(statistics);
- }
- }
- }
-
- ///
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- TdsParser bestEffortCleanupTarget = null;
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- if (!IsZombied && !Is2005PartialZombie)
- {
- _internalTransaction.Dispose();
- }
- }
- catch (System.OutOfMemoryException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- _connection.Abort(e);
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
- throw;
- }
- }
- base.Dispose(disposing);
- }
-
- ///
- public override void Rollback()
- {
- if (Is2005PartialZombie)
- {
- // Put something in the trace in case a customer has an issue
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} partial zombie no rollback required", ObjectID);
-
- _internalTransaction = null; // 2005 zombification
- }
- else
- {
- ZombieCheck();
-
- SqlStatistics statistics = null;
- using (TryEventScope.Create(" {0}", ObjectID))
- {
- SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current);
-
- TdsParser bestEffortCleanupTarget = null;
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- statistics = SqlStatistics.StartTimer(Statistics);
-
- _isFromAPI = true;
-
- _internalTransaction.Rollback();
- }
- catch (System.OutOfMemoryException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- _connection.Abort(e);
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
- throw;
- }
- finally
- {
- _isFromAPI = false;
-
- SqlStatistics.StopTimer(statistics);
- }
- }
- }
- }
-
- ///
- public void Rollback(string transactionName)
- {
- SqlConnection.ExecutePermission.Demand(); // MDAC 81476
-
- ZombieCheck();
-
- using (TryEventScope.Create(" {0} transactionName='{1}'", ObjectID, transactionName))
- {
- SqlStatistics statistics = null;
- TdsParser bestEffortCleanupTarget = null;
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- statistics = SqlStatistics.StartTimer(Statistics);
-
- _isFromAPI = true;
-
- _internalTransaction.Rollback(transactionName);
- }
- catch (System.OutOfMemoryException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- _connection.Abort(e);
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
- throw;
- }
- finally
- {
- _isFromAPI = false;
-
- SqlStatistics.StopTimer(statistics);
- }
- }
- }
-
- ///
- public void Save(string savePointName)
- {
- SqlConnection.ExecutePermission.Demand(); // MDAC 81476
-
- ZombieCheck();
-
- SqlStatistics statistics = null;
- using (TryEventScope.Create(" {0} savePointName='{1}'", ObjectID, savePointName))
- {
- TdsParser bestEffortCleanupTarget = null;
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
- statistics = SqlStatistics.StartTimer(Statistics);
-
- _internalTransaction.Save(savePointName);
- }
- catch (System.OutOfMemoryException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.StackOverflowException e)
- {
- _connection.Abort(e);
- throw;
- }
- catch (System.Threading.ThreadAbortException e)
- {
- _connection.Abort(e);
- SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
- throw;
- }
- finally
- {
- SqlStatistics.StopTimer(statistics);
- }
- }
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs
index d16088849e..791201951d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs
@@ -12,6 +12,12 @@ public class SqlConnection
{
internal Guid ClientConnectionId { get; set; }
+ #if NETFRAMEWORK
+ internal static System.Security.CodeAccessPermission ExecutePermission { get; set; }
+ #endif
+
internal SqlStatistics Statistics { get; set; }
+
+ internal void Abort(Exception e) { }
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs
index 31884f4b96..ad89176fe6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs
@@ -52,7 +52,7 @@ internal SqlInternalTransaction(SqlInternalConnection innerConnection, Transacti
internal SqlInternalTransaction(SqlInternalConnection innerConnection, TransactionType type, SqlTransaction outerTransaction, long transactionId)
{
- SqlClientEventSource.Log.TryPoolerTraceEvent("SqlInternalTransaction.ctor | RES | CPOOL | Object Id {0}, Created for connection {1}, outer transaction {2}, Type {3}", ObjectID, innerConnection.ObjectID, outerTransaction?.ObjectID, (int)type);
+ SqlClientEventSource.Log.TryPoolerTraceEvent("SqlInternalTransaction.ctor | RES | CPOOL | Object Id {0}, Created for connection {1}, outer transaction {2}, Type {3}", ObjectID, innerConnection.ObjectID, outerTransaction?.ObjectId, (int)type);
_innerConnection = innerConnection;
_transactionType = type;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs
deleted file mode 100644
index e099712fc2..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Data;
-using System.Data.Common;
-using System.Diagnostics;
-using Microsoft.Data.Common;
-
-namespace Microsoft.Data.SqlClient
-{
- ///
- public sealed partial class SqlTransaction : DbTransaction
- {
- private static int s_objectTypeCount; // EventSource Counter
- internal readonly int _objectID = System.Threading.Interlocked.Increment(ref s_objectTypeCount);
- internal readonly IsolationLevel _isolationLevel = IsolationLevel.ReadCommitted;
-
- private SqlInternalTransaction _internalTransaction;
- private readonly SqlConnection _connection;
-
- private bool _isFromAPI;
-
- internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con,
- IsolationLevel iso, SqlInternalTransaction internalTransaction)
- {
-#if NETFRAMEWORK
- SqlConnection.VerifyExecutePermission();
-#endif
- _isolationLevel = iso;
- _connection = con;
-
- if (internalTransaction == null)
- {
- _internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this);
- }
- else
- {
- Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!");
- _internalTransaction = internalTransaction;
- _internalTransaction.InitParent(this);
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////
- // PROPERTIES
- ////////////////////////////////////////////////////////////////////////////////////////
-
- ///
- public new SqlConnection Connection
- {// MDAC 66655
- get
- {
- if (IsZombied)
- {
- return null;
- }
- else
- {
- return _connection;
- }
- }
- }
-
- ///
- protected override DbConnection DbConnection => Connection;
-
- internal SqlInternalTransaction InternalTransaction => _internalTransaction;
-
- ///
- public override IsolationLevel IsolationLevel
- {
- get
- {
- ZombieCheck();
- return _isolationLevel;
- }
- }
-
- private bool Is2005PartialZombie => _internalTransaction !=null && _internalTransaction.IsCompleted;
-
- internal bool IsZombied => _internalTransaction == null || _internalTransaction.IsCompleted;
-
- internal int ObjectID => _objectID;
-
- internal SqlStatistics Statistics
- {
- get
- {
- if (_connection != null)
- {
- if (_connection.StatisticsEnabled)
- {
- return _connection.Statistics;
- }
- }
- return null;
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////
- // INTERNAL METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
-
- internal void Zombie()
- {
- // For Yukon, we have to defer "zombification" until
- // we get past the users' next rollback, else we'll
- // throw an exception there that is a breaking change.
- // Of course, if the connection is already closed,
- // then we're free to zombify...
- SqlInternalConnection internalConnection = (_connection.InnerConnection as SqlInternalConnection);
- if (internalConnection != null && !_isFromAPI)
- {
- SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlTransaction.Zombie | ADV | Object Id {0} yukon deferred zombie", ObjectID);
- }
- else
- {
- _internalTransaction = null; // pre SQL 2005 zombification
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////
- // PRIVATE METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
-
- private void ZombieCheck()
- {
- // If this transaction has been completed, throw exception since it is unusable.
- if (IsZombied)
- {
- if (Is2005PartialZombie)
- {
- _internalTransaction = null; // SQL 2005 zombification
- }
-
- throw ADP.TransactionZombied(this);
- }
- }
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs
new file mode 100644
index 0000000000..8130b529d7
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs
@@ -0,0 +1,470 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.ComponentModel;
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Threading;
+using Microsoft.Data.Common;
+
+#if NET
+using Microsoft.Data.SqlClient.Diagnostics;
+#else
+using System.Runtime.CompilerServices;
+#endif
+
+namespace Microsoft.Data.SqlClient
+{
+ ///
+ public sealed class SqlTransaction : DbTransaction
+ {
+ #if NET
+ private static readonly SqlDiagnosticListener s_diagnosticListener = new();
+ #endif
+
+ private static int s_objectTypeCount; // EventSource Counter
+
+ private readonly SqlConnection _connection;
+ private readonly IsolationLevel _isolationLevel;
+ private bool _isFromApi;
+
+ internal SqlTransaction(
+ SqlInternalConnection internalConnection,
+ SqlConnection con,
+ IsolationLevel iso,
+ SqlInternalTransaction internalTransaction)
+ {
+ #if NETFRAMEWORK
+ SqlConnection.VerifyExecutePermission();
+ #endif
+ _isolationLevel = iso;
+ _connection = con;
+
+ if (internalTransaction == null)
+ {
+ InternalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this);
+ }
+ else
+ {
+ Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!");
+ InternalTransaction = internalTransaction;
+ InternalTransaction.InitParent(this);
+ }
+ }
+
+ #region Properties
+
+ ///
+ public new SqlConnection Connection => IsZombied ? null : _connection;
+
+ ///
+ public override IsolationLevel IsolationLevel
+ {
+ get
+ {
+ ZombieCheck();
+ return _isolationLevel;
+ }
+ }
+
+ internal SqlInternalTransaction InternalTransaction { get; private set; }
+
+ internal bool IsZombied => InternalTransaction == null || InternalTransaction.IsCompleted;
+
+ internal int ObjectId { get; } = Interlocked.Increment(ref s_objectTypeCount);
+
+ ///
+ protected override DbConnection DbConnection => Connection;
+
+ private bool Is2005PartialZombie => InternalTransaction?.IsCompleted == true;
+
+ private SqlStatistics Statistics => _connection?.StatisticsEnabled == true ? _connection.Statistics : null;
+
+ #endregion
+
+ ///
+ public override void Commit()
+ {
+ #if NET
+ using DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionCommitScope(
+ _isolationLevel,
+ _connection,
+ InternalTransaction);
+ #endif
+
+ ZombieCheck();
+
+ using (TryEventScope.Create("SqlTransaction.Commit | API | Object Id {0}", ObjectId))
+ {
+ SqlStatistics statistics = null;
+
+ SqlClientEventSource.Log.TryCorrelationTraceEvent(
+ "SqlTransaction.Commit | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}",
+ ObjectId,
+ ActivityCorrelator.Current,
+ Connection?.ClientConnectionId);
+
+ #if NETFRAMEWORK
+ TdsParser bestEffortCleanupTarget = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ #endif
+ try
+ {
+ #if NETFRAMEWORK
+ bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
+ #endif
+
+ statistics = SqlStatistics.StartTimer(Statistics);
+
+ _isFromApi = true;
+
+ InternalTransaction.Commit();
+ }
+ #if NETFRAMEWORK
+ catch (OutOfMemoryException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (StackOverflowException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (ThreadAbortException e)
+ {
+ _connection.Abort(e);
+ SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
+ throw;
+ }
+ #endif
+ catch (SqlException ex)
+ {
+ #if NET
+ diagnosticScope.SetException(ex);
+ #endif
+
+ // GitHub Issue #130 - When a timeout exception has occurred on transaction completion request,
+ // this connection may not be in reusable state.
+ // We will abort this connection and make sure it does not go back to the pool.
+ if (ex.InnerException is Win32Exception innerException && innerException.NativeErrorCode == TdsEnums.SNI_WAIT_TIMEOUT)
+ {
+ _connection.Abort(ex);
+ }
+ throw;
+ }
+ #if NET
+ catch (Exception ex)
+ {
+ diagnosticScope.SetException(ex);
+ throw;
+ }
+ #endif
+ finally
+ {
+ SqlStatistics.StopTimer(statistics);
+ _isFromApi = false;
+ }
+ }
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ #if NETFRAMEWORK
+ TdsParser bestEffortCleanupTarget = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ #endif
+ try
+ {
+ #if NETFRAMEWORK
+ bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
+ #endif
+
+ if (!IsZombied && !Is2005PartialZombie)
+ {
+ InternalTransaction.Dispose();
+ }
+ }
+ catch (OutOfMemoryException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (StackOverflowException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (ThreadAbortException e)
+ {
+ _connection.Abort(e);
+
+ #if NETFRAMEWORK
+ SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
+ #endif
+
+ throw;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ ///
+ public override void Rollback()
+ {
+ #if NET
+ using DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope(
+ _isolationLevel,
+ _connection,
+ InternalTransaction,
+ transactionName: null);
+ #endif
+
+ if (Is2005PartialZombie)
+ {
+ // Put something in the trace in case a customer has an issue
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ "SqlTransaction.Rollback | ADV | Object Id {0}, partial zombie no rollback required",
+ ObjectId);
+
+ InternalTransaction = null; // 2005 zombification
+ }
+ else
+ {
+ ZombieCheck();
+
+ SqlStatistics statistics = null;
+ using (TryEventScope.Create("SqlTransaction.Rollback | API | Object Id {0}", ObjectId))
+ {
+ SqlClientEventSource.Log.TryCorrelationTraceEvent(
+ "SqlTransaction.Rollback | API | Correlation | Object Id {0}, ActivityID {1}, Client Connection Id {2}",
+ ObjectId,
+ ActivityCorrelator.Current,
+ Connection?.ClientConnectionId);
+
+ #if NETFRAMEWORK
+ TdsParser bestEffortCleanupTarget = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ #endif
+ try
+ {
+ #if NETFRAMEWORK
+ bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
+ #endif
+
+ statistics = SqlStatistics.StartTimer(Statistics);
+
+ _isFromApi = true;
+ InternalTransaction.Rollback();
+ }
+ #if NETFRAMEWORK
+ catch (OutOfMemoryException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (StackOverflowException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (ThreadAbortException e)
+ {
+ _connection.Abort(e);
+ SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
+ throw;
+ }
+ #else
+ catch (Exception ex)
+ {
+ diagnosticScope.SetException(ex);
+ throw;
+ }
+ #endif
+ finally
+ {
+ SqlStatistics.StopTimer(statistics);
+ _isFromApi = false;
+ }
+ }
+ }
+ }
+
+ ///
+ #if NET
+ public override void Rollback(string transactionName)
+ #else
+ public void Rollback(string transactionName)
+ #endif
+ {
+ #if NET
+ using DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope(
+ _isolationLevel,
+ _connection,
+ InternalTransaction,
+ transactionName);
+ #endif
+
+ #if NETFRAMEWORK
+ SqlConnection.ExecutePermission.Demand(); // MDAC 81476
+ #endif
+
+ ZombieCheck();
+
+ var eventScopeEnter = TryEventScope.Create(SqlClientEventSource.Log.TryScopeEnterEvent(
+ "SqlTransaction.Rollback | API | Object Id {0}, Transaction Name='{1}', ActivityID {2}, Client Connection Id {3}",
+ ObjectId,
+ transactionName,
+ ActivityCorrelator.Current,
+ Connection?.ClientConnectionId));
+ using (eventScopeEnter)
+ {
+ SqlStatistics statistics = null;
+
+ #if NETFRAMEWORK
+ TdsParser bestEffortCleanupTarget = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ #endif
+ try
+ {
+ #if NETFRAMEWORK
+ bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
+ #endif
+
+ statistics = SqlStatistics.StartTimer(Statistics);
+
+ _isFromApi = true;
+ InternalTransaction.Rollback(transactionName);
+ }
+ #if NETFRAMEWORK
+ catch (OutOfMemoryException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (StackOverflowException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (ThreadAbortException e)
+ {
+ _connection.Abort(e);
+ SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
+ throw;
+ }
+ #else
+ catch (Exception ex)
+ {
+ diagnosticScope.SetException(ex);
+ throw;
+ }
+ #endif
+ finally
+ {
+ SqlStatistics.StopTimer(statistics);
+ _isFromApi = false;
+ }
+ }
+ }
+
+ ///
+ #if NET
+ public override void Save(string savePointName)
+ #else
+ public void Save(string savePointName)
+ #endif
+ {
+ #if NETFRAMEWORK
+ SqlConnection.ExecutePermission.Demand(); // MDAC 81476
+ #endif
+
+ ZombieCheck();
+
+ SqlStatistics statistics = null;
+ using (TryEventScope.Create("SqlTransaction.Save | API | Object Id {0} | Save Point Name '{1}'", ObjectId, savePointName))
+ {
+ #if NETFRAMEWORK
+ TdsParser bestEffortCleanupTarget = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ #endif
+ try
+ {
+ #if NETFRAMEWORK
+ bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
+ #endif
+
+ statistics = SqlStatistics.StartTimer(Statistics);
+
+ InternalTransaction.Save(savePointName);
+ }
+ #if NETFRAMEWORK
+ catch (OutOfMemoryException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (StackOverflowException e)
+ {
+ _connection.Abort(e);
+ throw;
+ }
+ catch (ThreadAbortException e)
+ {
+ _connection.Abort(e);
+
+ #if NETFRAMEWORK
+ SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
+ #endif
+
+ throw;
+ }
+ #endif
+ finally
+ {
+ SqlStatistics.StopTimer(statistics);
+ }
+ }
+ }
+
+ internal void Zombie()
+ {
+ // For Yukon, we have to defer "zombification" until we get past the users' next
+ // rollback, else we'll throw an exception there that is a breaking change. Of course,
+ // if the connection is already closed, then we're free to zombify...
+ if (_connection.InnerConnection is SqlInternalConnection internalConnection && !_isFromApi)
+ {
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(
+ "SqlTransaction.Zombie | ADV | Object Id {0} yukon deferred zombie",
+ ObjectId);
+ }
+ else
+ {
+ // pre SQL 2005 zombification
+ InternalTransaction = null;
+ }
+ }
+
+ private void ZombieCheck()
+ {
+ // If this transaction has been completed, throw exception since it is unusable.
+ if (IsZombied)
+ {
+ if (Is2005PartialZombie)
+ {
+ // SQL 2005 zombification
+ InternalTransaction = null;
+ }
+
+ throw ADP.TransactionZombied(this);
+ }
+ }
+ }
+}