Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
66c2cd6
Change all possible references from SqlInternalConnection to SqlInter…
benrr101 Nov 12, 2025
2d664a6
Move BeginTransaction and BeginSqlTransaction into SqlInternalConnect…
benrr101 Nov 12, 2025
c796212
Move ChangeDatabase into SqlInternalConnectionTds, move ChangeDatabas…
benrr101 Nov 12, 2025
13c373c
Move EnlistTransaction to SqlInternalConnectionTds
benrr101 Nov 12, 2025
a176e2f
Remove abstract ValidateConnectionForExecute
benrr101 Nov 12, 2025
c5863e3
Move Enlist into SqlInternalConnectionTds
benrr101 Nov 12, 2025
8002ec7
Move EnlistNonNull into SqlInternalConnectionTds
benrr101 Nov 12, 2025
202beb5
Move EnlistNull into SqlInternalConnectionTds
benrr101 Nov 12, 2025
bb3d541
Removed no longer necessary abstractions in SqlInternalConnection
benrr101 Nov 12, 2025
4d39842
Move OnError into SqlInternalConnectionTds
benrr101 Nov 13, 2025
58c358f
Move Deactivate into SqlInternalConnectionTds, merge InternalDeactiva…
benrr101 Nov 13, 2025
07750f8
Move GetTransactionCookie into SqlInternalConnectionTds
benrr101 Nov 13, 2025
dbe9d9e
Move FindLiveReader into SqlInternalConnectionTds
benrr101 Nov 13, 2025
355f2d2
Merge SqlInternalConnection.Dispose into SqlInternalConnectionTds
benrr101 Nov 13, 2025
74b0c92
Move CleanupTransactionOnCompletion, CreateReferenceCollection into S…
benrr101 Nov 13, 2025
ce46515
Introduce a CachedContexts class, move the cached call contexts from …
benrr101 Nov 13, 2025
79cf052
Migrate usages of CachedCommandExecuteNonQueryAsyncContext to the new…
benrr101 Nov 13, 2025
0a45c97
Migrate usages of CachedCommandExecuteXmlReaderAsyncContext to the ne…
benrr101 Nov 13, 2025
7b5a637
Migrate usage of ReadAsyncCallContext to the new class
benrr101 Nov 13, 2025
ba4c7df
Migrate usage of IsDBNullAsyncCallContext to the new class.
benrr101 Nov 13, 2025
a0a1f24
Comments on CachedContexts
benrr101 Nov 13, 2025
41b7f2a
Sure why not move the reader snapshot into the CachedContexts class? …
benrr101 Nov 13, 2025
1b52999
Move _whereAbouts and s_globalTransactionTMID
benrr101 Nov 13, 2025
8fb7b0e
Merge constructors
benrr101 Nov 13, 2025
0e78a6e
Merge AvailableInternalTransaction, CurrentTransaction, HasLocalTrans…
benrr101 Nov 13, 2025
858d98e
Merge Connection, ConnectionOptions, CurrentDatabase, CurrentDataSour…
benrr101 Nov 13, 2025
6dfbd5d
Remove SqlInternalConnection
benrr101 Nov 13, 2025
a83bd7c
Move SqlInternalConnectionTds.cs to SqlConnectionInternal.cs
benrr101 Nov 13, 2025
c4db055
Renane Microsoft.Data.SqlClient.SqlInternalConnectionTds to Microsoft…
benrr101 Nov 13, 2025
d010d48
Fix one reference to SqlInternalConnectionTds in unit tests
benrr101 Nov 14, 2025
4d02cc2
As per copilot comment, rewriting a block in SqlConnectionFactory to …
benrr101 Nov 18, 2025
04e5cdf
As per copilot comment, adding more comments to CachedContexts class
benrr101 Nov 18, 2025
64766f4
Addressing some feedback from @mdiagle
benrr101 Dec 1, 2025
89b92b9
Merge branch 'main' into dev/russellben/flatten/sqlinternalconnection
benrr101 Dec 2, 2025
3b795ae
Rename Clear*Context to Take*Context
benrr101 Dec 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs">
<Link>Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\CachedContexts.cs">
<Link>Microsoft\Data\SqlClient\Connection\CachedContexts.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\ServerInfo.cs">
<Link>Microsoft\Data\SqlClient\Connection\ServerInfo.cs</Link>
</Compile>
Expand All @@ -219,6 +222,9 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs">
<Link>Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs">
<Link>Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs">
<Link>Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs</Link>
</Compile>
Expand Down Expand Up @@ -699,12 +705,6 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs">
<Link>Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalConnection.cs">
<Link>Microsoft\Data\SqlClient\SqlInternalConnection.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs">
<Link>Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalTransaction.cs">
<Link>Microsoft\Data\SqlClient\SqlInternalTransaction.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,9 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs">
<Link>Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\CachedContexts.cs">
<Link>Microsoft\Data\SqlClient\Connection\CachedContexts.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\ServerInfo.cs">
<Link>Microsoft\Data\SqlClient\Connection\ServerInfo.cs</Link>
</Compile>
Expand All @@ -488,6 +491,9 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs">
<Link>Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs">
<Link>Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs">
<Link>Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs</Link>
</Compile>
Expand Down Expand Up @@ -863,12 +869,6 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs">
<Link>Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalConnection.cs">
<Link>Microsoft\Data\SqlClient\SqlInternalConnection.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs">
<Link>Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SqlInternalTransaction.cs">
<Link>Microsoft\Data\SqlClient\SqlInternalTransaction.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Microsoft.SqlServer.Server;
using System.Security.Authentication;
using System.Collections.Generic;
using Microsoft.Data.SqlClient.Connection;

