Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
141 changes: 141 additions & 0 deletions doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<docs>
<members name="SqlClientApp">
<SqlClientApp>
<summary>
Specifies the known application identifiers that Microsoft.Data.SqlClient reports for user agent telemetry.
</summary>
<remarks>
<para>
Production applications that meet the bar are welcome to reserve an identifier here.
</para>
<para>
Identifier reservations are as follows:
</para>
<list type="bullet">
<item>
<description>0x0001-0x7FFF: Microsoft-defined large-scale applications.</description>
</item>
<item>
<description>0x8000-0xBFFF: Reserved for small-scale use.</description>
</item>
<item>
<description>0xC000-0xFFFF: Public and developer use.</description>
</item>
</list>
<para>
An unregistered identifier may still be reported by casting a value to this type. Identifiers are limited to
the 16-bit space the protocol allows, so a value outside 0x0000 to 0xFFFF is rejected when it is assigned to

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We only need the first sentence here. The compiler won't allow values larger than ushort once we set that as the enum's underlying type. It won't be possible to specify larger values.

<see cref="P:Microsoft.Data.SqlClient.SqlConnection.SqlClientAppId" />.
</para>
</remarks>
</SqlClientApp>
<Unknown>
<summary>
No application identity is reported. This is the default.
</summary>
<value>
0
</value>
</Unknown>
<EntityFramework>
<summary>
The Microsoft Entity Framework Core SQL Server provider.
</summary>
<value>
1
</value>
</EntityFramework>
<SemanticKernel>
<summary>
Microsoft Semantic Kernel.
</summary>
<value>
2
</value>
</SemanticKernel>
<ManagementStudio>
<summary>
Microsoft SQL Server Management Studio.
</summary>
<value>
3
</value>
</ManagementStudio>
<SqlManagementObjects>
<summary>
Microsoft SQL Server Management Objects.
</summary>
<value>
4
</value>
</SqlManagementObjects>
<DataTierApplicationFramework>
<summary>
Microsoft SQL Server Data-Tier Application Framework.
</summary>
<value>
5
</value>
</DataTierApplicationFramework>
<SqlToolsService>
<summary>
Microsoft SQL Tools Service.
</summary>
<value>
6
</value>
</SqlToolsService>
<AspNetCoreDistributedSqlServerCache>
<summary>
Microsoft ASP.NET Core distributed SQL Server cache.
</summary>
<value>
7
</value>
</AspNetCoreDistributedSqlServerCache>
<EntityFramework6>
<summary>
Microsoft Entity Framework 6 SQL Server provider.
</summary>
<value>
8
</value>
</EntityFramework6>
<AzureFunctionsSqlExtension>
<summary>
Microsoft Azure Functions SQL extension.
</summary>
<value>
9
</value>
</AzureFunctionsSqlExtension>
<OrleansAdoNet>
<summary>
Microsoft Orleans ADO.NET providers.
</summary>
<value>
10
</value>
</OrleansAdoNet>
<DurableTaskSqlServer>
<summary>
Microsoft Durable Task SQL Server provider.
</summary>
<value>
11
</value>
</DurableTaskSqlServer>
<SqlPackage>
<summary>
The <c>sqlpackage</c> command-line tool.
</summary>
<value>
12
</value>
<remarks>
<c>sqlpackage</c> is built on the Data-Tier Application Framework, but reports its own identifier so that
command-line use can be told apart from other callers of that framework.
</remarks>
</SqlPackage>
</members>
</docs>
43 changes: 43 additions & 0 deletions doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2294,6 +2294,49 @@ The following sample tries to open a connection to an invalid database to simula
</para>
</remarks>
</System.ICloneable.Clone>
<SqlClientAppId>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We don't need the Id suffix here. C# property names can be the same as their return value name.

<summary>
Gets or sets the middleware application identity reported to the server for this connection.
</summary>
<value>
A <see cref="T:Microsoft.Data.SqlClient.SqlClientApp" /> value. The default is
<see cref="F:Microsoft.Data.SqlClient.SqlClientApp.Unknown" />.
</value>
<example>
<para>
Set the identity before opening the connection:
</para>
<code language="c#">
using Microsoft.Data.SqlClient;

