Skip to content

Commit 3895d69

Browse files
authored
Flatten | SqlInternalConnectionTds and SqlInternalConnection (dotnet#3773)
* Change all possible references from SqlInternalConnection to SqlInternalConnectionTds * Move BeginTransaction and BeginSqlTransaction into SqlInternalConnectionTds.cs * Move ChangeDatabase into SqlInternalConnectionTds, move ChangeDatabaseInternal into ChangeDatabase, remove abstract ChangeDatabaseInternal * Move EnlistTransaction to SqlInternalConnectionTds * Remove abstract ValidateConnectionForExecute * Move Enlist into SqlInternalConnectionTds * Move EnlistNonNull into SqlInternalConnectionTds * Move EnlistNull into SqlInternalConnectionTds * Removed no longer necessary abstractions in SqlInternalConnection * Move OnError into SqlInternalConnectionTds * Move Deactivate into SqlInternalConnectionTds, merge InternalDeactivate into Deactivate * Move GetTransactionCookie into SqlInternalConnectionTds * Move FindLiveReader into SqlInternalConnectionTds * Merge SqlInternalConnection.Dispose into SqlInternalConnectionTds * Move CleanupTransactionOnCompletion, CreateReferenceCollection into SqlInternalConnectionTds * Introduce a CachedContexts class, move the cached call contexts from SqlInternalConnection into that. Migrate usages of ExecuteReaderAsyncCallContext to the new class. * Migrate usages of CachedCommandExecuteNonQueryAsyncContext to the new class. * Migrate usages of CachedCommandExecuteXmlReaderAsyncContext to the new class * Migrate usage of ReadAsyncCallContext to the new class * Migrate usage of IsDBNullAsyncCallContext to the new class. * Comments on CachedContexts * Sure why not move the reader snapshot into the CachedContexts class? the usages of it are basically doing the same thing * Move _whereAbouts and s_globalTransactionTMID * Merge constructors * Merge AvailableInternalTransaction, CurrentTransaction, HasLocalTransaction, HasLocalTransactionFromAPI * Merge Connection, ConnectionOptions, CurrentDatabase, CurrentDataSource, DelegatedTransaction, Is2008OrNewer, IsAzureSqlConnection, IsEnlistedInTransaction, IsGlobalTransaction, IsGlobalTransactionEnabledForServer, IsLockedForBulkCopy, IsTransactionRoot, PromotedDtcToken * Remove SqlInternalConnection * Move SqlInternalConnectionTds.cs to SqlConnectionInternal.cs * Renane Microsoft.Data.SqlClient.SqlInternalConnectionTds to Microsoft.Data.SqlClient.Connection.SqlInternalConnectionTds * Fix one reference to SqlInternalConnectionTds in unit tests * As per copilot comment, rewriting a block in SqlConnectionFactory to use a using block. Also removing redundant if statement condition. * As per copilot comment, adding more comments to CachedContexts class * Addressing some feedback from @mdiagle * Rename Clear*Context to Take*Context * Reorder and fix reflection objects in ConnectionHelper * Fixing null set issue in SqlDataReader?
1 parent 3608731 commit 3895d69

23 files changed

Lines changed: 1075 additions & 936 deletions

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@
269269
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs">
270270
<Link>Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs</Link>
271271
</Compile>
272+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\CachedContexts.cs">
273+
<Link>Microsoft\Data\SqlClient\Connection\CachedContexts.cs</Link>
274+
</Compile>
272275
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\ServerInfo.cs">
273276
<Link>Microsoft\Data\SqlClient\Connection\ServerInfo.cs</Link>
274277
</Compile>
@@ -278,6 +281,9 @@
278281
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs">
279282
<Link>Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs</Link>
280283
</Compile>
284+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs">
285+
<Link>Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs</Link>
286+
</Compile>
281287
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs">
282288
<Link>Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs</Link>
283289
</Compile>
@@ -848,12 +854,6 @@
848854
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs">
849855
<Link>Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs</Link>
850856
</Compile>
851-
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalConnection.cs">
852-
<Link>Microsoft\Data\SqlClient\SqlInternalConnection.cs</Link>
853-
</Compile>
854-
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs">
855-
<Link>Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs</Link>
856-
</Compile>
857857
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalTransaction.cs">
858858
<Link>Microsoft\Data\SqlClient\SqlInternalTransaction.cs</Link>
859859
</Compile>

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@
339339
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs">
340340
<Link>Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs</Link>
341341
</Compile>
342+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\CachedContexts.cs">
343+
<Link>Microsoft\Data\SqlClient\Connection\CachedContexts.cs</Link>
344+
</Compile>
342345
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\ServerInfo.cs">
343346
<Link>Microsoft\Data\SqlClient\Connection\ServerInfo.cs</Link>
344347
</Compile>
@@ -348,6 +351,9 @@
348351
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs">
349352
<Link>Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs</Link>
350353
</Compile>
354+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs">
355+
<Link>Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs</Link>
356+
</Compile>
351357
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs">
352358
<Link>Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs</Link>
353359
</Compile>
@@ -831,12 +837,6 @@
831837
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs">
832838
<Link>Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs</Link>
833839
</Compile>
834-
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalConnection.cs">
835-
<Link>Microsoft\Data\SqlClient\SqlInternalConnection.cs</Link>
836-
</Compile>
837-
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs">
838-
<Link>Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs</Link>
839-
</Compile>
840840
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalTransaction.cs">
841841
<Link>Microsoft\Data\SqlClient\SqlInternalTransaction.cs</Link>
842842
</Compile>

src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Transactions;
2121
using Microsoft.Data.Common.ConnectionString;
2222
using Microsoft.Data.SqlClient;
23+
using Microsoft.Data.SqlClient.Connection;
2324
using Microsoft.Identity.Client;
2425
using Microsoft.SqlServer.Server;
2526
using IsolationLevel = System.Data.IsolationLevel;
@@ -500,7 +501,11 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int
500501

501502
internal static ArgumentException MustBeReadOnly(string argumentName) => Argument(StringsHelper.GetString(Strings.ADP_MustBeReadOnly, argumentName));
502503

503-
internal static Exception CreateSqlException(MsalException msalException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username)
504+
internal static Exception CreateSqlException(
505+
MsalException msalException,
506+
SqlConnectionString connectionOptions,
507+
SqlConnectionInternal sender,
508+
string username)
504509
{
505510
// Error[0]
506511
SqlErrorCollection sqlErs = new();
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Threading;
7+
8+
#nullable enable
9+
10+
namespace Microsoft.Data.SqlClient.Connection
11+
{
12+
/// <summary>
13+
/// Provides thread-safe caching and sharing of asynchronous call contexts between objects
14+
/// within a single SQL connection context.
15+
/// </summary>
16+
/// <remarks>
17+
/// This internal class manages reusable context objects for various asynchronous operations
18+
/// such as ExecuteNonQueryAsync, ExecuteReaderAsync, etc, performed on a connection, enabling
19+
/// efficient reuse and reducing allocations.
20+
///
21+
/// Thread safety is ensured via interlocked operations, allowing concurrent access and
22+
/// updates without explicit locking. All accessors and mutators are designed to be safe for
23+
/// use by multiple threads.
24+
///
25+
/// Intended for internal use by connection management infrastructure.
26+
/// </remarks>
27+
internal class CachedContexts
28+
{
29+
#region Fields
30+
31+
/// <summary>
32+
/// Stores reusable context for ExecuteNonQueryAsync invocations.
33+
/// </summary>
34+
private SqlCommand.ExecuteNonQueryAsyncCallContext? _commandExecuteNonQueryAsyncContext;
35+
36+
/// <summary>
37+
/// Stores reusable context for ExecuteReaderAsync invocations.
38+
/// </summary>
39+
private SqlCommand.ExecuteReaderAsyncCallContext? _commandExecuteReaderAsyncContext;
40+
41+
/// <summary>
42+
/// Stores reusable context for ExecuteXmlReaderAsync invocations.
43+
/// </summary>
44+
private SqlCommand.ExecuteXmlReaderAsyncCallContext? _commandExecuteXmlReaderAsyncContext;
45+
46+
/// <summary>
47+
/// Stores reusable context for IsDBNullAsync invocations.
48+
/// </summary>
49+
private SqlDataReader.IsDBNullAsyncCallContext? _dataReaderIsDbNullContext;
50+
51+
/// <summary>
52+
/// Stores reusable context for ReadAsync invocations.
53+
/// </summary>
54+
private SqlDataReader.ReadAsyncCallContext? _dataReaderReadAsyncContext;
55+
56+
/// <summary>
57+
/// Stores a data reader snapshot.
58+
/// </summary>
59+
private SqlDataReader.Snapshot? _dataReaderSnapshot;
60+
61+
#endregion
62+
63+
#region Access Methods
64+
65+
/// <summary>
66+
/// Removes and returns the cached ExecuteNonQueryAsync context.
67+
/// </summary>
68+
/// <returns>The previously cached context or null when empty.</returns>
69+
internal SqlCommand.ExecuteNonQueryAsyncCallContext? TakeCommandExecuteNonQueryAsyncContext() =>
70+
Interlocked.Exchange(ref _commandExecuteNonQueryAsyncContext, null);
71+
72+
/// <summary>
73+
/// Removes and returns the cached ExecuteReaderAsync context.
74+
/// </summary>
75+
/// <returns>The previously cached context or null when empty.</returns>
76+
internal SqlCommand.ExecuteReaderAsyncCallContext? TakeCommandExecuteReaderAsyncContext() =>
77+
Interlocked.Exchange(ref _commandExecuteReaderAsyncContext, null);
78+
79+
/// <summary>
80+
/// Removes and returns the cached ExecuteXmlReaderAsync context.
81+
/// </summary>
82+
/// <returns>The previously cached context or null when empty.</returns>
83+
internal SqlCommand.ExecuteXmlReaderAsyncCallContext? TakeCommandExecuteXmlReaderAsyncContext() =>
84+
Interlocked.Exchange(ref _commandExecuteXmlReaderAsyncContext, null);
85+
86+
/// <summary>
87+
/// Removes and returns the cached ReadAsync context.
88+
/// </summary>
89+
/// <returns>The previously cached context or null when empty.</returns>
90+
internal SqlDataReader.ReadAsyncCallContext? TakeDataReaderReadAsyncContext() =>
91+
Interlocked.Exchange(ref _dataReaderReadAsyncContext, null);
92+
93+
/// <summary>
94+
/// Removes and returns the cached IsDBNullAsync context.
95+
/// </summary>
96+
/// <returns>The previously cached context or null when empty.</returns>
97+
internal SqlDataReader.IsDBNullAsyncCallContext? TakeDataReaderIsDbNullContext() =>
98+
Interlocked.Exchange(ref _dataReaderIsDbNullContext, null);
99+
100+
/// <summary>
101+
/// Removes and returns the cached data reader snapshot.
102+
/// </summary>
103+
/// <returns>The previously cached snapshot or null when empty.</returns>
104+
internal SqlDataReader.Snapshot? TakeDataReaderSnapshot() =>
105+
Interlocked.Exchange(ref _dataReaderSnapshot, null);
106+
107+
/// <summary>
108+
/// Attempts to cache the provided ExecuteNonQueryAsync context.
109+
/// </summary>
110+
/// <param name="value">Context instance to store.</param>
111+
/// <returns>
112+
/// True when the context is cached; false if an existing value is preserved.
113+
/// </returns>
114+
internal bool TrySetCommandExecuteNonQueryAsyncContext(SqlCommand.ExecuteNonQueryAsyncCallContext value) =>
115+
TrySetContext(value, ref _commandExecuteNonQueryAsyncContext);
116+
117+
/// <summary>
118+
/// Attempts to cache the provided ExecuteReaderAsync context.
119+
/// </summary>
120+
/// <param name="value">Context instance to store.</param>
121+
/// <returns>
122+
/// True when the context is cached; false if an existing value is preserved.
123+
/// </returns>
124+
internal bool TrySetCommandExecuteReaderAsyncContext(SqlCommand.ExecuteReaderAsyncCallContext value) =>
125+
TrySetContext(value, ref _commandExecuteReaderAsyncContext);
126+
127+
/// <summary>
128+
/// Attempts to cache the provided ExecuteXmlReaderAsync context.
129+
/// </summary>
130+
/// <param name="value">Context instance to store.</param>
131+
/// <returns>
132+
/// True when the context is cached; false if an existing value is preserved.
133+
/// </returns>
134+
internal bool TrySetCommandExecuteXmlReaderAsyncContext(SqlCommand.ExecuteXmlReaderAsyncCallContext value) =>
135+
TrySetContext(value, ref _commandExecuteXmlReaderAsyncContext);
136+
137+
/// <summary>
138+
/// Attempts to cache the provided ReadAsync context.
139+
/// </summary>
140+
/// <param name="value">Context instance to store.</param>
141+
/// <returns>
142+
/// True when the context is cached; false if an existing value is preserved.
143+
/// </returns>
144+
internal bool TrySetDataReaderReadAsyncContext(SqlDataReader.ReadAsyncCallContext value) =>
145+
TrySetContext(value, ref _dataReaderReadAsyncContext);
146+
147+
/// <summary>
148+
/// Attempts to cache the provided IsDBNullAsync context.
149+
/// </summary>
150+
/// <param name="value">Context instance to store.</param>
151+
/// <returns>
152+
/// True when the context is cached; false if an existing value is preserved.
153+
/// </returns>
154+
internal bool TrySetDataReaderIsDbNullContext(SqlDataReader.IsDBNullAsyncCallContext value) =>
155+
TrySetContext(value, ref _dataReaderIsDbNullContext);
156+
157+
/// <summary>
158+
/// Attempts to cache the provided data reader snapshot context.
159+
/// </summary>
160+
/// <param name="value">Context instance to store.</param>
161+
/// <returns>
162+
/// True when the snapshot is cached; false if an existing snapshot is preserved.
163+
/// </returns>
164+
internal bool TrySetDataReaderSnapshot(SqlDataReader.Snapshot value) =>
165+
TrySetContext(value, ref _dataReaderSnapshot);
166+
167+
#endregion
168+
169+
private static bool TrySetContext<TContext>(TContext value, ref TContext? location)
170+
where TContext : class
171+
{
172+
if (value is null)
173+
{
174+
throw new ArgumentNullException(nameof(value));
175+
}
176+
177+
return Interlocked.CompareExchange(ref location, value, null) is null;
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)