Skip to content
Open
69 changes: 68 additions & 1 deletion doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1357,12 +1357,28 @@ For more information on working with events, see [Connection Events](https://lea
</FireInfoMessageEventOnUserErrors>
<GetSchema2>
<summary>
Returns schema information for the data source of this <see cref="T:Microsoft.Data.SqlClient.SqlConnection" />. For more information about scheme, see <see href="https://learn.microsoft.com/sql/connect/ado-net/sql-server-schema-collections">SQL Server Schema Collections</see>.
Returns schema information for the data source of this <see cref="T:Microsoft.Data.SqlClient.SqlConnection" />. For more information about schemas, see <see href="https://learn.microsoft.com/sql/connect/ado-net/sql-server-schema-collections">SQL Server Schema Collections</see>.
</summary>
<returns>
A <see cref="T:System.Data.DataTable" /> that contains schema information.
</returns>
</GetSchema2>
<GetSchemaAsync>
<param name="cancellationToken">
The cancellation token.
</param>
<summary>
An asynchronous version of <see cref="M:Microsoft.Data.SqlClient.SqlConnection.GetSchema()" />, which returns schema information for the data source of this <see cref="T:Microsoft.Data.SqlClient.SqlConnection" />. For more information about schemas, see <see href="https://learn.microsoft.com/sql/connect/ado-net/sql-server-schema-collections">SQL Server Schema Collections</see>.
</summary>
<returns>
A task representing the asynchronous operation.
</returns>
<remarks>
<para>
For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see <see href="https://learn.microsoft.com/sql/connect/ado-net/asynchronous-programming">Asynchronous Programming</see>.
</para>
</remarks>
</GetSchemaAsync>
<GetSchemaCollectionName>
<param name="collectionName">
Specifies the name of the schema to return.
Expand Down Expand Up @@ -1648,6 +1664,28 @@ For more information on working with events, see [Connection Events](https://lea
<paramref name="collectionName" /> is specified as null.
</exception>
</GetSchemaCollectionName>
<GetSchemaCollectionNameAsync>
<param name="collectionName">
Specifies the name of the schema to return.
</param>
<param name="cancellationToken">
The cancellation token.
</param>
<summary>
An asynchronous version of <see cref="M:Microsoft.Data.SqlClient.SqlConnection.GetSchema(string)" />, which returns schema information for the data source of this <see cref="T:Microsoft.Data.SqlClient.SqlConnection" /> using the specified string for the schema name.
</summary>
<returns>
A task representing the asynchronous operation.
</returns>
<remarks>
<para>
For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see <see href="https://learn.microsoft.com/sql/connect/ado-net/asynchronous-programming">Asynchronous Programming</see>.
</para>
</remarks>
<exception cref="T:System.ArgumentException">
<paramref name="collectionName" /> is specified as null.
</exception>
</GetSchemaCollectionNameAsync>
<GetSchemaCollectionNameRestrictionValues>
<param name="collectionName">
Specifies the name of the schema to return.
Expand Down Expand Up @@ -1677,6 +1715,35 @@ For more information on working with events, see [Connection Events](https://lea
</exception>
<seealso cref="M:Microsoft.Data.SqlClient.SqlConnection.GetSchema" />
</GetSchemaCollectionNameRestrictionValues>
<GetSchemaCollectionNameRestrictionValuesAsync>
<param name="collectionName">
Specifies the name of the schema to return.
</param>
<param name="restrictionValues">
A set of restriction values for the requested schema.
</param>
<param name="cancellationToken">
The cancellation token.
</param>
<summary>
An asynchronous version of <see cref="M:Microsoft.Data.SqlClient.SqlConnection.GetSchema(string, string[])" />, which returns schema information for the data source of this <see cref="T:Microsoft.Data.SqlClient.SqlConnection" /> using the specified string for the schema name and the specified string array for the restriction values.
</summary>
<returns>
A task representing the asynchronous operation.
</returns>
<remarks>
<para>
The <paramref name="restrictionValues" /> parameter can supply <i>n</i> depth of values, which are specified by the restrictions collection for a specific collection. In order to set values on a given restriction, and not set the values of other restrictions, you need to set the preceding restrictions to <see langword="null" /> and then put the appropriate value in for the restriction that you would like to specify a value for.
</para>
<para>
An example of this is the "Tables" collection. If the "Tables" collection has three restrictions--database, owner, and table name--and you want to get back only the tables associated with the owner "Carl", you need to pass in the following values: null, "Carl". If a restriction value is not passed in, the default values are used for that restriction. This is the same mapping as passing in <see langword="null" />, which is different from passing in an empty string for the parameter value. In that case, the empty string ("") is considered to be the value for the specified parameter.
</para>
</remarks>
<exception cref="T:System.ArgumentException">
<paramref name="collectionName" /> is specified as null.
</exception>
<seealso cref="M:Microsoft.Data.SqlClient.SqlConnection.GetSchema" />
</GetSchemaCollectionNameRestrictionValuesAsync>
<InfoMessage>
<summary>
Occurs when SQL Server returns a warning or informational message.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,21 @@ public event Microsoft.Data.SqlClient.SqlInfoMessageEventHandler InfoMessage { a
public override System.Data.DataTable GetSchema(string collectionName) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaCollectionNameRestrictionValues/*'/>
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { throw null; }
#if NET
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaAsync/*'/>
public override System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaCollectionNameAsync/*'/>
public override System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaCollectionNameRestrictionValuesAsync/*'/>
public override System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(string collectionName, string[] restrictionValues, System.Threading.CancellationToken cancellationToken = default) { throw null; }
#else
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaAsync/*'/>
public System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaCollectionNameAsync/*'/>
public System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaCollectionNameRestrictionValuesAsync/*'/>
public System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(string collectionName, string[] restrictionValues, System.Threading.CancellationToken cancellationToken = default) { throw null; }
#endif
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Open/*'/>
public override void Open() { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/OpenWithOverrides/*'/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,12 @@ public override void EnlistTransaction(System.Transactions.Transaction transacti
public override System.Data.DataTable GetSchema(string collectionName) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaCollectionNameRestrictionValues/*'/>
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaAsync/*'/>
public System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaCollectionNameAsync/*'/>
public System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/GetSchemaCollectionNameRestrictionValuesAsync/*'/>
public System.Threading.Tasks.Task<System.Data.DataTable> GetSchemaAsync(string collectionName, string[] restrictionValues, System.Threading.CancellationToken cancellationToken = default) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/Open/*'/>
public override void Open() { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/OpenWithOverrides/*'/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.Common.ConnectionString;
Expand Down Expand Up @@ -47,6 +48,15 @@ protected internal override DataTable GetSchema(
throw ADP.ClosedConnectionError();
}

protected internal override Task<DataTable> GetSchemaAsync(
SqlConnectionFactory factory,
DbConnectionPoolGroup poolGroup,
DbConnection outerConnection,
string collectionName,
string[] restrictions,
CancellationToken cancellationToken)
=> throw ADP.ClosedConnectionError();

protected override DbReferenceCollection CreateReferenceCollection() => throw ADP.ClosedConnectionError();

internal override bool TryOpenConnection(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,22 @@ protected internal virtual DataTable GetSchema(
return metaDataFactory.GetSchema(outerConnection, collectionName, restrictions);
}

protected internal virtual Task<DataTable> GetSchemaAsync(
SqlConnectionFactory factory,
DbConnectionPoolGroup poolGroup,
DbConnection outerConnection,
string collectionName,
string[] restrictions,
CancellationToken cancellationToken)
{
Debug.Assert(outerConnection is not null, "outerConnection may not be null.");

DbMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this);
Debug.Assert(metaDataFactory is not null, "metaDataFactory may not be null.");

return metaDataFactory.GetSchemaAsync(outerConnection, collectionName, restrictions, cancellationToken);
}

protected virtual bool ObtainAdditionalLocksForClose()
{
// No additional locks in default implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

namespace Microsoft.Data.ProviderBase
Expand Down Expand Up @@ -106,7 +108,7 @@ protected virtual void Dispose(bool disposing)
}
}

private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection)
private async ValueTask<DataTable> ExecuteCommandAsync(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken)
Copy link
Member

Choose a reason for hiding this comment

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

Really nice to be seeing proper async/await in SqlClient!

{
DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections];
DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey];
Expand All @@ -125,8 +127,8 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri
throw ADP.TooManyRestrictions(collectionName);
}

DbCommand command = connection.CreateCommand();
SqlConnection castConnection = connection as SqlConnection;
SqlCommand command = castConnection.CreateCommand();

command.CommandText = sqlCommand;
command.CommandTimeout = Math.Max(command.CommandTimeout, 180);
Expand All @@ -152,12 +154,12 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri
command.Parameters.Add(restrictionParameter);
}

DbDataReader reader = null;
SqlDataReader reader = null;
try
{
try
{
reader = command.ExecuteReader();
reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteReader();
}
catch (Exception e)
{
Expand All @@ -174,16 +176,25 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri
Locale = CultureInfo.InvariantCulture
};

// We would ordinarily call reader.GetSchemaTableAsync, but this waits synchronously for the reader to receive its type metadata.
// Instead, we invoke reader.ReadAsync outside of the while loop, which will implicitly ensure that the metadata is available.
// ReadAsync/Read will throw an exception if necessary, so we can trust that the list of fields is available if the call returns.
bool firstResultAvailable = isAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : reader.Read();
DataTable schemaTable = reader.GetSchemaTable();

foreach (DataRow row in schemaTable.Rows)
{
resultTable.Columns.Add(row["ColumnName"] as string, (Type)row["DataType"] as Type);
resultTable.Columns.Add((string)row["ColumnName"], (Type)row["DataType"]);
}
object[] values = new object[resultTable.Columns.Count];
while (reader.Read())

if (firstResultAvailable)
{
reader.GetValues(values);
resultTable.Rows.Add(values);
object[] values = new object[resultTable.Columns.Count];
do
{
reader.GetValues(values);
resultTable.Rows.Add(values);
} while (isAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : reader.Read());
}
}
finally
Expand Down Expand Up @@ -375,6 +386,12 @@ private string GetParameterName(string neededCollectionName, int neededRestricti
}

public virtual DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions)
=> GetSchemaCore(connection, collectionName, restrictions, false, default).Result;

public virtual async Task<DataTable> GetSchemaAsync(DbConnection connection, string collectionName, string[] restrictions, CancellationToken cancellationToken)
=> await GetSchemaCore(connection, collectionName, restrictions, true, cancellationToken).ConfigureAwait(false);

private async ValueTask<DataTable> GetSchemaCore(DbConnection connection, string collectionName, string[] restrictions, bool isAsync, CancellationToken cancellationToken)
{
Debug.Assert(_metaDataCollectionsDataSet != null);

Expand All @@ -384,6 +401,7 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam

string[] hiddenColumns;

cancellationToken.ThrowIfCancellationRequested();
DataRow requestedCollectionRow = FindMetaDataCollectionRow(collectionName);
string exactCollectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string;

Expand All @@ -401,6 +419,7 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam
}
}

cancellationToken.ThrowIfCancellationRequested();
string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string;

DataTable requestedSchema;
Expand All @@ -424,16 +443,15 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam
throw ADP.TooManyRestrictions(exactCollectionName);
}


requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns);
break;

case SqlCommandKey:
requestedSchema = ExecuteCommand(requestedCollectionRow, restrictions, connection);
requestedSchema = await ExecuteCommandAsync(requestedCollectionRow, restrictions, connection, isAsync, cancellationToken).ConfigureAwait(false);
break;

case PrepareCollectionKey:
requestedSchema = PrepareCollection(exactCollectionName, restrictions, connection);
requestedSchema = await PrepareCollectionAsync(exactCollectionName, restrictions, connection, isAsync, cancellationToken).ConfigureAwait(false);
break;

default:
Expand Down Expand Up @@ -701,7 +719,7 @@ private static DataTable CreateReservedWordsDataTable()
}
};

protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection)
protected virtual ValueTask<DataTable> PrepareCollectionAsync(string collectionName, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken)
{
throw ADP.NotSupported();
}
Expand Down
Loading
Loading