using var connection = new SqlConnection(connectionString);
connection.SqlClientAppId = SqlClientApp.EntityFramework;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should our example use modern property initialization syntax?

using SqlConnection connection = new(connectionString)
{
  SqlClientAppId = SqlClientApp.EntityFramework;
}

connection.Open();
</code>
</example>
<exception cref="T:System.ArgumentOutOfRangeException">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This property won't throw out-of-range.

The value is outside the range 0 to 65535.
</exception>
Comment thread
cheenamalhotra marked this conversation as resolved.
<remarks>
<para>
This API is intended for registered applications that reserve an identifier in
<see cref="T:Microsoft.Data.SqlClient.SqlClientApp" />. An unregistered identifier may be reported by casting
a value to that type, provided it is within the 16-bit range the protocol allows.
</para>
<para>
The identity is sent once, during login, so it must be set before the connection is opened.
</para>
<para>
When pooling is enabled the value is reported only while establishing a new physical connection, and it is
not part of the pool key. A connection served from the pool therefore reports the identity of whichever
connection caused that physical connection to be created, and physical connections opened in the background
to satisfy <c>Min Pool Size</c> report
<see cref="F:Microsoft.Data.SqlClient.SqlClientApp.Unknown" />. Applications that mix identities over one
connection string should treat this telemetry as indicative rather than exact, or disable pooling where an
exact attribution is required.
</para>
</remarks>
</SqlClientAppId>
<WorkstationId>
<summary>
Gets a string that identifies the database client.
Expand Down
33 changes: 33 additions & 0 deletions src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,37 @@ public void LogError(string type, string method, string message) { }
public void LogInfo(string type, string method, string message) { }
}

