From 7313a6fb58a5bd19a1552575668196282dca8718 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Thu, 14 Nov 2024 20:36:00 +0000
Subject: [PATCH 1/8] Implemented asynchronous schema APIs
---
.../SqlConnection.xml | 67 ++++++++++++++++
.../netcore/ref/Microsoft.Data.SqlClient.cs | 6 ++
.../Microsoft/Data/SqlClient/SqlConnection.cs | 19 +++++
.../netfx/ref/Microsoft.Data.SqlClient.cs | 6 ++
.../Microsoft/Data/SqlClient/SqlConnection.cs | 40 ++++++++++
.../Data/SqlClient/SqlConnectionHelper.cs | 21 -----
.../src/Microsoft.Data.SqlClient.csproj | 1 +
.../Data/ProviderBase/DbConnectionClosed.cs | 4 +
.../Data/ProviderBase/DbConnectionInternal.cs | 16 ++++
.../Data/ProviderBase/DbMetaDataFactory.cs | 28 +++++--
.../Data/SqlClient/SqlMetadataFactory.cs | 31 ++++---
.../SqlConnectionBasicTests.cs | 3 +-
.../FunctionalTests/SqlConnectionTest.cs | 42 ++++++++++
.../ConnectionSchemaTest.cs | 80 ++++++++++---------
.../SqlSchemaInfoTest/SqlSchemaInfoTest.cs | 42 ++++++++++
15 files changed, 328 insertions(+), 78 deletions(-)
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
index b34a61386b..bf17e5148a 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
@@ -2332,6 +2332,22 @@
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 scheme, 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.
@@ -2617,6 +2633,28 @@
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.
@@ -2646,6 +2684,35 @@
+
+
+ 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 3ceb56da17..548b0425ea 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -965,10 +965,16 @@ public event Microsoft.Data.SqlClient.SqlInfoMessageEventHandler InfoMessage { a
protected override System.Data.Common.DbCommand CreateDbCommand() { throw null; }
///
public override System.Data.DataTable GetSchema() { throw null; }
+ ///
+ public override System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.Data.DataTable GetSchema(string collectionName) { throw null; }
+ ///
+ public override System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { throw null; }
+ ///
+ public override 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/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 555cbc051c..a1234c3a34 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
@@ -1781,12 +1781,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)
{
@@ -1794,6 +1806,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 9cbc4b136d..831bf1f1c5 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -875,10 +875,16 @@ public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction
public override void EnlistTransaction(System.Transactions.Transaction transaction) { }
///
public override System.Data.DataTable GetSchema() { throw null; }
+ ///
+ public System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.Data.DataTable GetSchema(string collectionName) { throw null; }
+ ///
+ public System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { 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 81bbb1ea85..422e60ac9f 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
@@ -2015,6 +2015,46 @@ private Task InternalOpenAsync(CancellationToken cancellationToken)
}
}
+ ///
+ 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
{
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 54d31d5301..fa0d632cf7 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
@@ -311,27 +311,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.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
index a5e25db563..942881a0d1 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
@@ -10,6 +10,7 @@
+
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 b25f9a2d3b..a8cec242b1 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
@@ -6,6 +6,7 @@
using System.Data;
using System.Data.Common;
using System.Diagnostics;
+using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Data.ProviderBase
@@ -37,6 +38,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 9744b8db9b..707a6f34a6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
@@ -935,6 +935,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 5ca28b5458..4a2b8e539f 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
@@ -107,7 +109,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];
@@ -158,7 +160,7 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri
{
try
{
- reader = command.ExecuteReader();
+ reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteReader();
}
catch (Exception e)
{
@@ -175,13 +177,18 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri
Locale = CultureInfo.InvariantCulture
};
+ cancellationToken.ThrowIfCancellationRequested();
+#if NET
+ DataTable schemaTable = isAsync ? await reader.GetSchemaTableAsync(cancellationToken).ConfigureAwait(false) : reader.GetSchemaTable();
+#else
DataTable schemaTable = reader.GetSchemaTable();
+#endif
foreach (DataRow row in schemaTable.Rows)
{
resultTable.Columns.Add(row["ColumnName"] as string, (Type)row["DataType"] as Type);
}
object[] values = new object[resultTable.Columns.Count];
- while (reader.Read())
+ while (isAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : reader.Read())
{
reader.GetValues(values);
resultTable.Rows.Add(values);
@@ -390,6 +397,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);
@@ -399,6 +412,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;
@@ -416,6 +430,7 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam
}
}
+ cancellationToken.ThrowIfCancellationRequested();
string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string;
DataTable requestedSchema;
@@ -439,7 +454,6 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam
throw ADP.TooManyRestrictions(exactCollectionName);
}
-
requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns);
// TODO: Consider an alternate method that doesn't involve special casing -- perhaps _prepareCollection
@@ -453,11 +467,11 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam
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:
@@ -514,7 +528,7 @@ private void LoadDataSetFromXml(Stream XmlStream)
_metaDataCollectionsDataSet.ReadXml(reader);
}
- 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 6bbc44f6b1..c8e0dd73f1 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs
@@ -191,10 +191,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 54b04234d6..284edb8576 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 83c92b7236..21a5ad725f 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
@@ -56,6 +57,47 @@ 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))
+ {
+ await conn.OpenAsync();
+ DataTable dataBases;
+
+ if (openTransaction)
+ {
+ using (SqlTransaction transaction = conn.BeginTransaction())
+ {
+ dataBases = await conn.GetSchemaAsync("DATABASES");
+ }
+ }
+ else
+ {
+ dataBases = await conn.GetSchemaAsync("DATABASES");
+ }
+
+ Assert.True(dataBases.Rows.Count > 0, "At least one database is expected");
+
+ 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()
{
From 63dae61ad34eb1c50958744599d861e189dce67e Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Thu, 14 Nov 2024 21:16:11 +0000
Subject: [PATCH 2/8] Remove erroneously-added reference to shared csproj
---
src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
index 942881a0d1..a5e25db563 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
@@ -10,7 +10,6 @@
-
From 40d5f6673042c65fcba517e5fa6fdedcb01a9377 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Fri, 15 Nov 2024 12:03:10 +0000
Subject: [PATCH 3/8] Implemented SqlDataReader.GetSchemaTableAsync
---
.../SqlDataReader.xml | 20 ++++++++++++++
.../netcore/ref/Microsoft.Data.SqlClient.cs | 6 +++++
.../Microsoft/Data/SqlClient/SqlDataReader.cs | 18 +++++++++++++
.../netfx/ref/Microsoft.Data.SqlClient.cs | 6 +++++
.../Microsoft/Data/SqlClient/SqlDataReader.cs | 18 +++++++++++++
.../Data/ProviderBase/DbMetaDataFactory.cs | 9 +++----
.../SQL/DataReaderTest/DataReaderTest.cs | 24 +++++++++++++++++
.../tests/ManualTests/SQL/UdtTest/UdtTest2.cs | 26 +++++++++++++++----
8 files changed, 116 insertions(+), 11 deletions(-)
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
index ad997ad909..3f3f8da358 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
@@ -1084,6 +1084,26 @@
The is closed.
+
+
+ The cancellation token.
+
+
+ An asynchronous version of , which returns a that describes the column metadata of the .
+
+
+ A task representing the asynchronous operation.
+
+
+
+ For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming.
+
+
+
+ The is closed.
+
+
+
The zero-based column ordinal.
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 548b0425ea..6cf308d6c5 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -5,6 +5,10 @@
// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available.
// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future.
+using System.Data;
+using System.Threading.Tasks;
+using System.Threading;
+
[assembly: System.CLSCompliant(true)]
namespace Microsoft.Data
{
@@ -1365,6 +1369,8 @@ public override void Close() { }
public virtual System.Data.SqlTypes.SqlXml GetSqlXml(int i) { throw null; }
///
public override System.Data.DataTable GetSchemaTable() { throw null; }
+ ///
+ public override Task GetSchemaTableAsync(CancellationToken cancellationToken) { throw null; }
///
public override System.IO.Stream GetStream(int i) { throw null; }
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index 5a877d61e9..1c1b6446c5 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -1501,6 +1501,24 @@ public override DataTable GetSchemaTable()
}
}
+ ///
+ public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ try
+ {
+ return Task.FromResult(GetSchemaTable());
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+
///
override public bool GetBoolean(int i)
{
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 831bf1f1c5..caacba9403 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -5,6 +5,10 @@
// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available.
// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future.
+using System.Data;
+using System.Threading.Tasks;
+using System.Threading;
+
[assembly: System.CLSCompliant(true)]
[assembly: System.Resources.NeutralResourcesLanguageAttribute("en-US")]
namespace Microsoft.Data
@@ -1371,6 +1375,8 @@ public override void Close() { }
public virtual System.Data.SqlTypes.SqlXml GetSqlXml(int i) { throw null; }
///
public override System.Data.DataTable GetSchemaTable() { throw null; }
+ ///
+ public Task GetSchemaTableAsync(CancellationToken cancellationToken) { throw null; }
///
public override System.IO.Stream GetStream(int i) { throw null; }
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index 88add0a27c..ac69ebd567 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -1697,6 +1697,24 @@ public override DataTable GetSchemaTable()
}
}
+ ///
+ public Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ try
+ {
+ return Task.FromResult(GetSchemaTable());
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+
///
override public bool GetBoolean(int i)
{
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 4a2b8e539f..21b31efeae 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs
@@ -128,8 +128,8 @@ private async ValueTask ExecuteCommandAsync(DataRow requestedCollecti
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);
@@ -155,7 +155,7 @@ private async ValueTask ExecuteCommandAsync(DataRow requestedCollecti
command.Parameters.Add(restrictionParameter);
}
- DbDataReader reader = null;
+ SqlDataReader reader = null;
try
{
try
@@ -178,11 +178,8 @@ private async ValueTask ExecuteCommandAsync(DataRow requestedCollecti
};
cancellationToken.ThrowIfCancellationRequested();
-#if NET
DataTable schemaTable = isAsync ? await reader.GetSchemaTableAsync(cancellationToken).ConfigureAwait(false) : reader.GetSchemaTable();
-#else
- DataTable schemaTable = reader.GetSchemaTable();
-#endif
+
foreach (DataRow row in schemaTable.Rows)
{
resultTable.Columns.Add(row["ColumnName"] as string, (Type)row["DataType"] as Type);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
index d00ea1d226..7f8598acb6 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
@@ -9,6 +9,7 @@
using System.Reflection;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -66,6 +67,29 @@ public static void MultiQuerySchema()
Assert.Contains("ColString", columnNames);
}
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
+ public static async Task MultiQuerySchemaAsync()
+ {
+ using SqlConnection connection = new(DataTestUtility.TCPConnectionString);
+ await connection.OpenAsync();
+ using SqlCommand command = connection.CreateCommand();
+ // Use multiple queries
+ command.CommandText = "SELECT 1 as ColInteger; SELECT 'STRING' as ColString";
+ using SqlDataReader reader = await command.ExecuteReaderAsync();
+ HashSet columnNames = new();
+ do
+ {
+ DataTable schemaTable = await reader.GetSchemaTableAsync();
+ foreach (DataRow myField in schemaTable.Rows)
+ {
+ columnNames.Add(myField["ColumnName"].ToString());
+ }
+
+ } while (await reader.NextResultAsync());
+ Assert.Contains("ColInteger", columnNames);
+ Assert.Contains("ColString", columnNames);
+ }
+
// Checks for the IsColumnSet bit in the GetSchemaTable for Sparse columns
// TODO Synapse: Cannot find data type 'xml'.
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
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..793e19c4a1 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
@@ -8,6 +8,7 @@
using System.Text;
using Xunit;
using Microsoft.SqlServer.Server;
+using System.Threading.Tasks;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
@@ -505,7 +506,7 @@ public void Reader_CircleLate()
}
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))]
- public void TestSchemaTable()
+ public async Task TestSchemaTable()
{
using (SqlConnection conn = new SqlConnection(_connStr))
using (SqlCommand cmd = new SqlCommand("select * from lines", conn))
@@ -515,17 +516,32 @@ public void TestSchemaTable()
using (SqlDataReader reader = cmd.ExecuteReader())
{
- DataTable t = reader.GetSchemaTable();
+ DataTable syncSchemaTable = reader.GetSchemaTable();
+ DataTable asyncSchemaTable = await reader.GetSchemaTableAsync();
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;
+ Assert.Equal(syncSchemaTable.Rows.Count, asyncSchemaTable.Rows.Count);
+ Assert.Equal(syncSchemaTable.Columns.Count, asyncSchemaTable.Columns.Count);
+
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];
+ DataRow asyncRow = asyncSchemaTable.Rows[i];
+
+ for (int j = 0; j < syncSchemaTable.Columns.Count; j++)
+ {
+ DataColumn syncColumn = syncSchemaTable.Columns[j];
+ DataColumn asyncColumn = asyncSchemaTable.Columns[j];
+
+ Assert.Equal(syncColumn.ColumnName, asyncColumn.ColumnName);
+ Assert.Equal(syncRow[syncColumn], asyncRow[asyncColumn]);
+
+ builder.Append(syncRow[syncColumn] + ", ");
+ }
builder.AppendLine();
}
From a58b0a413047bb8d1993033408a970c086f4fa8c Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Fri, 15 Nov 2024 12:13:19 +0000
Subject: [PATCH 4/8] ref project cleanup
---
.../netcore/ref/Microsoft.Data.SqlClient.cs | 6 +-----
.../netfx/ref/Microsoft.Data.SqlClient.cs | 6 +-----
2 files changed, 2 insertions(+), 10 deletions(-)
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 6cf308d6c5..99a9f00837 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -5,10 +5,6 @@
// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available.
// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future.
-using System.Data;
-using System.Threading.Tasks;
-using System.Threading;
-
[assembly: System.CLSCompliant(true)]
namespace Microsoft.Data
{
@@ -1370,7 +1366,7 @@ public override void Close() { }
///
public override System.Data.DataTable GetSchemaTable() { throw null; }
///
- public override Task GetSchemaTableAsync(CancellationToken cancellationToken) { throw null; }
+ public override System.Threading.Tasks.Task GetSchemaTableAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
///
public override System.IO.Stream GetStream(int i) { throw null; }
///
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 caacba9403..bb3f73ebdc 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -5,10 +5,6 @@
// NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available.
// New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future.
-using System.Data;
-using System.Threading.Tasks;
-using System.Threading;
-
[assembly: System.CLSCompliant(true)]
[assembly: System.Resources.NeutralResourcesLanguageAttribute("en-US")]
namespace Microsoft.Data
@@ -1376,7 +1372,7 @@ public override void Close() { }
///
public override System.Data.DataTable GetSchemaTable() { throw null; }
///
- public Task GetSchemaTableAsync(CancellationToken cancellationToken) { throw null; }
+ public System.Threading.Tasks.Task GetSchemaTableAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
///
public override System.IO.Stream GetStream(int i) { throw null; }
///
From b60c6a6b8da822e18ba0c8d2f10c8fcc6f1fc3c3 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Fri, 15 Nov 2024 13:41:28 +0000
Subject: [PATCH 5/8] Add default parameter value to ref projects
---
.../netcore/ref/Microsoft.Data.SqlClient.cs | 2 +-
.../netfx/ref/Microsoft.Data.SqlClient.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
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 99a9f00837..a6a6c0cde9 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -1366,7 +1366,7 @@ public override void Close() { }
///
public override System.Data.DataTable GetSchemaTable() { throw null; }
///
- public override System.Threading.Tasks.Task GetSchemaTableAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
+ public override System.Threading.Tasks.Task GetSchemaTableAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.IO.Stream GetStream(int i) { throw null; }
///
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 bb3f73ebdc..5009630505 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -1372,7 +1372,7 @@ public override void Close() { }
///
public override System.Data.DataTable GetSchemaTable() { throw null; }
///
- public System.Threading.Tasks.Task GetSchemaTableAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
+ public System.Threading.Tasks.Task GetSchemaTableAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.IO.Stream GetStream(int i) { throw null; }
///
From 6bbd433e42bf1ae4d3ab1abb41fcb988bf0a212d Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Sat, 16 Nov 2024 08:56:14 +0000
Subject: [PATCH 6/8] Revert addition of SqlDataReader API
This introduces async-over-sync, removing such requires work with the state machine and separate review.
---
.../netcore/ref/Microsoft.Data.SqlClient.cs | 2 --
.../Microsoft/Data/SqlClient/SqlDataReader.cs | 18 --------------
.../netfx/ref/Microsoft.Data.SqlClient.cs | 2 --
.../Microsoft/Data/SqlClient/SqlDataReader.cs | 5 +++-
.../SQL/DataReaderTest/DataReaderTest.cs | 24 -------------------
.../tests/ManualTests/SQL/UdtTest/UdtTest2.cs | 12 +---------
6 files changed, 5 insertions(+), 58 deletions(-)
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 2e23cce5a6..85930804b1 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -1367,8 +1367,6 @@ public override void Close() { }
public virtual System.Data.SqlTypes.SqlXml GetSqlXml(int i) { throw null; }
///
public override System.Data.DataTable GetSchemaTable() { throw null; }
- ///
- public override System.Threading.Tasks.Task GetSchemaTableAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.IO.Stream GetStream(int i) { throw null; }
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index 5b73d420eb..edf9fd78e6 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -1580,24 +1580,6 @@ public override DataTable GetSchemaTable()
}
}
- ///
- public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
- {
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
- }
-
- try
- {
- return Task.FromResult(GetSchemaTable());
- }
- catch (Exception ex)
- {
- return Task.FromException(ex);
- }
- }
-
///
override public bool GetBoolean(int i)
{
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 00d1024b0b..d999ea76d2 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -1373,8 +1373,6 @@ public override void Close() { }
public virtual System.Data.SqlTypes.SqlXml GetSqlXml(int i) { throw null; }
///
public override System.Data.DataTable GetSchemaTable() { throw null; }
- ///
- public System.Threading.Tasks.Task GetSchemaTableAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.IO.Stream GetStream(int i) { throw null; }
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index ac69ebd567..af2402b49b 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -1698,8 +1698,11 @@ public override DataTable GetSchemaTable()
}
///
- public Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
+ internal Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
{
+ // This method wraps GetSchemaTable in a Task, introducing async-over-sync. It should not be publicly exposed until
+ // this has been removed and replaced with an async path to guarantee that metadata has been read. Its purpose meanwhile
+ // is to enable code sharing between netcore and netfx.
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
index 7f8598acb6..d00ea1d226 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs
@@ -9,7 +9,6 @@
using System.Reflection;
using System.Text;
using System.Threading;
-using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -67,29 +66,6 @@ public static void MultiQuerySchema()
Assert.Contains("ColString", columnNames);
}
- [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
- public static async Task MultiQuerySchemaAsync()
- {
- using SqlConnection connection = new(DataTestUtility.TCPConnectionString);
- await connection.OpenAsync();
- using SqlCommand command = connection.CreateCommand();
- // Use multiple queries
- command.CommandText = "SELECT 1 as ColInteger; SELECT 'STRING' as ColString";
- using SqlDataReader reader = await command.ExecuteReaderAsync();
- HashSet columnNames = new();
- do
- {
- DataTable schemaTable = await reader.GetSchemaTableAsync();
- foreach (DataRow myField in schemaTable.Rows)
- {
- columnNames.Add(myField["ColumnName"].ToString());
- }
-
- } while (await reader.NextResultAsync());
- Assert.Contains("ColInteger", columnNames);
- Assert.Contains("ColString", columnNames);
- }
-
// Checks for the IsColumnSet bit in the GetSchemaTable for Sparse columns
// TODO Synapse: Cannot find data type 'xml'.
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
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 793e19c4a1..739c41c7a5 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs
@@ -8,7 +8,6 @@
using System.Text;
using Xunit;
using Microsoft.SqlServer.Server;
-using System.Threading.Tasks;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
@@ -506,7 +505,7 @@ public void Reader_CircleLate()
}
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))]
- public async Task TestSchemaTable()
+ public void TestSchemaTable()
{
using (SqlConnection conn = new SqlConnection(_connStr))
using (SqlCommand cmd = new SqlCommand("select * from lines", conn))
@@ -517,28 +516,19 @@ public async Task TestSchemaTable()
using (SqlDataReader reader = cmd.ExecuteReader())
{
DataTable syncSchemaTable = reader.GetSchemaTable();
- DataTable asyncSchemaTable = await reader.GetSchemaTableAsync();
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;
- Assert.Equal(syncSchemaTable.Rows.Count, asyncSchemaTable.Rows.Count);
- Assert.Equal(syncSchemaTable.Columns.Count, asyncSchemaTable.Columns.Count);
-
StringBuilder builder = new StringBuilder();
for (int i = 0; i < syncSchemaTable.Rows.Count; i++)
{
DataRow syncRow = syncSchemaTable.Rows[i];
- DataRow asyncRow = asyncSchemaTable.Rows[i];
for (int j = 0; j < syncSchemaTable.Columns.Count; j++)
{
DataColumn syncColumn = syncSchemaTable.Columns[j];
- DataColumn asyncColumn = asyncSchemaTable.Columns[j];
-
- Assert.Equal(syncColumn.ColumnName, asyncColumn.ColumnName);
- Assert.Equal(syncRow[syncColumn], asyncRow[asyncColumn]);
builder.Append(syncRow[syncColumn] + ", ");
}
From eb164d62c78278a22d4bf570877874a27db1d761 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Wed, 2 Jul 2025 22:14:34 +0100
Subject: [PATCH 7/8] Account for SqlDataReader merge and .NET Standard support
---
.../netcore/ref/Microsoft.Data.SqlClient.cs | 17 +++++++++++++----
.../Microsoft/Data/SqlClient/SqlDataReader.cs | 8 ++++++--
2 files changed, 19 insertions(+), 6 deletions(-)
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 b34c59f264..1fbb65e8f2 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -989,16 +989,25 @@ public event Microsoft.Data.SqlClient.SqlInfoMessageEventHandler InfoMessage { a
protected override System.Data.Common.DbCommand CreateDbCommand() { throw null; }
///
public override System.Data.DataTable GetSchema() { throw null; }
- ///
- public override System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.Data.DataTable GetSchema(string collectionName) { throw null; }
- ///
- public override System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { 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/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index eb9a0ddff5..62379a3bd7 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -1638,8 +1638,12 @@ public override DataTable GetSchemaTable()
}
}
- ///
+ ///
+#if NETFRAMEWORK
internal Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
+#else
+ public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
+#endif
{
// This method wraps GetSchemaTable in a Task, introducing async-over-sync. It should not be publicly exposed until
// this has been removed and replaced with an async path to guarantee that metadata has been read. Its purpose meanwhile
@@ -1659,7 +1663,7 @@ internal Task GetSchemaTableAsync(CancellationToken cancellationToken
}
}
- ///
+ ///
override public bool GetBoolean(int i)
{
ReadColumn(i);
From 5f05e45339915bc19b109ddb9809cc6656be99eb Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Fri, 4 Jul 2025 22:45:06 +0100
Subject: [PATCH 8/8] Response to code review
Remove SqlDataReader.GetSchemaTableAsync (and references to such.)
Expand test coverage of both GetSchema and GetSchemaAsync.
Correct typo in documentation for GetSchema and GetSchemaAsync.
---
.../SqlConnection.xml | 4 +-
.../SqlDataReader.xml | 20 -------
.../netfx/ref/Microsoft.Data.SqlClient.cs | 8 +--
.../Data/ProviderBase/DbMetaDataFactory.cs | 21 ++++---
.../Microsoft/Data/SqlClient/SqlDataReader.cs | 25 --------
.../SqlSchemaInfoTest/SqlSchemaInfoTest.cs | 60 +++++++++++++------
6 files changed, 62 insertions(+), 76 deletions(-)
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
index 03fea1eeff..d9911c3fc3 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
@@ -1357,7 +1357,7 @@ 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.
@@ -1368,7 +1368,7 @@ For more information on working with events, see [Connection Events](https://lea
The cancellation token.
- An asynchronous version of , which returns schema information for the data source of this . For more information about scheme, see SQL Server Schema Collections.
+ 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.
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
index 6adf12cf03..ff54d18a17 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml
@@ -794,26 +794,6 @@ The method retur
The is closed.
-
-
- The cancellation token.
-
-
- An asynchronous version of , which returns a that describes the column metadata of the .
-
-
- A task representing the asynchronous operation.
-
-
-
- For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming.
-
-
-
- The is closed.
-
-
-
The zero-based column ordinal.
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 65ca24f101..f646a748a2 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -876,14 +876,14 @@ public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction
public override void EnlistTransaction(System.Transactions.Transaction transaction) { }
///
public override System.Data.DataTable GetSchema() { throw null; }
- ///
- public System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
///
public override System.Data.DataTable GetSchema(string collectionName) { throw null; }
- ///
- public System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { 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; }
///
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 027ac6cec1..ba2fad0df6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs
@@ -176,18 +176,25 @@ private async ValueTask ExecuteCommandAsync(DataRow requestedCollecti
Locale = CultureInfo.InvariantCulture
};
- cancellationToken.ThrowIfCancellationRequested();
- DataTable schemaTable = isAsync ? await reader.GetSchemaTableAsync(cancellationToken).ConfigureAwait(false) : reader.GetSchemaTable();
+ // 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 (isAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : 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
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
index 62379a3bd7..d1f3f5c1a5 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs
@@ -1638,31 +1638,6 @@ public override DataTable GetSchemaTable()
}
}
- ///
-#if NETFRAMEWORK
- internal Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
-#else
- public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default)
-#endif
- {
- // This method wraps GetSchemaTable in a Task, introducing async-over-sync. It should not be publicly exposed until
- // this has been removed and replaced with an async path to guarantee that metadata has been read. Its purpose meanwhile
- // is to enable code sharing between netcore and netfx.
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
- }
-
- try
- {
- return Task.FromResult(GetSchemaTable());
- }
- catch (Exception ex)
- {
- return Task.FromException(ex);
- }
- }
-
///
override public bool GetBoolean(int i)
{
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 29b91985d1..a09d51d1fc 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs
@@ -23,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);
@@ -64,23 +76,35 @@ public static async Task TestGetSchemaAsync(bool openTransaction)
{
using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString))
{
- await conn.OpenAsync();
- DataTable dataBases;
+ SqlTransaction transaction = null;
- if (openTransaction)
+ await conn.OpenAsync();
+ try
{
- using (SqlTransaction transaction = conn.BeginTransaction())
+ if (openTransaction)
{
- dataBases = await conn.GetSchemaAsync("DATABASES");
+ 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);
}
- else
+ finally
{
- dataBases = await conn.GetSchemaAsync("DATABASES");
+ transaction?.Dispose();
}
- Assert.True(dataBases.Rows.Count > 0, "At least one database is expected");
-
DataTable metaDataCollections = await conn.GetSchemaAsync(DbMetaDataCollectionNames.MetaDataCollections);
Assert.True(metaDataCollections != null && metaDataCollections.Rows.Count > 0);