diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
index 11602bd078..d9911c3fc3 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
@@ -1357,12 +1357,28 @@ For more information on working with events, see [Connection Events](https://lea
- Returns schema information for the data source of this . For more information about scheme, see SQL Server Schema Collections.
+ Returns schema information for the data source of this . For more information about schemas, see SQL Server Schema Collections.
A that contains schema information.
+
+
+ The cancellation token.
+
+
+ An asynchronous version of , which returns schema information for the data source of this . For more information about schemas, see SQL Server Schema Collections.
+
+
+ A task representing the asynchronous operation.
+
+
+
+ For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming.
+
+
+
Specifies the name of the schema to return.
@@ -1648,6 +1664,28 @@ For more information on working with events, see [Connection Events](https://lea
is specified as null.
+
+
+ Specifies the name of the schema to return.
+
+
+ The cancellation token.
+
+
+ An asynchronous version of , which returns schema information for the data source of this using the specified string for the schema name.
+
+
+ A task representing the asynchronous operation.
+
+
+
+ For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming.
+
+
+
+ is specified as null.
+
+
Specifies the name of the schema to return.
@@ -1677,6 +1715,35 @@ For more information on working with events, see [Connection Events](https://lea
+
+
+ Specifies the name of the schema to return.
+
+
+ A set of restriction values for the requested schema.
+
+
+ The cancellation token.
+
+
+ An asynchronous version of , which returns schema information for the data source of this using the specified string for the schema name and the specified string array for the restriction values.
+
+
+ A task representing the asynchronous operation.
+
+
+
+ The parameter can supply n 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 and then put the appropriate value in for the restriction that you would like to specify a value for.
+
+
+ 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 , 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.
+
+
+
+ is specified as null.
+
+
+
Occurs when SQL Server returns a warning or informational message.
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index 55b68fd9b3..1fbb65e8f2 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -993,6 +993,21 @@ public event Microsoft.Data.SqlClient.SqlInfoMessageEventHandler InfoMessage { a
public override System.Data.DataTable GetSchema(string collectionName) { throw null; }
///
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { throw null; }
+#if NET
+ ///
+ public override System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
+ ///
+ public override System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; }
+ ///
+ public override System.Threading.Tasks.Task GetSchemaAsync(string collectionName, string[] restrictionValues, System.Threading.CancellationToken cancellationToken = default) { throw null; }
+#else
+ ///
+ public System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
+ ///
+ public System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; }
+ ///
+ public System.Threading.Tasks.Task GetSchemaAsync(string collectionName, string[] restrictionValues, System.Threading.CancellationToken cancellationToken = default) { throw null; }
+#endif
///
public override void Open() { }
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 45df50badb..8db18ed265 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -1836,12 +1836,24 @@ public override DataTable GetSchema()
return GetSchema(DbMetaDataCollectionNames.MetaDataCollections, null);
}
+ ///
+ public override Task GetSchemaAsync(CancellationToken cancellationToken = default)
+ {
+ return GetSchemaAsync(DbMetaDataCollectionNames.MetaDataCollections, cancellationToken);
+ }
+
///
public override DataTable GetSchema(string collectionName)
{
return GetSchema(collectionName, null);
}
+ ///
+ public override Task GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default)
+ {
+ return GetSchemaAsync(collectionName, null, cancellationToken);
+ }
+
///
public override DataTable GetSchema(string collectionName, string[] restrictionValues)
{
@@ -1849,6 +1861,13 @@ public override DataTable GetSchema(string collectionName, string[] restrictionV
return InnerConnection.GetSchema(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues);
}
+ ///
+ public override Task GetSchemaAsync(string collectionName, string[] restrictionValues, CancellationToken cancellationToken = default)
+ {
+ SqlClientEventSource.Log.TryTraceEvent("SqlConnection.GetSchemaAsync | Info | Object Id {0}, Collection Name '{1}'", ObjectID, collectionName);
+ return InnerConnection.GetSchemaAsync(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues, cancellationToken);
+ }
+
///
public override bool CanCreateBatch => true;
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index d7f280ca33..f646a748a2 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -880,6 +880,12 @@ public override void EnlistTransaction(System.Transactions.Transaction transacti
public override System.Data.DataTable GetSchema(string collectionName) { throw null; }
///
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { throw null; }
+ ///
+ public System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
+ ///
+ public System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; }
+ ///
+ public System.Threading.Tasks.Task GetSchemaAsync(string collectionName, string[] restrictionValues, System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override void Open() { }
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 46800b4140..c847980954 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -1752,6 +1752,46 @@ private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationTok
}
}
+ ///
+ public override DataTable GetSchema()
+ {
+ return GetSchema(DbMetaDataCollectionNames.MetaDataCollections, null);
+ }
+
+ ///
+ public Task GetSchemaAsync(CancellationToken cancellationToken = default)
+ {
+ return GetSchemaAsync(DbMetaDataCollectionNames.MetaDataCollections, cancellationToken);
+ }
+
+ ///
+ public override DataTable GetSchema(string collectionName)
+ {
+ return GetSchema(collectionName, null);
+ }
+
+ ///
+ public Task GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default)
+ {
+ return GetSchemaAsync(collectionName, null, cancellationToken);
+ }
+
+ ///
+ public override DataTable GetSchema(string collectionName, string[] restrictionValues)
+ {
+ // NOTE: This is virtual because not all providers may choose to support
+ // returning schema data
+ SqlConnection.ExecutePermission.Demand();
+ return InnerConnection.GetSchema(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues);
+ }
+
+ ///
+ public Task GetSchemaAsync(string collectionName, string[] restrictionValues, CancellationToken cancellationToken = default)
+ {
+ SqlConnection.ExecutePermission.Demand();
+ return InnerConnection.GetSchemaAsync(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues, cancellationToken);
+ }
+
private class OpenAsyncRetry
{
private SqlConnection _parent;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
index eb5cb511be..f901428fa7 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
@@ -313,27 +313,6 @@ internal DbMetaDataFactory GetMetaDataFactoryInternal(DbConnectionInternal inter
return GetMetaDataFactory(internalConnection);
}
- ///
- override public DataTable GetSchema()
- {
- return this.GetSchema(DbMetaDataCollectionNames.MetaDataCollections, null);
- }
-
- ///
- override public DataTable GetSchema(string collectionName)
- {
- return this.GetSchema(collectionName, null);
- }
-
- ///
- override public DataTable GetSchema(string collectionName, string[] restrictionValues)
- {
- // NOTE: This is virtual because not all providers may choose to support
- // returning schema data
- SqlConnection.ExecutePermission.Demand();
- return InnerConnection.GetSchema(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues);
- }
-
internal void NotifyWeakReference(int message)
{
InnerConnection.NotifyWeakReference(message);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
index d4ea183312..5bd6381cbb 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
@@ -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;
@@ -39,6 +40,9 @@ internal override void CloseConnection(DbConnection owningObject, DbConnectionFa
protected internal override DataTable GetSchema(DbConnectionFactory factory, DbConnectionPoolGroup poolGroup, DbConnection outerConnection, string collectionName, string[] restrictions)
=> throw ADP.ClosedConnectionError();
+ protected internal override Task GetSchemaAsync(DbConnectionFactory 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(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
index b4dfb4f214..22953fe884 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
@@ -885,6 +885,22 @@ protected internal virtual DataTable GetSchema(
return metaDataFactory.GetSchema(outerConnection, collectionName, restrictions);
}
+ protected internal virtual Task GetSchemaAsync(
+ DbConnectionFactory 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
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs
index 4eea91478c..ba2fad0df6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs
@@ -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
@@ -106,7 +108,7 @@ protected virtual void Dispose(bool disposing)
}
}
- private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection)
+ private async ValueTask ExecuteCommandAsync(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken)
{
DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections];
DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey];
@@ -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);
@@ -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)
{
@@ -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
@@ -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 GetSchemaAsync(DbConnection connection, string collectionName, string[] restrictions, CancellationToken cancellationToken)
+ => await GetSchemaCore(connection, collectionName, restrictions, true, cancellationToken).ConfigureAwait(false);
+
+ private async ValueTask GetSchemaCore(DbConnection connection, string collectionName, string[] restrictions, bool isAsync, CancellationToken cancellationToken)
{
Debug.Assert(_metaDataCollectionsDataSet != null);
@@ -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;
@@ -401,6 +419,7 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam
}
}
+ cancellationToken.ThrowIfCancellationRequested();
string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string;
DataTable requestedSchema;
@@ -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:
@@ -701,7 +719,7 @@ private static DataTable CreateReservedWordsDataTable()
}
};
- protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection)
+ protected virtual ValueTask PrepareCollectionAsync(string collectionName, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken)
{
throw ADP.NotSupported();
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs
index da39c1f375..4df5a27cdc 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs
@@ -8,6 +8,8 @@
using System.Data.Common;
using System.IO;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.ProviderBase;
@@ -26,7 +28,7 @@ public SqlMetaDataFactory(Stream XMLStream,
base(XMLStream, serverVersion, serverVersionNormalized)
{ }
- private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string ServerVersion)
+ private async ValueTask AddUDTsToDataTypesTableAsync(DataTable dataTypesTable, SqlConnection connection, string ServerVersion, bool isAsync, CancellationToken cancellationToken)
{
const string sqlCommand =
"select " +
@@ -52,7 +54,7 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con
SqlCommand engineEditionCommand = connection.CreateCommand();
engineEditionCommand.CommandText = "SELECT SERVERPROPERTY('EngineEdition');";
- var engineEdition = (int)engineEditionCommand.ExecuteScalar();
+ var engineEdition = (int)(isAsync ? await engineEditionCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false) : engineEditionCommand.ExecuteScalar());
if (_assemblyPropertyUnsupportedEngines.Contains(engineEdition))
{
@@ -97,13 +99,12 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con
const int publicKeyIndex = 7;
- using (IDataReader reader = command.ExecuteReader())
+ using (DbDataReader reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteReader())
{
object[] values = new object[11];
- while (reader.Read())
+ while (isAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : reader.Read())
{
-
reader.GetValues(values);
DataRow newRow = dataTypesTable.NewRow();
@@ -173,11 +174,12 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con
newRow.AcceptChanges();
} // if assembly name
+ cancellationToken.ThrowIfCancellationRequested();
}//end while
} // end using
}
- private void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string ServerVersion)
+ private async ValueTask AddTVPsToDataTypesTableAsync(DataTable dataTypesTable, SqlConnection connection, string ServerVersion, bool isAsync, CancellationToken cancellationToken)
{
const string sqlCommand =
@@ -219,11 +221,11 @@ private void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnection con
const int isNullableIndex = 1;
const int typeNameIndex = 0;
- using (IDataReader reader = command.ExecuteReader())
+ using (DbDataReader reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteReader())
{
object[] values = new object[11];
- while (reader.Read())
+ while (isAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : reader.Read())
{
reader.GetValues(values);
@@ -249,11 +251,13 @@ private void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnection con
dataTypesTable.Rows.Add(newRow);
newRow.AcceptChanges();
} // if type name
+
+ cancellationToken.ThrowIfCancellationRequested();
}//end while
} // end using
}
- private DataTable GetDataTypesTable(SqlConnection connection)
+ private async ValueTask GetDataTypesTableAsync(SqlConnection connection, bool isAsync, CancellationToken cancellationToken)
{
// verify the existence of the table in the data set
DataTable dataTypesTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.DataTypes];
@@ -262,17 +266,18 @@ private DataTable GetDataTypesTable(SqlConnection connection)
throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.DataTypes);
}
+ cancellationToken.ThrowIfCancellationRequested();
// copy the table filtering out any rows that don't apply to tho current version of the provider
dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, null);
- addUDTsToDataTypesTable(dataTypesTable, connection, ServerVersionNormalized);
- AddTVPsToDataTypesTable(dataTypesTable, connection, ServerVersionNormalized);
+ await AddUDTsToDataTypesTableAsync(dataTypesTable, connection, ServerVersionNormalized, isAsync, cancellationToken).ConfigureAwait(false);
+ await AddTVPsToDataTypesTableAsync(dataTypesTable, connection, ServerVersionNormalized, isAsync, cancellationToken).ConfigureAwait(false);
dataTypesTable.AcceptChanges();
return dataTypesTable;
}
- protected override DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection)
+ protected async override ValueTask PrepareCollectionAsync(string collectionName, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken)
{
SqlConnection sqlConnection = (SqlConnection)connection;
DataTable resultTable = null;
@@ -283,7 +288,7 @@ protected override DataTable PrepareCollection(string collectionName, string[] r
{
throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataTypes);
}
- resultTable = GetDataTypesTable(sqlConnection);
+ resultTable = await GetDataTypesTableAsync(sqlConnection, isAsync, cancellationToken).ConfigureAwait(false);
}
if (resultTable == null)
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs
index 616a8fec6f..879b653420 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs
@@ -195,10 +195,11 @@ public void SqlConnectionInvalidParameters(string connString)
}
[Fact]
- public void ClosedConnectionSchemaRetrieval()
+ public async Task ClosedConnectionSchemaRetrieval()
{
using SqlConnection connection = new(string.Empty);
Assert.Throws(() => connection.GetSchema());
+ await Assert.ThrowsAsync(() => connection.GetSchemaAsync());
}
[Theory]
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs
index d61866f176..f203920df1 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs
@@ -7,6 +7,7 @@
using System.Collections.Generic;
using Xunit;
using System.Reflection;
+using System.Threading.Tasks;
namespace Microsoft.Data.SqlClient.Tests
{
@@ -354,6 +355,47 @@ public void GetSchema_Connection_Closed()
Assert.NotNull(ex.Message);
}
+ [Fact]
+ public async Task GetSchemaAsync_Connection_Closed()
+ {
+ SqlConnection cn = new SqlConnection();
+
+ InvalidOperationException ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync());
+ // Invalid operation. The connection is closed
+ Assert.Null(ex.InnerException);
+ Assert.NotNull(ex.Message);
+
+ ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync("Tables"));
+ // Invalid operation. The connection is closed
+ Assert.Null(ex.InnerException);
+ Assert.NotNull(ex.Message);
+
+ ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync(null));
+ // Invalid operation. The connection is closed
+ Assert.Null(ex.InnerException);
+ Assert.NotNull(ex.Message);
+
+ ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync("Tables", new string[] { "master" }));
+ // Invalid operation. The connection is closed
+ Assert.Null(ex.InnerException);
+ Assert.NotNull(ex.Message);
+
+ ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync(null, new string[] { "master" }));
+ // Invalid operation. The connection is closed
+ Assert.Null(ex.InnerException);
+ Assert.NotNull(ex.Message);
+
+ ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync("Tables", null));
+ // Invalid operation. The connection is closed
+ Assert.Null(ex.InnerException);
+ Assert.NotNull(ex.Message);
+
+ ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync(null, null));
+ // Invalid operation. The connection is closed
+ Assert.Null(ex.InnerException);
+ Assert.NotNull(ex.Message);
+ }
+
[Theory]
[InlineData("Authentication = ActiveDirectoryIntegrated;Password = ''")]
[InlineData("Authentication = ActiveDirectoryIntegrated;PWD = ''")]
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs
index 0bc5b29806..134ca481fb 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Data;
+using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -18,96 +19,96 @@ public static bool CanRunSchemaTests()
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetTablesFromSchema()
+ public static async Task GetTablesFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.Tables, new string[] { "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "TABLE_TYPE" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.Tables, new string[] { "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "TABLE_TYPE" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetProceduresFromSchema()
+ public static async Task GetProceduresFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.Procedures, new string[] { "ROUTINE_SCHEMA", "ROUTINE_NAME", "ROUTINE_TYPE" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.Procedures, new string[] { "ROUTINE_SCHEMA", "ROUTINE_NAME", "ROUTINE_TYPE" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetProcedureParametersFromSchema()
+ public static async Task GetProcedureParametersFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.ProcedureParameters, new string[] { "PARAMETER_MODE", "PARAMETER_NAME" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.ProcedureParameters, new string[] { "PARAMETER_MODE", "PARAMETER_NAME" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetDatabasesFromSchema()
+ public static async Task GetDatabasesFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.Databases, new string[] { "database_name", "dbid", "create_date" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.Databases, new string[] { "database_name", "dbid", "create_date" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetForeignKeysFromSchema()
+ public static async Task GetForeignKeysFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.ForeignKeys, new string[] { "CONSTRAINT_TYPE", "IS_DEFERRABLE", "INITIALLY_DEFERRED" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.ForeignKeys, new string[] { "CONSTRAINT_TYPE", "IS_DEFERRABLE", "INITIALLY_DEFERRED" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetIndexesFromSchema()
+ public static async Task GetIndexesFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.Indexes, new string[] { "index_name", "constraint_name" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.Indexes, new string[] { "index_name", "constraint_name" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetIndexColumnsFromSchema()
+ public static async Task GetIndexColumnsFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.IndexColumns, new string[] { "index_name", "KeyType", "column_name" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.IndexColumns, new string[] { "index_name", "KeyType", "column_name" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetColumnsFromSchema()
+ public static async Task GetColumnsFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.Columns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.Columns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetAllColumnsFromSchema()
+ public static async Task GetAllColumnsFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.AllColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.AllColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetColumnSetColumnsFromSchema()
+ public static async Task GetColumnSetColumnsFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.ColumnSetColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.ColumnSetColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetUsersFromSchema()
+ public static async Task GetUsersFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.Users, new string[] { "uid", "user_name" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.Users, new string[] { "uid", "user_name" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetViewsFromSchema()
+ public static async Task GetViewsFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.Views, new string[] { "TABLE_NAME", "CHECK_OPTION", "IS_UPDATABLE" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.Views, new string[] { "TABLE_NAME", "CHECK_OPTION", "IS_UPDATABLE" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetViewColumnsFromSchema()
+ public static async Task GetViewColumnsFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.ViewColumns, new string[] { "VIEW_CATALOG", "VIEW_SCHEMA", "VIEW_NAME" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.ViewColumns, new string[] { "VIEW_CATALOG", "VIEW_SCHEMA", "VIEW_NAME" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetUserDefinedTypesFromSchema()
+ public static async Task GetUserDefinedTypesFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.UserDefinedTypes, new string[] { "assembly_name", "version_revision", "culture_info" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.UserDefinedTypes, new string[] { "assembly_name", "version_revision", "culture_info" });
}
[ConditionalFact(nameof(CanRunSchemaTests))]
- public static void GetStructuredTypeMembersFromSchema()
+ public static async Task GetStructuredTypeMembersFromSchema()
{
- VerifySchemaTable(SqlClientMetaDataCollectionNames.StructuredTypeMembers, new string[] { "TYPE_CATALOG", "TYPE_SCHEMA", "TYPE_NAME", "MEMBER_NAME", "ORDINAL_POSITION" });
+ await VerifySchemaTable(SqlClientMetaDataCollectionNames.StructuredTypeMembers, new string[] { "TYPE_CATALOG", "TYPE_SCHEMA", "TYPE_NAME", "MEMBER_NAME", "ORDINAL_POSITION" });
}
- private static void VerifySchemaTable(string schemaItemName, string[] testColumnNames)
+ private static async Task VerifySchemaTable(string schemaItemName, string[] testColumnNames)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString)
{
@@ -118,17 +119,24 @@ private static void VerifySchemaTable(string schemaItemName, string[] testColumn
{
// Connect to the database then retrieve the schema information
connection.Open();
- DataTable table = connection.GetSchema(schemaItemName);
+ DataTable syncTable = connection.GetSchema(schemaItemName);
+ DataTable asyncTable = await connection.GetSchemaAsync(schemaItemName);
// Get all table columns
- HashSet columnNames = new HashSet();
+ HashSet syncColumnNames = new HashSet();
+ HashSet asyncColumnNames = new HashSet();
- foreach (DataColumn column in table.Columns)
+ foreach (DataColumn column in syncTable.Columns)
{
- columnNames.Add(column.ColumnName);
+ syncColumnNames.Add(column.ColumnName);
+ }
+ foreach (DataColumn column in asyncTable.Columns)
+ {
+ asyncColumnNames.Add(column.ColumnName);
}
- Assert.All(testColumnNames, column => Assert.Contains(column, columnNames));
+ Assert.All(testColumnNames, column => Assert.Contains(column, syncColumnNames));
+ Assert.All(testColumnNames, column => Assert.Contains(column, asyncColumnNames));
}
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs
index 4d2e964a5a..a09d51d1fc 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs
@@ -8,6 +8,7 @@
using System.Data;
using System.Data.Common;
using System.Linq;
+using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -22,23 +23,35 @@ public static void TestGetSchema(bool openTransaction)
{
using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
{
- conn.Open();
- DataTable dataBases;
+ SqlTransaction transaction = null;
- if (openTransaction)
+ conn.Open();
+ try
{
- using (SqlTransaction transaction = conn.BeginTransaction())
+ if (openTransaction)
{
- dataBases = conn.GetSchema("DATABASES");
+ transaction = conn.BeginTransaction();
}
+
+ DataTable dataBases = conn.GetSchema("DATABASES");
+ Assert.True(dataBases.Rows.Count > 0, "At least one database is expected");
+
+ string firstDatabaseName = dataBases.Rows[0]["database_name"] as string;
+ dataBases = conn.GetSchema("DATABASES", [firstDatabaseName]);
+
+ Assert.Equal(1, dataBases.Rows.Count);
+ Assert.Equal(firstDatabaseName, dataBases.Rows[0]["database_name"] as string);
+
+ string nonexistentDatabaseName = DataTestUtility.GenerateRandomCharacters("NonExistentDatabase_");
+ dataBases = conn.GetSchema("DATABASES", [nonexistentDatabaseName]);
+
+ Assert.Equal(0, dataBases.Rows.Count);
}
- else
+ finally
{
- dataBases = conn.GetSchema("DATABASES");
+ transaction?.Dispose();
}
- Assert.True(dataBases.Rows.Count > 0, "At least one database is expected");
-
DataTable metaDataCollections = conn.GetSchema(DbMetaDataCollectionNames.MetaDataCollections);
Assert.True(metaDataCollections != null && metaDataCollections.Rows.Count > 0);
@@ -56,6 +69,59 @@ public static void TestGetSchema(bool openTransaction)
}
}
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ [InlineData(true)]
+ [InlineData(false)]
+ public static async Task TestGetSchemaAsync(bool openTransaction)
+ {
+ using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
+ {
+ SqlTransaction transaction = null;
+
+ await conn.OpenAsync();
+ try
+ {
+ if (openTransaction)
+ {
+ transaction = conn.BeginTransaction();
+ }
+
+ DataTable dataBases = await conn.GetSchemaAsync("DATABASES");
+ Assert.True(dataBases.Rows.Count > 0, "At least one database is expected");
+
+ string firstDatabaseName = dataBases.Rows[0]["database_name"] as string;
+ dataBases = await conn.GetSchemaAsync("DATABASES", [firstDatabaseName]);
+
+ Assert.Equal(1, dataBases.Rows.Count);
+ Assert.Equal(firstDatabaseName, dataBases.Rows[0]["database_name"] as string);
+
+ string nonexistentDatabaseName = DataTestUtility.GenerateRandomCharacters("NonExistentDatabase_");
+ dataBases = await conn.GetSchemaAsync("DATABASES", [nonexistentDatabaseName]);
+
+ Assert.Equal(0, dataBases.Rows.Count);
+ }
+ finally
+ {
+ transaction?.Dispose();
+ }
+
+ DataTable metaDataCollections = await conn.GetSchemaAsync(DbMetaDataCollectionNames.MetaDataCollections);
+ Assert.True(metaDataCollections != null && metaDataCollections.Rows.Count > 0);
+
+ DataTable metaDataSourceInfo = await conn.GetSchemaAsync(DbMetaDataCollectionNames.DataSourceInformation);
+ Assert.True(metaDataSourceInfo != null && metaDataSourceInfo.Rows.Count > 0);
+
+ DataTable metaDataTypes = await conn.GetSchemaAsync(DbMetaDataCollectionNames.DataTypes);
+ Assert.True(metaDataTypes != null && metaDataTypes.Rows.Count > 0);
+
+ var tinyintRow = metaDataTypes.Rows.OfType().Where(p => (string)p["TypeName"] == "tinyint");
+ foreach (var row in tinyintRow)
+ {
+ Assert.True((String)row["TypeName"] == "tinyint" && (String)row["DataType"] == "System.Byte" && (bool)row["IsUnsigned"]);
+ }
+ }
+ }
+
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
public static void TestCommandBuilder()
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
index 16d48d7c37..739c41c7a5 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
@@ -515,17 +515,23 @@ public void TestSchemaTable()
using (SqlDataReader reader = cmd.ExecuteReader())
{
- DataTable t = reader.GetSchemaTable();
+ DataTable syncSchemaTable = reader.GetSchemaTable();
string expectedSchemaTableValues =
"ids, 0, 4, 10, 255, False, , , , ids, , , System.Int32, True, 8, , , False, False, False, , False, False, System.Data.SqlTypes.SqlInt32, int, , , , , 8, False, " + Environment.NewLine +
"pos, 1, 20, 255, 255, False, , , , pos, , , Line, True, 29, , , False, False, False, , False, False, Line, UdtTestDb.dbo.Line, , , , Line, Shapes, Version=1.2.0.0, Culture=neutral, PublicKeyToken=a3e3aa32e6a16344, 29, False, " + Environment.NewLine;
StringBuilder builder = new StringBuilder();
- foreach (DataRow row in t.Rows)
+ for (int i = 0; i < syncSchemaTable.Rows.Count; i++)
{
- foreach (DataColumn col in t.Columns)
- builder.Append(row[col] + ", ");
+ DataRow syncRow = syncSchemaTable.Rows[i];
+
+ for (int j = 0; j < syncSchemaTable.Columns.Count; j++)
+ {
+ DataColumn syncColumn = syncSchemaTable.Columns[j];
+
+ builder.Append(syncRow[syncColumn] + ", ");
+ }
builder.AppendLine();
}