/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlClientApp/*' />
public enum SqlClientApp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Set the underlying type to ushort.

Applies here and in src/

{
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/Unknown/*' />
Unknown = 0x0000,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think using decimal integers would be more readable and greppable.

Applies here and in src/

/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/EntityFramework/*' />
EntityFramework = 0x0001,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SemanticKernel/*' />
SemanticKernel = 0x0002,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/ManagementStudio/*' />
ManagementStudio = 0x0003,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlManagementObjects/*' />
SqlManagementObjects = 0x0004,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/DataTierApplicationFramework/*' />
DataTierApplicationFramework = 0x0005,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlToolsService/*' />
SqlToolsService = 0x0006,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/AspNetCoreDistributedSqlServerCache/*' />
AspNetCoreDistributedSqlServerCache = 0x0007,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/EntityFramework6/*' />
EntityFramework6 = 0x0008,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/AzureFunctionsSqlExtension/*' />
AzureFunctionsSqlExtension = 0x0009,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/OrleansAdoNet/*' />
OrleansAdoNet = 0x000A,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/DurableTaskSqlServer/*' />
DurableTaskSqlServer = 0x000B,
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlPackage/*' />
SqlPackage = 0x000C
}

/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientMetaDataCollectionNames.xml' path='docs/members[@name="SqlClientMetaDataCollectionNames"]/SqlClientMetaDataCollectionNames/*'/>
public static class SqlClientMetaDataCollectionNames
{
Expand Down Expand Up @@ -1010,6 +1041,8 @@ public SqlConnection() { }
public SqlConnection(string connectionString) { }
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/ctorConnectionStringCredential/*'/>
public SqlConnection(string connectionString, Microsoft.Data.SqlClient.SqlCredential credential) { }
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/SqlClientAppId/*' />
public Microsoft.Data.SqlClient.SqlClientApp SqlClientAppId { get { throw null; } set { } }

/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/AccessToken/*'/>
[System.ComponentModel.BrowsableAttribute(false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable

private bool _sessionRecoveryRequested;

/// <summary>
/// The middleware application identity of the <see cref="SqlConnection"/> that caused
/// this physical connection to be created. Reported once, at login.
/// </summary>
private readonly SqlClientApp _sqlClientAppId;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think we need the Id suffix anywhere.


private int _threadIdOwningParserLock = -1;

// @TODO: Rename to indicate this has to do with routing
Expand Down Expand Up @@ -344,12 +350,14 @@ internal SqlConnectionInternal(
IDbConnectionPool pool = null,
Func<SqlAuthenticationParameters, CancellationToken, Task<SqlAuthenticationToken>> accessTokenCallback = null,
SspiContextProvider sspiContextProvider = null,
ISqlClientMetrics metrics = null)
ISqlClientMetrics metrics = null,
SqlClientApp sqlClientAppId = SqlClientApp.Unknown)
: base(metrics)
{
Debug.Assert(connectionOptions is not null, "null connectionOptions");

ConnectionOptions = connectionOptions;
_sqlClientAppId = sqlClientAppId;

#if DEBUG
if (reconnectSessionData != null)
Expand Down Expand Up @@ -3063,6 +3071,7 @@ private void Login(
login.password = ConnectionOptions.Password;
login.applicationName = ConnectionOptions.ApplicationName;
login.language = _currentLanguage;
login.appId = _sqlClientAppId;

if (!login.userInstance)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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.

#nullable enable

namespace Microsoft.Data.SqlClient;

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlClientApp/*' />
public enum SqlClientApp
{
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/Unknown/*' />
Unknown = 0x0000,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/EntityFramework/*' />
EntityFramework = 0x0001,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SemanticKernel/*' />
SemanticKernel = 0x0002,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/ManagementStudio/*' />
ManagementStudio = 0x0003,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlManagementObjects/*' />
SqlManagementObjects = 0x0004,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/DataTierApplicationFramework/*' />
DataTierApplicationFramework = 0x0005,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlToolsService/*' />
SqlToolsService = 0x0006,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/AspNetCoreDistributedSqlServerCache/*' />
AspNetCoreDistributedSqlServerCache = 0x0007,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/EntityFramework6/*' />
EntityFramework6 = 0x0008,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/AzureFunctionsSqlExtension/*' />
AzureFunctionsSqlExtension = 0x0009,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/OrleansAdoNet/*' />
OrleansAdoNet = 0x000A,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/DurableTaskSqlServer/*' />
DurableTaskSqlServer = 0x000B,
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlPackage/*' />
SqlPackage = 0x000C
Comment thread
cheenamalhotra marked this conversation as resolved.
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing tests.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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;

#nullable enable

namespace Microsoft.Data.SqlClient;

/// <summary>
/// Driver-owned feature flags reported in the Driver Properties part of the
/// USERAGENT login feature extension payload.
/// </summary>
/// <remarks>
/// This part is driver-owned, so its meaning is defined entirely by
/// Microsoft.Data.SqlClient and carries no cross-driver contract. Other
/// drivers use the same part for their own purposes.
/// </remarks>
[Flags]
internal enum SqlClientDriverProperties : ushort

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggest ulong to give us room for 64 flags.

{
/// <summary>No tracked features are enabled.</summary>
None = 0x0000,

/// <summary>
/// The connection pool V2 implementation
/// (<c>Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2</c>) is
/// enabled.
/// </summary>
ConnectionPoolV2 = 0x0001
Comment thread
cheenamalhotra marked this conversation as resolved.
}

/// <summary>
/// Resolves the <see cref="SqlClientDriverProperties"/> flags that describe
/// how this process is configured.
/// </summary>
internal static class SqlClientDriverPropertiesResolver
{
/// <summary>
/// The flags describing the current process.
/// </summary>
/// <remarks>
/// The flags are sourced from process-wide switches, so this is stable
/// for the life of the process.
/// </remarks>
internal static SqlClientDriverProperties Current =>
Resolve(LocalAppContextSwitches.UseConnectionPoolV2);

/// <summary>
/// Maps the process configuration to the flags that describe it.
/// </summary>
/// <param name="useConnectionPoolV2">
/// Whether the connection pool V2 implementation is enabled.
/// </param>
/// <returns>
/// The flags describing the supplied configuration.
/// </returns>
/// <remarks>
/// The mapping is kept separate from <see cref="Current"/> because the
/// switches it reads are cached for the life of the process, which makes
/// them impractical to vary in a test.
/// </remarks>
internal static SqlClientDriverProperties Resolve(bool useConnectionPoolV2)
{
SqlClientDriverProperties properties = SqlClientDriverProperties.None;

if (useConnectionPoolV2)
{
properties |= SqlClientDriverProperties.ConnectionPoolV2;
}

return properties;
}
}
Loading
Loading