Skip to content

Commit 0809419

Browse files
authored
Merge | SqlCommand Prepare/Unprepare (#3593)
* Add parameter-less overload for TryCorrelationTraceEvent * Introduce common file, rename non-common files, remove stub * Merge ObjectID and _objectTypeCount * Merge EXECTYPE and _execType * Merge _inPrepare and InPrepare * Merge _hiddenPrepare and comments for _inPrepare * Merge _dirty and IsDirty * Merge _activeConnection, _commandText, _commandType, * Merge IsPrepared, IsUserPrepared * Merge _stateObject, remove StateObject (not used) * Merge Prepare * Merge InternalPrepare * Merge Unprepare * Move _prepareHandle and s_cachedInvalidPrepareHandle * Add parentheses to checks in Prepare, factor out check for stored procedure and text command without parameters * Fix bug introduced by previous PR update, split test into smaller tests
1 parent 2f24ec7 commit 0809419

File tree

8 files changed

+414
-482
lines changed

8 files changed

+414
-482
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,9 @@
573573
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs">
574574
<Link>Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs</Link>
575575
</Compile>
576+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlCommand.cs">
577+
<Link>Microsoft\Data\SqlClient\SqlCommand.cs</Link>
578+
</Compile>
576579
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlCommandSet.cs">
577580
<Link>Microsoft\Data\SqlClient\SqlCommandSet.cs</Link>
578581
</Compile>
@@ -808,7 +811,7 @@
808811
<Link>System\Diagnostics\CodeAnalysis.cs</Link>
809812
</Compile>
810813

811-
<Compile Include="Microsoft\Data\SqlClient\SqlCommand.cs" />
814+
<Compile Include="Microsoft\Data\SqlClient\SqlCommand.netcore.cs" />
812815
<Compile Include="Microsoft\Data\SqlClient\SqlConnection.cs" />
813816
<Compile Include="Microsoft\Data\SqlClient\SqlConnectionHelper.cs" />
814817
<Compile Include="Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs" />

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs renamed to src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs

Lines changed: 0 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,10 @@
2626
// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future.
2727
namespace Microsoft.Data.SqlClient
2828
{
29-
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml' path='docs/members[@name="SqlCommand"]/SqlCommand/*'/>
30-
[DefaultEvent("RecordsAffected")]
31-
[ToolboxItem(true)]
32-
[DesignerCategory("")]
3329
// TODO: Add designer attribute when Microsoft.VSDesigner.Data.VS.SqlCommandDesigner uses Microsoft.Data.SqlClient
3430
public sealed partial class SqlCommand : DbCommand, ICloneable
3531
{
36-
private static int _objectTypeCount; // EventSource Counter
3732
private const int MaxRPCNameLength = 1046;
38-
internal readonly int ObjectID = Interlocked.Increment(ref _objectTypeCount);
3933

4034
internal sealed class ExecuteReaderAsyncCallContext : AAsyncCallContext<SqlCommand, SqlDataReader, CancellationTokenRegistration>
4135
{
@@ -113,8 +107,6 @@ protected override void AfterCleared(SqlCommand owner)
113107
}
114108
}
115109

116-
private string _commandText;
117-
private CommandType _commandType;
118110
private int? _commandTimeout;
119111
private UpdateRowSource _updatedRowSource = UpdateRowSource.Both;
120112
private bool _designTimeInvisible;
@@ -165,38 +157,10 @@ protected override void AfterCleared(SqlCommand owner)
165157

166158
internal static readonly Action<object> s_cancelIgnoreFailure = CancelIgnoreFailureCallback;
167159

168-
// Prepare
169-
// Against 7.0 Serve a prepare/unprepare requires an extra roundtrip to the server.
170-
//
171-
// From 8.0 and above, the preparation can be done as part of the command execution.
172-
173-
private enum EXECTYPE
174-
{
175-
UNPREPARED, // execute unprepared commands, all server versions (results in sp_execsql call)
176-
PREPAREPENDING, // prepare and execute command, 8.0 and above only (results in sp_prepexec call)
177-
PREPARED, // execute prepared commands, all server versions (results in sp_exec call)
178-
}
179-
180-
// _hiddenPrepare
181-
// On 8.0 and above the Prepared state cannot be left. Once a command is prepared it will always be prepared.
182-
// A change in parameters, commandtext etc (IsDirty) automatically causes a hidden prepare
183-
//
184-
// _inPrepare will be set immediately before the actual prepare is done.
185-
// The OnReturnValue function will test this flag to determine whether the returned value is a _prepareHandle or something else.
186-
//
187-
// _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.
188-
189-
private static readonly object s_cachedInvalidPrepareHandle = (object)-1;
190-
private bool _inPrepare = false;
191-
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
192-
private bool _hiddenPrepare = false;
193160
private int _preparedConnectionCloseCount = -1;
194161
private int _preparedConnectionReconnectCount = -1;
195162

196163
private SqlParameterCollection _parameters;
197-
private SqlConnection _activeConnection;
198-
private bool _dirty = false; // true if the user changes the commandtext or number of parameters after the command is already prepared
199-
private EXECTYPE _execType = EXECTYPE.UNPREPARED; // by default, assume the user is not sharing a connection so the command has not been prepared
200164
private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes
201165
private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes
202166

@@ -227,13 +191,6 @@ private bool ShouldCacheEncryptionMetadata
227191
#if DEBUG
228192
internal static int DebugForceAsyncWriteDelay { get; set; }
229193
#endif
230-
internal bool InPrepare
231-
{
232-
get
233-
{
234-
return _inPrepare;
235-
}
236-
}
237194

238195
/// <summary>
239196
/// Return if column encryption setting is enabled.
@@ -382,8 +339,6 @@ private AsyncState CachedAsyncState
382339

383340
private StatementCompletedEventHandler _statementCompletedEventHandler;
384341

385-
private TdsParserStateObject _stateObj; // this is the TDS session we're using.
386-
387342
// Volatile bool used to synchronize with cancel thread the state change of an executing
388343
// command going from pre-processing to obtaining a stateObject. The cancel synchronization
389344
// we require in the command is only from entering an Execute* API to obtaining a
@@ -880,131 +835,6 @@ private void PropertyChanging()
880835
this.IsDirty = true;
881836
}
882837

883-
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml' path='docs/members[@name="SqlCommand"]/Prepare/*'/>
884-
public override void Prepare()
885-
{
886-
// Reset _pendingCancel upon entry into any Execute - used to synchronize state
887-
// between entry into Execute* API and the thread obtaining the stateObject.
888-
_pendingCancel = false;
889-
890-
SqlStatistics statistics = null;
891-
using (TryEventScope.Create("SqlCommand.Prepare | API | Object Id {0}", ObjectID))
892-
{
893-
SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlCommand.Prepare | API | Correlation | Object Id {0}, ActivityID {1}, Client Connection Id {2}", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId);
894-
try
895-
{
896-
statistics = SqlStatistics.StartTimer(Statistics);
897-
898-
// only prepare if batch with parameters
899-
if (this.IsPrepared && !this.IsDirty
900-
|| (this.CommandType == CommandType.StoredProcedure)
901-
|| ((System.Data.CommandType.Text == this.CommandType)
902-
&& (0 == GetParameterCount(_parameters))))
903-
{
904-
if (Statistics != null)
905-
{
906-
Statistics.SafeIncrement(ref Statistics._prepares);
907-
}
908-
_hiddenPrepare = false;
909-
}
910-
else
911-
{
912-
// Validate the command outside of the try/catch to avoid putting the _stateObj on error
913-
ValidateCommand(isAsync: false);
914-
915-
bool processFinallyBlock = true;
916-
try
917-
{
918-
// 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)
919-
GetStateObject();
920-
921-
// Loop through parameters ensuring that we do not have unspecified types, sizes, scales, or precisions
922-
if (_parameters != null)
923-
{
924-
int count = _parameters.Count;
925-
for (int i = 0; i < count; ++i)
926-
{
927-
_parameters[i].Prepare(this);
928-
}
929-
}
930-
931-
InternalPrepare();
932-
}
933-
catch (Exception e)
934-
{
935-
processFinallyBlock = ADP.IsCatchableExceptionType(e);
936-
throw;
937-
}
938-
finally
939-
{
940-
if (processFinallyBlock)
941-
{
942-
_hiddenPrepare = false; // The command is now officially prepared
943-
944-
ReliablePutStateObject();
945-
}
946-
}
947-
}
948-
}
949-
finally
950-
{
951-
SqlStatistics.StopTimer(statistics);
952-
}
953-
}
954-
}
955-
956-
private void InternalPrepare()
957-
{
958-
if (this.IsDirty)
959-
{
960-
Debug.Assert(_cachedMetaData == null || !_dirty, "dirty query should not have cached metadata!"); // can have cached metadata if dirty because of parameters
961-
//
962-
// someone changed the command text or the parameter schema so we must unprepare the command
963-
//
964-
this.Unprepare();
965-
this.IsDirty = false;
966-
}
967-
Debug.Assert(_execType != EXECTYPE.PREPARED, "Invalid attempt to Prepare already Prepared command!");
968-
Debug.Assert(_activeConnection != null, "must have an open connection to Prepare");
969-
Debug.Assert(_stateObj != null, "TdsParserStateObject should not be null");
970-
Debug.Assert(_stateObj.Parser != null, "TdsParser class should not be null in Command.Execute!");
971-
Debug.Assert(_stateObj.Parser == _activeConnection.Parser, "stateobject parser not same as connection parser");
972-
Debug.Assert(false == _inPrepare, "Already in Prepare cycle, this.inPrepare should be false!");
973-
974-
// remember that the user wants to do a prepare but don't actually do an rpc
975-
_execType = EXECTYPE.PREPAREPENDING;
976-
// Note the current close count of the connection - this will tell us if the connection has been closed between calls to Prepare() and Execute
977-
_preparedConnectionCloseCount = _activeConnection.CloseCount;
978-
_preparedConnectionReconnectCount = _activeConnection.ReconnectCount;
979-
980-
if (Statistics != null)
981-
{
982-
Statistics.SafeIncrement(ref Statistics._prepares);
983-
}
984-
}
985-
986-
// SqlInternalConnectionTds needs to be able to unprepare a statement
987-
internal void Unprepare()
988-
{
989-
Debug.Assert(true == IsPrepared, "Invalid attempt to Unprepare a non-prepared command!");
990-
Debug.Assert(_activeConnection != null, "must have an open connection to UnPrepare");
991-
Debug.Assert(false == _inPrepare, "_inPrepare should be false!");
992-
_execType = EXECTYPE.PREPAREPENDING;
993-
994-
SqlClientEventSource.Log.TryTraceEvent("SqlCommand.UnPrepare | Info | Object Id {0}, Current Prepared Handle {1}", ObjectID, _prepareHandle);
995-
996-
// Don't zero out the handle because we'll pass it in to sp_prepexec on the next prepare
997-
// Unless the close count isn't the same as when we last prepared
998-
if ((_activeConnection.CloseCount != _preparedConnectionCloseCount) || (_activeConnection.ReconnectCount != _preparedConnectionReconnectCount))
999-
{
1000-
// reset our handle
1001-
_prepareHandle = s_cachedInvalidPrepareHandle;
1002-
}
1003-
1004-
_cachedMetaData = null;
1005-
SqlClientEventSource.Log.TryTraceEvent("SqlCommand.UnPrepare | Info | Object Id {0}, Command unprepared.", ObjectID);
1006-
}
1007-
1008838
// Cancel is supposed to be multi-thread safe.
1009839
// It doesn't make sense to verify the connection exists or that it is open during cancel
1010840
// because immediately after checking the connection can be closed or removed via another thread.
@@ -6761,48 +6591,6 @@ internal void OnConnectionClosed()
67616591
}
67626592
}
67636593

6764-
internal TdsParserStateObject StateObject
6765-
{
6766-
get
6767-
{
6768-
return _stateObj;
6769-
}
6770-
}
6771-
6772-
private bool IsPrepared
6773-
{
6774-
get { return (_execType != EXECTYPE.UNPREPARED); }
6775-
}
6776-
6777-
private bool IsUserPrepared
6778-
{
6779-
get { return IsPrepared && !_hiddenPrepare && !IsDirty; }
6780-
}
6781-
6782-
internal bool IsDirty
6783-
{
6784-
get
6785-
{
6786-
// only dirty if prepared
6787-
var activeConnection = _activeConnection;
6788-
return (IsPrepared &&
6789-
(_dirty ||
6790-
((_parameters != null) && (_parameters.IsDirty)) ||
6791-
((activeConnection != null) && ((activeConnection.CloseCount != _preparedConnectionCloseCount) || (activeConnection.ReconnectCount != _preparedConnectionReconnectCount)))));
6792-
}
6793-
set
6794-
{
6795-
// only mark the command as dirty if it is already prepared
6796-
// but always clear the value if it we are clearing the dirty flag
6797-
_dirty = value ? IsPrepared : false;
6798-
if (_parameters != null)
6799-
{
6800-
_parameters.IsDirty = _dirty;
6801-
}
6802-
_cachedMetaData = null;
6803-
}
6804-
}
6805-
68066594
/// <summary>
68076595
/// Get or add to the number of records affected by SpDescribeParameterEncryption.
68086596
/// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise.

src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,9 @@
741741
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs">
742742
<Link>Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs</Link>
743743
</Compile>
744+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlCommand.cs">
745+
<Link>Microsoft\Data\SqlClient\SqlCommand.cs</Link>
746+
</Compile>
744747
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlCommandBuilder.cs">
745748
<Link>Microsoft\Data\SqlClient\SqlCommandBuilder.cs</Link>
746749
</Compile>
@@ -976,7 +979,7 @@
976979
<Link>System\Runtime\CompilerServices\IsExternalInit.netfx.cs</Link>
977980
</Compile>
978981

979-
<Compile Include="Microsoft\Data\SqlClient\SqlCommand.cs" />
982+
<Compile Include="Microsoft\Data\SqlClient\SqlCommand.netfx.cs" />
980983
<Compile Include="Microsoft\Data\SqlClient\SqlConnection.cs" />
981984
<Compile Include="Microsoft\Data\SqlClient\SqlConnectionHelper.cs" />
982985
<Compile Include="Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs" />

0 commit comments

Comments
 (0)