#if NETFRAMEWORK
using System.Reflection;
Expand Down Expand Up @@ -465,7 +466,11 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int

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

internal static Exception CreateSqlException(MsalException msalException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username)
internal static Exception CreateSqlException(
MsalException msalException,
SqlConnectionString connectionOptions,
SqlConnectionInternal sender,
string username)
{
// Error[0]
SqlErrorCollection sqlErs = new();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// 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.Threading;

#nullable enable

namespace Microsoft.Data.SqlClient.Connection
{
/// <summary>
/// Provides cached asynchronous call contexts shared between objects in a connection's context.
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Missing XML documentation for the CachedContexts class. As a newly introduced internal class with public-facing methods, it should have a summary describing its purpose and usage pattern, especially since it deals with thread-safe context caching using Interlocked operations.

Suggested change
/// Provides cached asynchronous call contexts shared between objects in a connection's context.
/// Provides thread-safe caching and sharing of asynchronous call contexts between objects within a single SQL connection context.
/// <para>
/// This internal class manages reusable context objects for various asynchronous operations (such as ExecuteNonQueryAsync, ExecuteReaderAsync, etc.)
/// performed on a connection, enabling efficient reuse and reducing allocations.
/// </para>
/// <para>
/// Thread safety is ensured via atomic operations using <see cref="Interlocked"/> methods, allowing concurrent access and updates
/// without explicit locking. All accessors and mutators are designed to be safe for use by multiple threads.
/// </para>
/// <para>
/// Intended for internal use by connection management infrastructure; not part of the public API.
/// </para>

Copilot uses AI. Check for mistakes.
/// </summary>
internal class CachedContexts
{
#region Fields

/// <summary>
/// Stores reusable context for ExecuteNonQueryAsync invocations.
/// </summary>
private SqlCommand.ExecuteNonQueryAsyncCallContext? _commandExecuteNonQueryAsyncContext;

/// <summary>
/// Stores reusable context for ExecuteReaderAsync invocations.
/// </summary>
private SqlCommand.ExecuteReaderAsyncCallContext? _commandExecuteReaderAsyncContext;

/// <summary>
/// Stores reusable context for ExecuteXmlReaderAsync invocations.
/// </summary>
private SqlCommand.ExecuteXmlReaderAsyncCallContext? _commandExecuteXmlReaderAsyncContext;

/// <summary>
/// Stores reusable context for IsDBNullAsync invocations.
/// </summary>
private SqlDataReader.IsDBNullAsyncCallContext? _dataReaderIsDbNullContext;

/// <summary>
/// Stores reusable context for ReadAsync invocations.
/// </summary>
private SqlDataReader.ReadAsyncCallContext? _dataReaderReadAsyncContext;

/// <summary>
/// Stores a data reader snapshot.
/// </summary>
private SqlDataReader.Snapshot? _dataReaderSnapshot;

#endregion

#region Access Methods

/// <summary>
/// Removes and returns the cached ExecuteNonQueryAsync context.
/// </summary>
/// <returns>The previously cached context or null when empty.</returns>
internal SqlCommand.ExecuteNonQueryAsyncCallContext? ClearCommandExecuteNonQueryAsyncContext() =>
Interlocked.Exchange(ref _commandExecuteNonQueryAsyncContext, null);

/// <summary>
/// Removes and returns the cached ExecuteReaderAsync context.
/// </summary>
/// <returns>The previously cached context or null when empty.</returns>
internal SqlCommand.ExecuteReaderAsyncCallContext? ClearCommandExecuteReaderAsyncContext() =>
Interlocked.Exchange(ref _commandExecuteReaderAsyncContext, null);

/// <summary>
/// Removes and returns the cached ExecuteXmlReaderAsync context.
/// </summary>
/// <returns>The previously cached context or null when empty.</returns>
internal SqlCommand.ExecuteXmlReaderAsyncCallContext? ClearCommandExecuteXmlReaderAsyncContext() =>
Interlocked.Exchange(ref _commandExecuteXmlReaderAsyncContext, null);

/// <summary>
/// Removes and returns the cached ReadAsync context.
/// </summary>
/// <returns>The previously cached context or null when empty.</returns>
internal SqlDataReader.ReadAsyncCallContext? ClearDataReaderReadAsyncContext() =>
Interlocked.Exchange(ref _dataReaderReadAsyncContext, null);

/// <summary>
/// Removes and returns the cached IsDBNullAsync context.
/// </summary>
/// <returns>The previously cached context or null when empty.</returns>
internal SqlDataReader.IsDBNullAsyncCallContext? ClearDataReaderIsDbNullContext() =>
Interlocked.Exchange(ref _dataReaderIsDbNullContext, null);

/// <summary>
/// Removes and returns the cached data reader snapshot.
/// </summary>
/// <returns>The previously cached snapshot or null when empty.</returns>
internal SqlDataReader.Snapshot? ClearDataReaderSnapshot() =>
Interlocked.Exchange(ref _dataReaderSnapshot, null);

/// <summary>
/// Attempts to cache the provided ExecuteNonQueryAsync context.
/// </summary>
/// <param name="value">Context instance to store.</param>
/// <returns>
/// True when the context is cached; false if an existing value is preserved.
/// </returns>
internal bool TrySetCommandExecuteNonQueryAsyncContext(SqlCommand.ExecuteNonQueryAsyncCallContext value) =>
TrySetContext(value, ref _commandExecuteNonQueryAsyncContext);

/// <summary>
/// Attempts to cache the provided ExecuteReaderAsync context.
/// </summary>
/// <param name="value">Context instance to store.</param>
/// <returns>
/// True when the context is cached; false if an existing value is preserved.
/// </returns>
internal bool TrySetCommandExecuteReaderAsyncContext(SqlCommand.ExecuteReaderAsyncCallContext value) =>
TrySetContext(value, ref _commandExecuteReaderAsyncContext);

/// <summary>
/// Attempts to cache the provided ExecuteXmlReaderAsync context.
/// </summary>
/// <param name="value">Context instance to store.</param>
/// <returns>
/// True when the context is cached; false if an existing value is preserved.
/// </returns>
internal bool TrySetCommandExecuteXmlReaderAsyncContext(SqlCommand.ExecuteXmlReaderAsyncCallContext value) =>
TrySetContext(value, ref _commandExecuteXmlReaderAsyncContext);

/// <summary>
/// Attempts to cache the provided ReadAsync context.
/// </summary>
/// <param name="value">Context instance to store.</param>
/// <returns>
/// True when the context is cached; false if an existing value is preserved.
/// </returns>
internal bool TrySetDataReaderReadAsyncContext(SqlDataReader.ReadAsyncCallContext value) =>
TrySetContext(value, ref _dataReaderReadAsyncContext);

/// <summary>
/// Attempts to cache the provided IsDBNullAsync context.
/// </summary>
/// <param name="value">Context instance to store.</param>
/// <returns>
/// True when the context is cached; false if an existing value is preserved.
/// </returns>
internal bool TrySetDataReaderIsDbNullContext(SqlDataReader.IsDBNullAsyncCallContext value) =>
TrySetContext(value, ref _dataReaderIsDbNullContext);

/// <summary>
/// Attempts to cache the provided data reader snapshot context.
/// </summary>
/// <param name="value">Context instance to store.</param>
/// <returns>
/// True when the snapshot is cached; false if an existing snapshot is preserved.
/// </returns>
internal bool TrySetDataReaderSnapshot(SqlDataReader.Snapshot value) =>
TrySetContext(value, ref _dataReaderSnapshot);

#endregion

private static bool TrySetContext<TContext>(TContext value, ref TContext? location)
where TContext : class
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}

return Interlocked.CompareExchange(ref location, value, null) is null;
}
Comment on lines +169 to +178
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TrySetContext method throws ArgumentNullException when value is null. However, the public-facing methods that call this helper (e.g., TrySetCommandExecuteNonQueryAsyncContext, TrySetDataReaderSnapshot) don't document this behavior. Consider adding XML documentation to these methods indicating that they will throw ArgumentNullException if a null value is provided, or handle the null case gracefully by returning false instead of throwing.

Copilot uses AI. Check for mistakes.
}
}
Loading
Loading