From 20102e54ed8086cea947fce212a5f2a65745ad84 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Mon, 14 Jul 2025 22:45:03 -0500 Subject: [PATCH 01/15] Introduce a CER helper --- .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 ++ .../ConstrainedExecutionHelper.netfx.cs | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ConstrainedExecutionHelper.netfx.cs 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 0c2fe65f93..c4a71da76c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -870,6 +870,9 @@ Microsoft\Data\SqlClient\Utilities\BufferWriterExtensions.netfx.cs + + Microsoft\Data\SqlClient\Utilities\ConstrainedExecutionHelper.netfx.cs + Microsoft\Data\SqlClient\Utilities\ObjectPool.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ConstrainedExecutionHelper.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ConstrainedExecutionHelper.netfx.cs new file mode 100644 index 0000000000..51e8a557fa --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ConstrainedExecutionHelper.netfx.cs @@ -0,0 +1,46 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Microsoft.Data.SqlClient.Utilities +{ + internal static class ConstrainedExecutionHelper + { + internal static void RunWithConnectionAbortAndCleanup(Action action, SqlConnection connectionToAbort) + { + TdsParser bestEffortCleanupTarget = null; + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(connectionToAbort); + action(); + } + catch (OutOfMemoryException e) + { + connectionToAbort.Abort(e); + throw; + } + catch (StackOverflowException e) + { + connectionToAbort.Abort(e); + throw; + } + catch (ThreadAbortException e) + { + connectionToAbort.Abort(e); + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); + + throw; + } + } + } +} + +#endif From 98321d8f1eb4cb558bf13e524951b7f4bc170425 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Mon, 14 Jul 2025 22:45:22 -0500 Subject: [PATCH 02/15] Add parameter-less overload for TryCorrelationTraceEvent --- .../Microsoft/Data/SqlClient/SqlClientEventSource.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs index 75f87ea13c..8da01fbbd7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs @@ -832,6 +832,16 @@ internal void TryAdvancedTraceErrorEvent(string message, T0 #endregion #region Correlation Trace + + [NonEvent] + internal void TryCorrelationTraceEvent(string message) + { + if (Log.IsCorrelationEnabled()) + { + CorrelationTrace(message); + } + } + [NonEvent] internal void TryCorrelationTraceEvent(string message, T0 args0) { From 0a607e5012048b56068bc5f87a51c8c9345a7c1b Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 16:20:49 -0500 Subject: [PATCH 03/15] Introduce common file, rename non-common files, remove stub --- .../src/Microsoft.Data.SqlClient.csproj | 5 ++++- .../{SqlCommand.cs => SqlCommand.netcore.cs} | 4 ---- .../netfx/src/Microsoft.Data.SqlClient.csproj | 5 ++++- .../{SqlCommand.cs => SqlCommand.netfx.cs} | 6 +----- .../Microsoft/Data/SqlClient/SqlCommand.cs | 19 +++++++++++++++++++ .../Data/SqlClient/SqlCommand.stub.cs | 15 --------------- 6 files changed, 28 insertions(+), 26 deletions(-) rename src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/{SqlCommand.cs => SqlCommand.netcore.cs} (99%) rename src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/{SqlCommand.cs => SqlCommand.netfx.cs} (99%) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.stub.cs 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 d1dd14f6b7..1cc249d23e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -570,6 +570,9 @@ Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs + + Microsoft\Data\SqlClient\SqlCommand.cs + Microsoft\Data\SqlClient\SqlCommandSet.cs @@ -799,7 +802,7 @@ System\Diagnostics\CodeAnalysis.cs - + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs rename to src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index f4f38dbe73..3d32a54e93 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -26,10 +26,6 @@ // New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future. namespace Microsoft.Data.SqlClient { - /// - [DefaultEvent("RecordsAffected")] - [ToolboxItem(true)] - [DesignerCategory("")] // TODO: Add designer attribute when Microsoft.VSDesigner.Data.VS.SqlCommandDesigner uses Microsoft.Data.SqlClient public sealed partial class SqlCommand : DbCommand, ICloneable { 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 c4a71da76c..3a0d003f80 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -684,6 +684,9 @@ Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs + + Microsoft\Data\SqlClient\SqlCommand.cs + Microsoft\Data\SqlClient\SqlCommandBuilder.cs @@ -916,7 +919,7 @@ System\Runtime\CompilerServices\IsExternalInit.netfx.cs - + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs rename to src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index d65a0b7522..25d3e4b153 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -29,12 +29,8 @@ // New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future. namespace Microsoft.Data.SqlClient { - /// - [DefaultEvent("RecordsAffected")] - [ToolboxItem(true)] - [DesignerCategory("")] // TODO: Add designer attribute when Microsoft.VSDesigner.Data.VS.SqlCommandDesigner uses Microsoft.Data.SqlClient - public sealed class SqlCommand : DbCommand, ICloneable + public sealed partial class SqlCommand : DbCommand, ICloneable { private static int _objectTypeCount; // EventSource Counter private const int MaxRPCNameLength = 1046; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs new file mode 100644 index 0000000000..c30fc34ac2 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -0,0 +1,19 @@ +// 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; + +namespace Microsoft.Data.SqlClient +{ + /// + [DefaultEvent("RecordsAffected")] + [DesignerCategory("")] + [ToolboxItem(true)] + public sealed partial class SqlCommand : DbCommand, ICloneable + { + + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.stub.cs deleted file mode 100644 index bb7e537cf8..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.stub.cs +++ /dev/null @@ -1,15 +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. - -// @TODO: This is only a stub class for removing clearing errors while merging other files. - -namespace Microsoft.Data.SqlClient -{ - public class SqlCommand - { - public SqlConnection Connection { get; set; } - - internal SqlStatistics Statistics { get; set; } - } -} From 11b14d3126ecd4e60be8a97ec7c73077c4da49f1 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 16:24:36 -0500 Subject: [PATCH 04/15] Merge ObjectID and _objectTypeCount --- .../Data/SqlClient/SqlCommand.netcore.cs | 2 -- .../Microsoft/Data/SqlClient/SqlCommand.netfx.cs | 2 -- .../src/Microsoft/Data/SqlClient/SqlCommand.cs | 16 +++++++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 3d32a54e93..bae7bc228e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -29,9 +29,7 @@ namespace Microsoft.Data.SqlClient // TODO: Add designer attribute when Microsoft.VSDesigner.Data.VS.SqlCommandDesigner uses Microsoft.Data.SqlClient public sealed partial class SqlCommand : DbCommand, ICloneable { - private static int _objectTypeCount; // EventSource Counter private const int MaxRPCNameLength = 1046; - internal readonly int ObjectID = Interlocked.Increment(ref _objectTypeCount); internal sealed class ExecuteReaderAsyncCallContext : AAsyncCallContext { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 25d3e4b153..59d52e80e3 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -32,9 +32,7 @@ namespace Microsoft.Data.SqlClient // TODO: Add designer attribute when Microsoft.VSDesigner.Data.VS.SqlCommandDesigner uses Microsoft.Data.SqlClient public sealed partial class SqlCommand : DbCommand, ICloneable { - private static int _objectTypeCount; // EventSource Counter private const int MaxRPCNameLength = 1046; - internal readonly int ObjectID = Interlocked.Increment(ref _objectTypeCount); internal sealed class ExecuteReaderAsyncCallContext : AAsyncCallContext { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index c30fc34ac2..d9accf538d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -5,6 +5,7 @@ using System; using System.ComponentModel; using System.Data.Common; +using System.Threading; namespace Microsoft.Data.SqlClient { @@ -14,6 +15,19 @@ namespace Microsoft.Data.SqlClient [ToolboxItem(true)] public sealed partial class SqlCommand : DbCommand, ICloneable { - + #region Fields + + /// + /// Number of instances of SqlCommand that have been created. Used to generate ObjectId + /// + private static int _objectTypeCount = 0; + + #endregion + + #region Properties + + internal int ObjectID { get; } = Interlocked.Increment(ref _objectTypeCount); + + #endregion } } From 41b3e60ac3e612bba827bf0a951ea150be55babe Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 16:32:12 -0500 Subject: [PATCH 05/15] Merge EXECTYPE and _execType --- .../Data/SqlClient/SqlCommand.netcore.cs | 14 +-------- .../Data/SqlClient/SqlCommand.netfx.cs | 13 -------- .../Microsoft/Data/SqlClient/SqlCommand.cs | 31 ++++++++++++++++++- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index bae7bc228e..6dcc22870d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -158,18 +158,7 @@ protected override void AfterCleared(SqlCommand owner) private bool _parentOperationStarted = false; internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; - - // Prepare - // Against 7.0 Serve a prepare/unprepare requires an extra roundtrip to the server. - // - // From 8.0 and above, the preparation can be done as part of the command execution. - - private enum EXECTYPE - { - UNPREPARED, // execute unprepared commands, all server versions (results in sp_execsql call) - PREPAREPENDING, // prepare and execute command, 8.0 and above only (results in sp_prepexec call) - PREPARED, // execute prepared commands, all server versions (results in sp_exec call) - } + // _hiddenPrepare // On 8.0 and above the Prepared state cannot be left. Once a command is prepared it will always be prepared. @@ -190,7 +179,6 @@ private enum EXECTYPE private SqlParameterCollection _parameters; private SqlConnection _activeConnection; private bool _dirty = false; // true if the user changes the commandtext or number of parameters after the command is already prepared - private EXECTYPE _execType = EXECTYPE.UNPREPARED; // by default, assume the user is not sharing a connection so the command has not been prepared private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 59d52e80e3..175268b9ea 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -158,18 +158,6 @@ protected override void AfterCleared(SqlCommand owner) #endif internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; - // Prepare - // Against 7.0 Serve a prepare/unprepare requires an extra roundtrip to the server. - // - // From 8.0 and above, the preparation can be done as part of the command execution. - - private enum EXECTYPE - { - UNPREPARED, // execute unprepared commands, all server versions (results in sp_execsql call) - PREPAREPENDING, // prepare and execute command, 8.0 and above only (results in sp_prepexec call) - PREPARED, // execute prepared commands, all server versions (results in sp_exec call) - } - // devnotes // // _hiddenPrepare @@ -191,7 +179,6 @@ private enum EXECTYPE private SqlParameterCollection _parameters; private SqlConnection _activeConnection; private bool _dirty = false; // true if the user changes the commandtext or number of parameters after the command is already prepared - private EXECTYPE _execType = EXECTYPE.UNPREPARED; // by default, assume the user is not sharing a connection so the command has not been prepared private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index d9accf538d..a127d0221c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -22,10 +22,39 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// private static int _objectTypeCount = 0; + /// + /// Current state of preparation of the command. + /// By default, assume the user is not sharing a connection so the command has not been prepared. + /// + private EXECTYPE _execType = EXECTYPE.UNPREPARED; + #endregion + #region Enums + + // @TODO: Rename to match naming conventions + private enum EXECTYPE + { + /// + /// Execute unprepared commands, all server versions (results in sp_execsql call) + /// + UNPREPARED, + + /// + /// Prepare and execute command, 8.0 and above only (results in sp_prepexec call) + /// + PREPAREPENDING, + + /// + /// execute prepared commands, all server versions (results in sp_exec call) + /// + PREPARED, + } + + #endregion + #region Properties - + internal int ObjectID { get; } = Interlocked.Increment(ref _objectTypeCount); #endregion From 117f88deec37b732ccee78a50affed7ac3de7e75 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 16:39:43 -0500 Subject: [PATCH 06/15] Merge _inPrepare and InPrepare --- .../src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs | 8 -------- .../src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs | 8 -------- .../src/Microsoft/Data/SqlClient/SqlCommand.cs | 10 +++++++++- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 6dcc22870d..ece1e02b39 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -170,7 +170,6 @@ protected override void AfterCleared(SqlCommand owner) // _prepareHandle - the handle of a prepared command. Apparently there can be multiple prepared commands at a time - a feature that we do not support yet. private static readonly object s_cachedInvalidPrepareHandle = (object)-1; - private bool _inPrepare = false; private object _prepareHandle = s_cachedInvalidPrepareHandle; // this is an int which is used in the object typed SqlParameter.Value field, avoid repeated boxing by storing in a box private bool _hiddenPrepare = false; private int _preparedConnectionCloseCount = -1; @@ -209,13 +208,6 @@ private bool ShouldCacheEncryptionMetadata #if DEBUG internal static int DebugForceAsyncWriteDelay { get; set; } #endif - internal bool InPrepare - { - get - { - return _inPrepare; - } - } /// /// Return if column encryption setting is enabled. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 175268b9ea..21b9fe3a6e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -170,7 +170,6 @@ protected override void AfterCleared(SqlCommand owner) // _prepareHandle - the handle of a prepared command. Apparently there can be multiple prepared commands at a time - a feature that we do not support yet. private static readonly object s_cachedInvalidPrepareHandle = (object)-1; - private bool _inPrepare = false; private object _prepareHandle = s_cachedInvalidPrepareHandle; // this is an int which is used in the object typed SqlParameter.Value field, avoid repeated boxing by storing in a box private bool _hiddenPrepare = false; private int _preparedConnectionCloseCount = -1; @@ -209,13 +208,6 @@ private bool ShouldCacheEncryptionMetadata #if DEBUG internal static int DebugForceAsyncWriteDelay { get; set; } #endif - internal bool InPrepare - { - get - { - return _inPrepare; - } - } /// /// Return if column encryption setting is enabled. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index a127d0221c..715844c547 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -27,7 +27,13 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// By default, assume the user is not sharing a connection so the command has not been prepared. /// private EXECTYPE _execType = EXECTYPE.UNPREPARED; - + + /// + /// Whether the current instance is in the middle of preparation. + /// + // @TODO: Make auto-property + private bool _inPrepare = false; + #endregion #region Enums @@ -55,6 +61,8 @@ private enum EXECTYPE #region Properties + internal bool InPrepare => _inPrepare; + internal int ObjectID { get; } = Interlocked.Increment(ref _objectTypeCount); #endregion From e9904dfb342111136fe0aab38fe3501afd246344 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 16:49:28 -0500 Subject: [PATCH 07/15] Merge _hiddenPrepare and comments for _inPrepare --- .../Microsoft/Data/SqlClient/SqlCommand.netcore.cs | 10 ++-------- .../src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs | 10 ---------- .../src/Microsoft/Data/SqlClient/SqlCommand.cs | 11 ++++++++++- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index ece1e02b39..86036c421d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -159,19 +159,13 @@ protected override void AfterCleared(SqlCommand owner) internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; - - // _hiddenPrepare - // On 8.0 and above the Prepared state cannot be left. Once a command is prepared it will always be prepared. - // A change in parameters, commandtext etc (IsDirty) automatically causes a hidden prepare - // - // _inPrepare will be set immediately before the actual prepare is done. - // The OnReturnValue function will test this flag to determine whether the returned value is a _prepareHandle or something else. + + // // _prepareHandle - the handle of a prepared command. Apparently there can be multiple prepared commands at a time - a feature that we do not support yet. private static readonly object s_cachedInvalidPrepareHandle = (object)-1; private object _prepareHandle = s_cachedInvalidPrepareHandle; // this is an int which is used in the object typed SqlParameter.Value field, avoid repeated boxing by storing in a box - private bool _hiddenPrepare = false; private int _preparedConnectionCloseCount = -1; private int _preparedConnectionReconnectCount = -1; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 21b9fe3a6e..888c4491f9 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -158,20 +158,10 @@ protected override void AfterCleared(SqlCommand owner) #endif internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; - // devnotes - // - // _hiddenPrepare - // On 8.0 and above the Prepared state cannot be left. Once a command is prepared it will always be prepared. - // A change in parameters, commandtext etc (IsDirty) automatically causes a hidden prepare - // - // _inPrepare will be set immediately before the actual prepare is done. - // The OnReturnValue function will test this flag to determine whether the returned value is a _prepareHandle or something else. - // // _prepareHandle - the handle of a prepared command. Apparently there can be multiple prepared commands at a time - a feature that we do not support yet. private static readonly object s_cachedInvalidPrepareHandle = (object)-1; private object _prepareHandle = s_cachedInvalidPrepareHandle; // this is an int which is used in the object typed SqlParameter.Value field, avoid repeated boxing by storing in a box - private bool _hiddenPrepare = false; private int _preparedConnectionCloseCount = -1; private int _preparedConnectionReconnectCount = -1; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 715844c547..e14f930a4f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -27,9 +27,18 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// By default, assume the user is not sharing a connection so the command has not been prepared. /// private EXECTYPE _execType = EXECTYPE.UNPREPARED; + + /// + /// On 8.0 and above the Prepared state cannot be left. Once a command is prepared it will + /// always be prepared. A change in parameters, command text, etc (IsDirty) automatically + /// causes a hidden prepare. + /// + private bool _hiddenPrepare = false; /// - /// Whether the current instance is in the middle of preparation. + /// _inPrepare will be set immediately before the actual prepare is done. The OnReturnValue + /// function will test this flag to determine whether the returned value is a + /// _prepareHandle or something else. /// // @TODO: Make auto-property private bool _inPrepare = false; From eaf8ea4e1b23ea5fc1d96de074ab950b1a59bce2 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 17:02:48 -0500 Subject: [PATCH 08/15] Merge _dirty and IsDirty --- .../Data/SqlClient/SqlCommand.netcore.cs | 25 ------------ .../Data/SqlClient/SqlCommand.netfx.cs | 25 ------------ .../Microsoft/Data/SqlClient/SqlCommand.cs | 39 +++++++++++++++++++ 3 files changed, 39 insertions(+), 50 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 86036c421d..26acc0f7fc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -171,7 +171,6 @@ protected override void AfterCleared(SqlCommand owner) private SqlParameterCollection _parameters; private SqlConnection _activeConnection; - private bool _dirty = false; // true if the user changes the commandtext or number of parameters after the command is already prepared private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes @@ -6789,30 +6788,6 @@ private bool IsUserPrepared get { return IsPrepared && !_hiddenPrepare && !IsDirty; } } - internal bool IsDirty - { - get - { - // only dirty if prepared - var activeConnection = _activeConnection; - return (IsPrepared && - (_dirty || - ((_parameters != null) && (_parameters.IsDirty)) || - ((activeConnection != null) && ((activeConnection.CloseCount != _preparedConnectionCloseCount) || (activeConnection.ReconnectCount != _preparedConnectionReconnectCount))))); - } - set - { - // only mark the command as dirty if it is already prepared - // but always clear the value if it we are clearing the dirty flag - _dirty = value ? IsPrepared : false; - if (_parameters != null) - { - _parameters.IsDirty = _dirty; - } - _cachedMetaData = null; - } - } - /// /// Get or add to the number of records affected by SpDescribeParameterEncryption. /// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 888c4491f9..2df771dc98 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -167,7 +167,6 @@ protected override void AfterCleared(SqlCommand owner) private SqlParameterCollection _parameters; private SqlConnection _activeConnection; - private bool _dirty = false; // true if the user changes the commandtext or number of parameters after the command is already prepared private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes @@ -6857,30 +6856,6 @@ private bool IsUserPrepared get { return IsPrepared && !_hiddenPrepare && !IsDirty; } } - internal bool IsDirty - { - get - { - // only dirty if prepared - var activeConnection = _activeConnection; - return (IsPrepared && - (_dirty || - ((_parameters != null) && (_parameters.IsDirty)) || - ((activeConnection != null) && ((activeConnection.CloseCount != _preparedConnectionCloseCount) || (activeConnection.ReconnectCount != _preparedConnectionReconnectCount))))); - } - set - { - // only mark the command as dirty if it is already prepared - // but always clear the value if it we are clearing the dirty flag - _dirty = value ? IsPrepared : false; - if (_parameters != null) - { - _parameters.IsDirty = _dirty; - } - _cachedMetaData = null; - } - } - /// /// Get or add to the number of records affected by SpDescribeParameterEncryption. /// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index e14f930a4f..e57c3b842d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -28,6 +28,13 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// private EXECTYPE _execType = EXECTYPE.UNPREPARED; + /// + /// True if the user changes the command text or number of parameters after the command has + /// already prepared. + /// + // @TODO: Consider renaming "_IsUserDirty" + private bool _dirty = false; + /// /// On 8.0 and above the Prepared state cannot be left. Once a command is prepared it will /// always be prepared. A change in parameters, command text, etc (IsDirty) automatically @@ -72,8 +79,40 @@ private enum EXECTYPE internal bool InPrepare => _inPrepare; + // @TODO: Rename to match conventions. internal int ObjectID { get; } = Interlocked.Increment(ref _objectTypeCount); + private bool IsDirty + { + get + { + // @TODO: Factor out closeCount/reconnectCount checks to properties and clean up. + // To wit: closeCount checks whether the connection has been closed after preparation, + // reconnectCount, the same only with reconnections. + + // only dirty if prepared + var activeConnection = _activeConnection; + return IsPrepared && + (_dirty || + (_parameters != null && _parameters.IsDirty) || + (activeConnection != null && (activeConnection.CloseCount != _preparedConnectionCloseCount || activeConnection.ReconnectCount != _preparedConnectionReconnectCount))); + } + set + { + // @TODO: Consider reworking to do this in a helper method, since setting, sets to the + // _dirty, but that's not the only consideration when determining dirtiness. + + // only mark the command as dirty if it is already prepared + // but always clear the value if it we are clearing the dirty flag + _dirty = value ? IsPrepared : false; + if (_parameters != null) + { + _parameters.IsDirty = _dirty; + } + _cachedMetaData = null; + } + } + #endregion } } From 7d03d254f3390b74eafe4937b751b1a02546e0d1 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 17:08:07 -0500 Subject: [PATCH 09/15] Merge _activeConnection, _commandText, _commandType, --- .../Data/SqlClient/SqlCommand.netcore.cs | 3 --- .../Microsoft/Data/SqlClient/SqlCommand.netfx.cs | 3 --- .../src/Microsoft/Data/SqlClient/SqlCommand.cs | 16 ++++++++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 26acc0f7fc..28cd3ee1ca 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -107,8 +107,6 @@ protected override void AfterCleared(SqlCommand owner) } } - private string _commandText; - private CommandType _commandType; private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; @@ -170,7 +168,6 @@ protected override void AfterCleared(SqlCommand owner) private int _preparedConnectionReconnectCount = -1; private SqlParameterCollection _parameters; - private SqlConnection _activeConnection; private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 2df771dc98..9042c9d3aa 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -110,8 +110,6 @@ protected override void AfterCleared(SqlCommand owner) } } - private string _commandText; - private CommandType _commandType; private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; @@ -166,7 +164,6 @@ protected override void AfterCleared(SqlCommand owner) private int _preparedConnectionReconnectCount = -1; private SqlParameterCollection _parameters; - private SqlConnection _activeConnection; private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index e57c3b842d..8642bae57c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; +using System.Data; using System.Data.Common; using System.Threading; @@ -22,6 +23,21 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// private static int _objectTypeCount = 0; + /// + /// Connection that will be used to process the current instance. + /// + private SqlConnection _activeConnection; + + /// + /// Text to execute when executing the command. + /// + private string _commandText; + + /// + /// Type of the command to execute. + /// + private CommandType _commandType; + /// /// Current state of preparation of the command. /// By default, assume the user is not sharing a connection so the command has not been prepared. From a220377591a6b1e10347317342266a6b7ac46c71 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 17:12:00 -0500 Subject: [PATCH 10/15] Merge IsPrepared, IsUserPrepared --- .../src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs | 10 ---------- .../src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs | 10 ---------- .../src/Microsoft/Data/SqlClient/SqlCommand.cs | 7 ++++++- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 28cd3ee1ca..eab7e265ea 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -6775,16 +6775,6 @@ internal TdsParserStateObject StateObject } } - private bool IsPrepared - { - get { return (_execType != EXECTYPE.UNPREPARED); } - } - - private bool IsUserPrepared - { - get { return IsPrepared && !_hiddenPrepare && !IsDirty; } - } - /// /// Get or add to the number of records affected by SpDescribeParameterEncryption. /// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 9042c9d3aa..4fe597c2e8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -6843,16 +6843,6 @@ internal TdsParserStateObject StateObject } } - private bool IsPrepared - { - get { return (_execType != EXECTYPE.UNPREPARED); } - } - - private bool IsUserPrepared - { - get { return IsPrepared && !_hiddenPrepare && !IsDirty; } - } - /// /// Get or add to the number of records affected by SpDescribeParameterEncryption. /// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 8642bae57c..6a550dba6c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -119,7 +119,7 @@ private bool IsDirty // _dirty, but that's not the only consideration when determining dirtiness. // only mark the command as dirty if it is already prepared - // but always clear the value if it we are clearing the dirty flag + // but always clear the value if we are clearing the dirty flag _dirty = value ? IsPrepared : false; if (_parameters != null) { @@ -129,6 +129,11 @@ private bool IsDirty } } + private bool IsPrepared => _execType is not EXECTYPE.UNPREPARED; + + // @TODO: IsPrepared is part of IsDirty - this is confusing. + private bool IsUserPrepared => IsPrepared && !_hiddenPrepare && !IsDirty; + #endregion } } From ef1dacfd34272d6acef3286b60d5477a603a0916 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 17:15:08 -0500 Subject: [PATCH 11/15] Merge _stateObject, remove StateObject (not used) --- .../src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs | 10 ---------- .../src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs | 10 ---------- .../src/Microsoft/Data/SqlClient/SqlCommand.cs | 5 +++++ 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index eab7e265ea..40549fbf08 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -346,8 +346,6 @@ private AsyncState CachedAsyncState private StatementCompletedEventHandler _statementCompletedEventHandler; - private TdsParserStateObject _stateObj; // this is the TDS session we're using. - // Volatile bool used to synchronize with cancel thread the state change of an executing // command going from pre-processing to obtaining a stateObject. The cancel synchronization // we require in the command is only from entering an Execute* API to obtaining a @@ -6767,14 +6765,6 @@ internal void OnConnectionClosed() } } - internal TdsParserStateObject StateObject - { - get - { - return _stateObj; - } - } - /// /// Get or add to the number of records affected by SpDescribeParameterEncryption. /// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index 4fe597c2e8..c7e2cae901 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -344,8 +344,6 @@ private AsyncState CachedAsyncState private StatementCompletedEventHandler _statementCompletedEventHandler; - private TdsParserStateObject _stateObj; // this is the TDS session we're using. - // Volatile bool used to synchronize with cancel thread the state change of an executing // command going from pre-processing to obtaining a stateObject. The cancel synchronization // we require in the command is only from entering an Execute* API to obtaining a @@ -6835,14 +6833,6 @@ internal void OnConnectionClosed() } } - internal TdsParserStateObject StateObject - { - get - { - return _stateObj; - } - } - /// /// Get or add to the number of records affected by SpDescribeParameterEncryption. /// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 6a550dba6c..12622bb22a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -65,6 +65,11 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// // @TODO: Make auto-property private bool _inPrepare = false; + + /// + /// TDS session the current instance is using. + /// + private TdsParserStateObject _stateObj; #endregion From 253672bfb97127ce27a87a28a020b3ff602a833d Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 17:35:49 -0500 Subject: [PATCH 12/15] Move code from Prepare into InternalPrepare - this'll make more sense with the next commit --- .../Data/SqlClient/SqlCommand.netcore.cs | 26 +++++++++---------- .../Data/SqlClient/SqlCommand.netfx.cs | 26 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 40549fbf08..2c10c003f2 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -877,19 +877,6 @@ public override void Prepare() bool processFinallyBlock = true; try { - // NOTE: The state object isn't actually needed for this, but it is still here for back-compat (since it does a bunch of checks) - GetStateObject(); - - // Loop through parameters ensuring that we do not have unspecified types, sizes, scales, or precisions - if (_parameters != null) - { - int count = _parameters.Count; - for (int i = 0; i < count; ++i) - { - _parameters[i].Prepare(this); - } - } - InternalPrepare(); } catch (Exception e) @@ -917,6 +904,19 @@ public override void Prepare() private void InternalPrepare() { + // NOTE: The state object isn't actually needed for this, but it is still here for back-compat (since it does a bunch of checks) + GetStateObject(); + + // Loop through parameters ensuring that we do not have unspecified types, sizes, scales, or precisions + if (_parameters != null) + { + int count = _parameters.Count; + for (int i = 0; i < count; ++i) + { + _parameters[i].Prepare(this); + } + } + if (this.IsDirty) { Debug.Assert(_cachedMetaData == null || !_dirty, "dirty query should not have cached metadata!"); // can have cached metadata if dirty because of parameters diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index c7e2cae901..c9b5efbf75 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -915,19 +915,6 @@ public override void Prepare() { bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - // NOTE: The state object isn't actually needed for this, but it is still here for back-compat (since it does a bunch of checks) - GetStateObject(); - - // Loop through parameters ensuring that we do not have unspecified types, sizes, scales, or precisions - if (_parameters != null) - { - int count = _parameters.Count; - for (int i = 0; i < count; ++i) - { - _parameters[i].Prepare(this); // MDAC 67063 - } - } - InternalPrepare(); } catch (System.OutOfMemoryException e) @@ -972,6 +959,19 @@ public override void Prepare() private void InternalPrepare() { + // NOTE: The state object isn't actually needed for this, but it is still here for back-compat (since it does a bunch of checks) + GetStateObject(); + + // Loop through parameters ensuring that we do not have unspecified types, sizes, scales, or precisions + if (_parameters != null) + { + int count = _parameters.Count; + for (int i = 0; i < count; ++i) + { + _parameters[i].Prepare(this); // MDAC 67063 + } + } + if (this.IsDirty) { Debug.Assert(_cachedMetaData == null || !_dirty, "dirty query should not have cached metadata!"); // can have cached metadata if dirty because of parameters From 363386725578658aefe7ce018f92fb496fcc7e25 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 17:41:23 -0500 Subject: [PATCH 13/15] Merge InternalPrepare to PrepareInternal --- .../Data/SqlClient/SqlCommand.netcore.cs | 45 +--------------- .../Data/SqlClient/SqlCommand.netfx.cs | 45 +--------------- .../Microsoft/Data/SqlClient/SqlCommand.cs | 54 +++++++++++++++++++ 3 files changed, 56 insertions(+), 88 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 2c10c003f2..a5e222d7f6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -877,7 +877,7 @@ public override void Prepare() bool processFinallyBlock = true; try { - InternalPrepare(); + PrepareInternal(); } catch (Exception e) { @@ -902,49 +902,6 @@ public override void Prepare() } } - private void InternalPrepare() - { - // NOTE: The state object isn't actually needed for this, but it is still here for back-compat (since it does a bunch of checks) - GetStateObject(); - - // Loop through parameters ensuring that we do not have unspecified types, sizes, scales, or precisions - if (_parameters != null) - { - int count = _parameters.Count; - for (int i = 0; i < count; ++i) - { - _parameters[i].Prepare(this); - } - } - - if (this.IsDirty) - { - Debug.Assert(_cachedMetaData == null || !_dirty, "dirty query should not have cached metadata!"); // can have cached metadata if dirty because of parameters - // - // someone changed the command text or the parameter schema so we must unprepare the command - // - this.Unprepare(); - this.IsDirty = false; - } - Debug.Assert(_execType != EXECTYPE.PREPARED, "Invalid attempt to Prepare already Prepared command!"); - Debug.Assert(_activeConnection != null, "must have an open connection to Prepare"); - Debug.Assert(_stateObj != null, "TdsParserStateObject should not be null"); - Debug.Assert(_stateObj.Parser != null, "TdsParser class should not be null in Command.Execute!"); - Debug.Assert(_stateObj.Parser == _activeConnection.Parser, "stateobject parser not same as connection parser"); - Debug.Assert(false == _inPrepare, "Already in Prepare cycle, this.inPrepare should be false!"); - - // remember that the user wants to do a prepare but don't actually do an rpc - _execType = EXECTYPE.PREPAREPENDING; - // Note the current close count of the connection - this will tell us if the connection has been closed between calls to Prepare() and Execute - _preparedConnectionCloseCount = _activeConnection.CloseCount; - _preparedConnectionReconnectCount = _activeConnection.ReconnectCount; - - if (Statistics != null) - { - Statistics.SafeIncrement(ref Statistics._prepares); - } - } - // SqlInternalConnectionTds needs to be able to unprepare a statement internal void Unprepare() { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index c9b5efbf75..ea93976c0b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -915,7 +915,7 @@ public override void Prepare() { bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - InternalPrepare(); + PrepareInternal(); } catch (System.OutOfMemoryException e) { @@ -957,49 +957,6 @@ public override void Prepare() } } - private void InternalPrepare() - { - // NOTE: The state object isn't actually needed for this, but it is still here for back-compat (since it does a bunch of checks) - GetStateObject(); - - // Loop through parameters ensuring that we do not have unspecified types, sizes, scales, or precisions - if (_parameters != null) - { - int count = _parameters.Count; - for (int i = 0; i < count; ++i) - { - _parameters[i].Prepare(this); // MDAC 67063 - } - } - - if (this.IsDirty) - { - Debug.Assert(_cachedMetaData == null || !_dirty, "dirty query should not have cached metadata!"); // can have cached metadata if dirty because of parameters - // - // someone changed the command text or the parameter schema so we must unprepare the command - // - this.Unprepare(); - this.IsDirty = false; - } - Debug.Assert(_execType != EXECTYPE.PREPARED, "Invalid attempt to Prepare already Prepared command!"); - Debug.Assert(_activeConnection != null, "must have an open connection to Prepare"); - Debug.Assert(_stateObj != null, "TdsParserStateObject should not be null"); - Debug.Assert(_stateObj.Parser != null, "TdsParser class should not be null in Command.Execute!"); - Debug.Assert(_stateObj.Parser == _activeConnection.Parser, "stateobject parser not same as connection parser"); - Debug.Assert(false == _inPrepare, "Already in Prepare cycle, this.inPrepare should be false!"); - - // remember that the user wants to do a prepare but don't actually do an rpc - _execType = EXECTYPE.PREPAREPENDING; - // Note the current close count of the connection - this will tell us if the connection has been closed between calls to Prepare() and Execute - _preparedConnectionCloseCount = _activeConnection.CloseCount; - _preparedConnectionReconnectCount = _activeConnection.ReconnectCount; - - if (Statistics != null) - { - Statistics.SafeIncrement(ref Statistics._prepares); - } - } - // SqlInternalConnectionTds needs to be able to unprepare a statement internal void Unprepare() { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 12622bb22a..4b12b5c382 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -6,7 +6,9 @@ using System.ComponentModel; using System.Data; using System.Data.Common; +using System.Diagnostics; using System.Threading; +using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { @@ -140,5 +142,57 @@ private bool IsDirty private bool IsUserPrepared => IsPrepared && !_hiddenPrepare && !IsDirty; #endregion + + #region Public Methods + + #endregion + + #region Private Methods + + private void PrepareInternal() + { + // NOTE: The state object isn't actually needed for this, but it is still here for + // back-compat (since it does a bunch of checks) + GetStateObject(); + + // Loop through parameters ensuring that we do not have unspecified types, sizes, + // scales, or precisions + if (_parameters is not null) + { + int count = _parameters.Count; + for (int i = 0; i < count; ++i) + { + _parameters[i].Prepare(this); + } + } + + if (IsDirty) + { + Debug.Assert(_cachedMetaData is null || !_dirty, "dirty query should not have cached metadata!"); + + // Someone changed the command text or the parameter schema so we must unprepare the command + this.Unprepare(); + IsDirty = false; + } + + Debug.Assert(_execType is not EXECTYPE.PREPARED, "Invalid attempt to Prepare already Prepared command!"); + Debug.Assert(_activeConnection is not null, "must have an open connection to Prepare"); + Debug.Assert(_stateObj is not null, "TdsParserStateObject should not be null"); + Debug.Assert(_stateObj.Parser is not null, "TdsParser class should not be null in Command.Execute!"); + Debug.Assert(_stateObj.Parser == _activeConnection.Parser, "stateobject parser not same as connection parser"); + Debug.Assert(!_inPrepare, "Already in Prepare cycle, this.inPrepare should be false!"); + + // Remember that the user wants to do a prepare but don't actually do an rpc + _execType = EXECTYPE.PREPAREPENDING; + + // Note the current close count of the connection - this will tell us if the connection has been + // closed between calls to Prepare and Execute + _preparedConnectionCloseCount = _activeConnection.CloseCount; + _preparedConnectionReconnectCount = _activeConnection.ReconnectCount; + + Statistics?.SafeIncrement(ref Statistics._prepares); + } + + #endregion } } From 93b0d09798b02703b60504ef875e0eb758ecb735 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 15 Jul 2025 17:58:15 -0500 Subject: [PATCH 14/15] Merge Prepare - see? pretty cool, huh --- .../Data/SqlClient/SqlCommand.netcore.cs | 60 -------------- .../Data/SqlClient/SqlCommand.netfx.cs | 83 ------------------- .../Microsoft/Data/SqlClient/SqlCommand.cs | 73 ++++++++++++++++ 3 files changed, 73 insertions(+), 143 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index a5e222d7f6..872d4e889a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -842,66 +842,6 @@ private void PropertyChanging() this.IsDirty = true; } - /// - public override void Prepare() - { - // Reset _pendingCancel upon entry into any Execute - used to synchronize state - // between entry into Execute* API and the thread obtaining the stateObject. - _pendingCancel = false; - - SqlStatistics statistics = null; - using (TryEventScope.Create("SqlCommand.Prepare | API | Object Id {0}", ObjectID)) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlCommand.Prepare | API | Correlation | Object Id {0}, ActivityID {1}, Client Connection Id {2}", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId); - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - // only prepare if batch with parameters - if (this.IsPrepared && !this.IsDirty - || (this.CommandType == CommandType.StoredProcedure) - || ((System.Data.CommandType.Text == this.CommandType) - && (0 == GetParameterCount(_parameters)))) - { - if (Statistics != null) - { - Statistics.SafeIncrement(ref Statistics._prepares); - } - _hiddenPrepare = false; - } - else - { - // Validate the command outside of the try/catch to avoid putting the _stateObj on error - ValidateCommand(isAsync: false); - - bool processFinallyBlock = true; - try - { - PrepareInternal(); - } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - if (processFinallyBlock) - { - _hiddenPrepare = false; // The command is now officially prepared - - ReliablePutStateObject(); - } - } - } - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - } - // SqlInternalConnectionTds needs to be able to unprepare a statement internal void Unprepare() { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index ea93976c0b..cba1f1de93 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -874,89 +874,6 @@ private void PropertyChanging() this.IsDirty = true; } - /// - public override void Prepare() - { - SqlConnection.ExecutePermission.Demand(); - - // Reset _pendingCancel upon entry into any Execute - used to synchronize state - // between entry into Execute* API and the thread obtaining the stateObject. - _pendingCancel = false; - - using (TryEventScope.Create("SqlCommand.Prepare | API | Object Id {0}", ObjectID)) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - - SqlStatistics statistics = SqlStatistics.StartTimer(Statistics); - - // only prepare if batch with parameters - // MDAC BUG #'s 73776 & 72101 - if ( - IsPrepared && !IsDirty - || CommandType == CommandType.StoredProcedure - || (CommandType == CommandType.Text && GetParameterCount(_parameters) == 0) - ) - { - if (Statistics != null) - { - Statistics.SafeIncrement(ref Statistics._prepares); - } - _hiddenPrepare = false; - } - else - { - // Validate the command outside of the try\catch to avoid putting the _stateObj on error - ValidateCommand(isAsync: false); - - bool processFinallyBlock = true; - TdsParser bestEffortCleanupTarget = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - - PrepareInternal(); - } - catch (System.OutOfMemoryException e) - { - processFinallyBlock = false; - _activeConnection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - processFinallyBlock = false; - _activeConnection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - processFinallyBlock = false; - _activeConnection.Abort(e); - - SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); - throw; - } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - if (processFinallyBlock) - { - _hiddenPrepare = false; // The command is now officially prepared - - ReliablePutStateObject(); - } - } - } - - SqlStatistics.StopTimer(statistics); - } - } - // SqlInternalConnectionTds needs to be able to unprepare a statement internal void Unprepare() { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 4b12b5c382..52b99680a5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Threading; using Microsoft.Data.Common; +using Microsoft.Data.SqlClient.Utilities; namespace Microsoft.Data.SqlClient { @@ -145,6 +146,78 @@ private bool IsDirty #region Public Methods + /// + public override void Prepare() + { + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif + + using var _ = TryEventScope.Create("SqlCommand.Prepare | API | Object Id {0}", ObjectID); + SqlClientEventSource.Log.TryCorrelationTraceEvent( + "SqlCommand.Prepare | API | Correlation | " + + $"Object Id {ObjectID}, " + + $"ActivityID {ActivityCorrelator.Current}, " + + $"Client Connection Id {Connection?.ClientConnectionId}"); + + // Reset _pendingCancel upon entry into any Execute - used to synchronize state + // between entry into Execute* API and the thread obtaining the stateObject. + _pendingCancel = false; + + SqlStatistics statistics = null; + try + { + statistics = SqlStatistics.StartTimer(Statistics); + + // Only prepare batch has parameters + // @TODO: Factor out stored proc/text+parameter count to property + // @TODO: Not using parentheses is confusing here b/c it relies on order of operations knowledge + if (IsPrepared && !IsDirty || CommandType == CommandType.StoredProcedure + || (CommandType == CommandType.Text && GetParameterCount(_parameters) == 0)) + { + Statistics?.SafeIncrement(ref Statistics._prepares); + _hiddenPrepare = false; + } + else + { + // Validate the command outside the try\catch to avoid putting the _stateObj on error + ValidateCommand(isAsync: false); + + bool processFinallyBlock = true; + try + { + #if NET + PrepareInternal(); + #else + ConstrainedExecutionHelper.RunWithConnectionAbortAndCleanup(PrepareInternal, _activeConnection); + #endif + } + catch (Exception e) + { + // NOTE: This will catch any CER exceptions that were rethrown in netfx. + // they will always be uncatchable, so this is equivalent to setting + // processFinallyBlock to false inside the CER handlers. + processFinallyBlock = ADP.IsCatchableExceptionType(e); + throw; + } + finally + { + if (processFinallyBlock) + { + // The command is now officially prepared + _hiddenPrepare = false; + ReliablePutStateObject(); + } + } + + } + } + finally + { + SqlStatistics.StopTimer(statistics); + } + } + #endregion #region Private Methods From 9bf1118938cd5f0a433433f8ccd7b6030e7630d7 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Wed, 16 Jul 2025 15:22:41 -0500 Subject: [PATCH 15/15] Merge Unprepare --- .../Data/SqlClient/SqlCommand.netcore.cs | 22 -------------- .../Data/SqlClient/SqlCommand.netfx.cs | 22 -------------- .../Microsoft/Data/SqlClient/SqlCommand.cs | 29 +++++++++++++++++++ 3 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 872d4e889a..0d7dbaf00f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -842,28 +842,6 @@ private void PropertyChanging() this.IsDirty = true; } - // SqlInternalConnectionTds needs to be able to unprepare a statement - internal void Unprepare() - { - Debug.Assert(true == IsPrepared, "Invalid attempt to Unprepare a non-prepared command!"); - Debug.Assert(_activeConnection != null, "must have an open connection to UnPrepare"); - Debug.Assert(false == _inPrepare, "_inPrepare should be false!"); - _execType = EXECTYPE.PREPAREPENDING; - - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.UnPrepare | Info | Object Id {0}, Current Prepared Handle {1}", ObjectID, _prepareHandle); - - // Don't zero out the handle because we'll pass it in to sp_prepexec on the next prepare - // Unless the close count isn't the same as when we last prepared - if ((_activeConnection.CloseCount != _preparedConnectionCloseCount) || (_activeConnection.ReconnectCount != _preparedConnectionReconnectCount)) - { - // reset our handle - _prepareHandle = s_cachedInvalidPrepareHandle; - } - - _cachedMetaData = null; - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.UnPrepare | Info | Object Id {0}, Command unprepared.", ObjectID); - } - // Cancel is supposed to be multi-thread safe. // It doesn't make sense to verify the connection exists or that it is open during cancel // because immediately after checking the connection can be closed or removed via another thread. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs index cba1f1de93..eff93177bf 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs @@ -874,28 +874,6 @@ private void PropertyChanging() this.IsDirty = true; } - // SqlInternalConnectionTds needs to be able to unprepare a statement - internal void Unprepare() - { - Debug.Assert(true == IsPrepared, "Invalid attempt to Unprepare a non-prepared command!"); - Debug.Assert(_activeConnection != null, "must have an open connection to UnPrepare"); - Debug.Assert(false == _inPrepare, "_inPrepare should be false!"); - _execType = EXECTYPE.PREPAREPENDING; - - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.UnPrepare | Info | Object Id {0}, Current Prepared Handle {1}", ObjectID, _prepareHandle); - - // Don't zero out the handle because we'll pass it in to sp_prepexec on the next prepare - // Unless the close count isn't the same as when we last prepared - if ((_activeConnection.CloseCount != _preparedConnectionCloseCount) || (_activeConnection.ReconnectCount != _preparedConnectionReconnectCount)) - { - // reset our handle - _prepareHandle = -1; - } - - _cachedMetaData = null; - SqlClientEventSource.Log.TryTraceEvent("SqlCommand.UnPrepare | Info | Object Id {0}, Command unprepared.", ObjectID); - } - // Cancel is supposed to be multi-thread safe. // It doesn't make sense to verify the connection exists or that it is open during cancel // because immediately after checkin the connection can be closed or removed via another thread. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 52b99680a5..b3343789e6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -265,6 +265,35 @@ private void PrepareInternal() Statistics?.SafeIncrement(ref Statistics._prepares); } + + private void Unprepare() + { + Debug.Assert(IsPrepared, "Invalid attempt to Unprepare a non-prepared command!"); + Debug.Assert(_activeConnection is not null, "must have an open connection to UnPrepare"); + Debug.Assert(!_inPrepare, "_inPrepare should be false!"); + + SqlClientEventSource.Log.TryTraceEvent( + "SqlCommand.UnPrepare | Info | " + + $"Object Id {ObjectID}, " + + $"Current Prepared Handle {_prepareHandle}"); + + _execType = EXECTYPE.PREPAREPENDING; + + // Don't zero out the handle because we'll pass it in to sp_prepexec on the next prepare + // Unless the close count isn't the same as when we last prepared + if (_activeConnection.CloseCount != _preparedConnectionCloseCount || + _activeConnection.ReconnectCount != _preparedConnectionReconnectCount) + { + // Reset our handle + _prepareHandle = s_cachedInvalidPrepareHandle; + } + + _cachedMetaData = null; + + SqlClientEventSource.Log.TryTraceEvent( + $"SqlCommand.UnPrepare | Info | " + + $"Object Id {ObjectID}, Command unprepared."); + } #endregion }