From 54a66cc06ccbda52f68d619fc9b518a2ff35b258 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 30 Nov 2025 05:09:13 +0000 Subject: [PATCH 01/14] Remove unnecessary references to DbMetaDataFactory --- .../Data/ProviderBase/DbConnectionInternal.cs | 2 +- .../Data/ProviderBase/DbMetaDataFactory.cs | 2 +- .../ConnectionPool/DbConnectionPoolGroup.cs | 14 +------------- .../Data/SqlClient/SqlConnectionFactory.cs | 6 +++--- 4 files changed, 6 insertions(+), 18 deletions(-) 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 3e27bf5305..939eefafef 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -863,7 +863,7 @@ protected internal virtual DataTable GetSchema( { Debug.Assert(outerConnection is not null, "outerConnection may not be null."); - DbMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this); + SqlMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this); Debug.Assert(metaDataFactory is not null, "metaDataFactory may not be null."); return metaDataFactory.GetSchema(outerConnection, collectionName, restrictions); 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..408a7a6440 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -341,7 +341,7 @@ private string GetParameterName(string neededCollectionName, int neededRestricti DataColumnCollection restrictionColumns = restrictionsTable.Columns; if (restrictionColumns != null) { - collectionName = restrictionColumns[DbMetaDataFactory.CollectionNameKey]; + collectionName = restrictionColumns[CollectionNameKey]; parameterName = restrictionColumns[ParameterNameKey]; restrictionName = restrictionColumns[RestrictionNameKey]; restrictionNumber = restrictionColumns[RestrictionNumberKey]; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 226a5749b4..3a7b01945a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -40,7 +40,6 @@ sealed internal class DbConnectionPoolGroup private int _state; // see PoolGroupState* below private DbConnectionPoolGroupProviderInfo _providerInfo; - private DbMetaDataFactory _metaDataFactory; private static int s_objectTypeCount; // EventSource counter @@ -95,18 +94,7 @@ internal DbConnectionPoolGroupProviderInfo ProviderInfo internal DbConnectionPoolGroupOptions PoolGroupOptions => _poolGroupOptions; - internal DbMetaDataFactory MetaDataFactory - { - get - { - return _metaDataFactory; - } - - set - { - _metaDataFactory = value; - } - } + internal SqlMetaDataFactory MetaDataFactory { get; set; } internal int Clear() { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 6f697d37e8..71ff121371 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -265,7 +265,7 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup( return connectionPoolGroup; } - internal DbMetaDataFactory GetMetaDataFactory( + internal SqlMetaDataFactory GetMetaDataFactory( DbConnectionPoolGroup poolGroup, DbConnectionInternal internalConnection) { @@ -273,7 +273,7 @@ internal DbMetaDataFactory GetMetaDataFactory( // Get the matadatafactory from the pool entry. If it does not already have one // create one and save it on the pool entry - DbMetaDataFactory metaDataFactory = poolGroup.MetaDataFactory; + SqlMetaDataFactory metaDataFactory = poolGroup.MetaDataFactory; // CONSIDER: serializing this so we don't construct multiple metadata factories // if two threads happen to hit this at the same time. One will be GC'd @@ -756,7 +756,7 @@ private static DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(Sql return poolingOptions; } - private static DbMetaDataFactory CreateMetaDataFactory( + private static SqlMetaDataFactory CreateMetaDataFactory( DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) { From 950069cd36b75998243f4d25aa9e5b2cefd4c5b9 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 30 Nov 2025 05:25:29 +0000 Subject: [PATCH 02/14] Remove unnecessary cacheMetaDataFactory parameter and logic --- .../Data/SqlClient/SqlConnectionFactory.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 71ff121371..9ed1159c6c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -271,22 +271,9 @@ internal SqlMetaDataFactory GetMetaDataFactory( { Debug.Assert(poolGroup is not null, "connectionPoolGroup may not be null."); - // Get the matadatafactory from the pool entry. If it does not already have one + // Get the metadata factory from the pool entry. If it does not already have one // create one and save it on the pool entry - SqlMetaDataFactory metaDataFactory = poolGroup.MetaDataFactory; - - // CONSIDER: serializing this so we don't construct multiple metadata factories - // if two threads happen to hit this at the same time. One will be GC'd - if (metaDataFactory is null) - { - metaDataFactory = CreateMetaDataFactory(internalConnection, out bool allowCache); - if (allowCache) - { - poolGroup.MetaDataFactory = metaDataFactory; - } - } - - return metaDataFactory; + return poolGroup.MetaDataFactory ??= CreateMetaDataFactory(internalConnection); } internal void QueuePoolForRelease(IDbConnectionPool pool, bool clearing) @@ -756,16 +743,13 @@ private static DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(Sql return poolingOptions; } - private static SqlMetaDataFactory CreateMetaDataFactory( - DbConnectionInternal internalConnection, - out bool cacheMetaDataFactory) + private static SqlMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection) { Debug.Assert(internalConnection is not null, "internalConnection may not be null."); Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml"); Debug.Assert(xmlStream is not null, $"{nameof(xmlStream)} may not be null."); - cacheMetaDataFactory = true; return new SqlMetaDataFactory(xmlStream, internalConnection.ServerVersion, internalConnection.ServerVersion); From 629fc1b061347511379ad983570482205eaf254d Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 30 Nov 2025 05:41:29 +0000 Subject: [PATCH 03/14] Remove unused capability to provide normalized server version SqlConnectionFactory always passed the same value to both parameters --- .../Data/ProviderBase/DbMetaDataFactory.cs | 22 ++++++------------- .../Data/SqlClient/SqlConnectionFactory.cs | 4 +--- .../Data/SqlClient/SqlMetaDataFactory.cs | 10 ++++----- 3 files changed, 12 insertions(+), 24 deletions(-) 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 408a7a6440..87c38b570c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -18,8 +18,6 @@ internal class DbMetaDataFactory { private DataSet _metaDataCollectionsDataSet; - private string _normalizedServerVersion; - private string _serverVersionString; // well known column names private const string CollectionNameKey = "CollectionName"; private const string PopulationMechanismKey = "PopulationMechanism"; @@ -37,23 +35,19 @@ internal class DbMetaDataFactory private const string SqlCommandKey = "SQLCommand"; private const string PrepareCollectionKey = "PrepareCollection"; - public DbMetaDataFactory(Stream xmlStream, string serverVersion, string normalizedServerVersion) + public DbMetaDataFactory(Stream xmlStream, string serverVersion) { ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); - ADP.CheckArgumentNull(normalizedServerVersion, nameof(normalizedServerVersion)); - _serverVersionString = serverVersion; - _normalizedServerVersion = normalizedServerVersion; + ServerVersion = serverVersion; LoadDataSetFromXml(xmlStream); } protected DataSet CollectionDataSet => _metaDataCollectionsDataSet; - protected string ServerVersion => _serverVersionString; - - protected string ServerVersionNormalized => _normalizedServerVersion; + protected string ServerVersion { get; } protected DataTable CloneAndFilterCollection(string collectionName, string[] hiddenColumnNames) { @@ -100,8 +94,6 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _normalizedServerVersion = null; - _serverVersionString = null; _metaDataCollectionsDataSet.Dispose(); } } @@ -321,8 +313,8 @@ private void FixUpDataSourceInformationRow(DataRow dataSourceInfoRow) Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersion)); Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersionNormalized)); - dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersion] = _serverVersionString; - dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = _normalizedServerVersion; + dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersion] = ServerVersion; + dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = ServerVersion; } @@ -722,7 +714,7 @@ private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) { if (version != DBNull.Value) { - if (0 > string.Compare(_normalizedServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) + if (0 > string.Compare(ServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) { result = false; } @@ -741,7 +733,7 @@ private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) { if (version != DBNull.Value) { - if (0 < string.Compare(_normalizedServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) + if (0 < string.Compare(ServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) { result = false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 9ed1159c6c..22eb0a3d06 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -750,9 +750,7 @@ private static SqlMetaDataFactory CreateMetaDataFactory(DbConnectionInternal int Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml"); Debug.Assert(xmlStream is not null, $"{nameof(xmlStream)} may not be null."); - return new SqlMetaDataFactory(xmlStream, - internalConnection.ServerVersion, - internalConnection.ServerVersion); + return new SqlMetaDataFactory(xmlStream, internalConnection.ServerVersion); } private Task CreateReplaceConnectionContinuation( 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..d20dfa7662 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -20,10 +20,8 @@ internal sealed class SqlMetaDataFactory : DbMetaDataFactory private const string ServerVersionNormalized10 = "10.00.0000"; private readonly HashSet _assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; - public SqlMetaDataFactory(Stream XMLStream, - string serverVersion, - string serverVersionNormalized) : - base(XMLStream, serverVersion, serverVersionNormalized) + public SqlMetaDataFactory(Stream XMLStream, string serverVersion) : + base(XMLStream, serverVersion) { } private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string ServerVersion) @@ -265,8 +263,8 @@ private DataTable GetDataTypesTable(SqlConnection connection) // 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); + addUDTsToDataTypesTable(dataTypesTable, connection, ServerVersion); + AddTVPsToDataTypesTable(dataTypesTable, connection, ServerVersion); dataTypesTable.AcceptChanges(); return dataTypesTable; From 3bbd21f404398cba4a475129fc32e709e7e00676 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 30 Nov 2025 05:51:38 +0000 Subject: [PATCH 04/14] Convert CollectionDataSet property to auto-property --- .../Data/ProviderBase/DbMetaDataFactory.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) 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 87c38b570c..bad0b4d6b6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -16,8 +16,6 @@ namespace Microsoft.Data.ProviderBase { internal class DbMetaDataFactory { - - private DataSet _metaDataCollectionsDataSet; // well known column names private const string CollectionNameKey = "CollectionName"; private const string PopulationMechanismKey = "PopulationMechanism"; @@ -42,10 +40,10 @@ public DbMetaDataFactory(Stream xmlStream, string serverVersion) ServerVersion = serverVersion; - LoadDataSetFromXml(xmlStream); + CollectionDataSet = LoadDataSetFromXml(xmlStream); } - protected DataSet CollectionDataSet => _metaDataCollectionsDataSet; + protected DataSet CollectionDataSet { get; } protected string ServerVersion { get; } @@ -56,7 +54,7 @@ protected DataTable CloneAndFilterCollection(string collectionName, string[] hid DataColumnCollection destinationColumns; DataRow newRow; - DataTable sourceTable = _metaDataCollectionsDataSet.Tables[collectionName]; + DataTable sourceTable = CollectionDataSet.Tables[collectionName]; if ((sourceTable == null) || (collectionName != sourceTable.TableName)) { @@ -94,13 +92,13 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _metaDataCollectionsDataSet.Dispose(); + CollectionDataSet.Dispose(); } } private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) { - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey]; DataColumn numberOfRestrictionsColumn = metaDataCollectionsTable.Columns[NumberOfRestrictionsKey]; DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[CollectionNameKey]; @@ -224,7 +222,7 @@ internal DataRow FindMetaDataCollectionRow(string collectionName) bool haveMultipleInexactMatches; string candidateCollectionName; - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; if (metaDataCollectionsTable == null) { throw ADP.InvalidXml(); @@ -327,7 +325,7 @@ private string GetParameterName(string neededCollectionName, int neededRestricti string result = null; - DataTable restrictionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; + DataTable restrictionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; if (restrictionsTable != null) { DataColumnCollection restrictionColumns = restrictionsTable.Columns; @@ -368,9 +366,9 @@ private string GetParameterName(string neededCollectionName, int neededRestricti public virtual DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) { - Debug.Assert(_metaDataCollectionsDataSet != null); + Debug.Assert(CollectionDataSet != null); - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[PopulationMechanismKey]; DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; @@ -468,7 +466,7 @@ private bool IncludeThisColumn(DataColumn sourceColumn, string[] hiddenColumnNam return result; } - private void LoadDataSetFromXml(Stream XmlStream) + private DataSet LoadDataSetFromXml(Stream XmlStream) { DataSet metaDataCollectionsDataSet = new DataSet("NewDataSet") { @@ -542,7 +540,7 @@ private void LoadDataSetFromXml(Stream XmlStream) } } - _metaDataCollectionsDataSet = metaDataCollectionsDataSet; + return metaDataCollectionsDataSet; } private static void LoadDataTable(XmlReader reader, DataTable table, Action rowFixup) From 798cc089093efe4fdcd8f0be48acef5551a5d32d Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:49:57 +0000 Subject: [PATCH 05/14] Cleanup of SqlMetaDataFactory prior to merge Convert the list of unsupported engines to a static variable. Make the UDT/TVP processing methods static. Change name of constants and ServerVersion parameter to fit naming conventions. Introduce constant for EngineEdition query. Reuse SqlCommand where possible. Remove layer of indentation for using block. Remove redundant client-side checks for null on fields which are declared as non-nullable within SQL Server. Slightly simplify building of assembly qualified names. Convert checks whether columns exist in a DataTable (and DataTables in a DataSet) to debug assertions, since these are statically built now. --- .../Data/SqlClient/SqlMetaDataFactory.cs | 264 ++++++++---------- 1 file changed, 113 insertions(+), 151 deletions(-) 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 d20dfa7662..e67295af2d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Diagnostics; using System.IO; using System.Text; using Microsoft.Data.Common; @@ -18,15 +19,16 @@ internal sealed class SqlMetaDataFactory : DbMetaDataFactory private const string ServerVersionNormalized90 = "09.00.0000"; private const string ServerVersionNormalized10 = "10.00.0000"; - private readonly HashSet _assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; + private static readonly HashSet s_assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; public SqlMetaDataFactory(Stream XMLStream, string serverVersion) : base(XMLStream, serverVersion) { } - private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string ServerVersion) + private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) { - const string sqlCommand = + const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; + const string ListUdtSqlCommand = "select " + "assemblies.name, " + "types.assembly_class, " + @@ -41,18 +43,30 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con "max_length " + "from sys.assemblies as assemblies join sys.assembly_types as types " + "on assemblies.assembly_id = types.assembly_id "; + const int AssemblyNameSqlIndex = 0; + const int AssemblyClassSqlIndex = 1; + const int VersionMajorSqlIndex = 2; + const int VersionMinorSqlIndex = 3; + const int VersionBuildSqlIndex = 4; + const int VersionRevisionSqlIndex = 5; + const int CultureInfoSqlIndex = 6; + const int PublicKeySqlIndex = 7; + const int IsNullableSqlIndex = 8; + const int IsFixedLengthSqlIndex = 9; + const int ColumnSizeSqlIndex = 10; // pre 9.0/2005 servers do not have UDTs - if (0 > string.Compare(ServerVersion, ServerVersionNormalized90, StringComparison.OrdinalIgnoreCase)) + if (0 > string.Compare(serverVersion, ServerVersionNormalized90, StringComparison.OrdinalIgnoreCase)) { return; } - SqlCommand engineEditionCommand = connection.CreateCommand(); - engineEditionCommand.CommandText = "SELECT SERVERPROPERTY('EngineEdition');"; - var engineEdition = (int)engineEditionCommand.ExecuteScalar(); + using SqlCommand command = connection.CreateCommand(); - if (_assemblyPropertyUnsupportedEngines.Contains(engineEdition)) + command.CommandText = GetEngineEditionSqlCommand; + int engineEdition = (int)command.ExecuteScalar(); + + if (s_assemblyPropertyUnsupportedEngines.Contains(engineEdition)) { // Azure SQL Edge (9) throws an exception when querying sys.assemblies // Azure Synapse Analytics (6) and Azure Synapse serverless SQL pool (11) @@ -60,10 +74,7 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con return; } - // Execute the SELECT statement - SqlCommand command = connection.CreateCommand(); - command.CommandText = sqlCommand; - DataColumn providerDbtype = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; + DataColumn providerDbType = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; DataColumn columnSize = dataTypesTable.Columns[DbMetaDataColumnNames.ColumnSize]; DataColumn isFixedLength = dataTypesTable.Columns[DbMetaDataColumnNames.IsFixedLength]; DataColumn isSearchable = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchable]; @@ -71,199 +82,154 @@ private void addUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection con DataColumn typeName = dataTypesTable.Columns[DbMetaDataColumnNames.TypeName]; DataColumn isNullable = dataTypesTable.Columns[DbMetaDataColumnNames.IsNullable]; - if ((providerDbtype == null) || - (columnSize == null) || - (isFixedLength == null) || - (isSearchable == null) || - (isLiteralSupported == null) || - (typeName == null) || - (isNullable == null)) - { - throw ADP.InvalidXml(); - } + Debug.Assert(providerDbType is not null, "providerDbType column not found"); + Debug.Assert(columnSize is not null, "columnSize column not found"); + Debug.Assert(isFixedLength is not null, "isFixedLength column not found"); + Debug.Assert(isSearchable is not null, "isSearchable column not found"); + Debug.Assert(isLiteralSupported is not null, "isLiteralSupported column not found"); + Debug.Assert(typeName is not null, "typeName column not found"); + Debug.Assert(isNullable is not null, "isNullable column not found"); - const int columnSizeIndex = 10; - const int isFixedLengthIndex = 9; - const int isNullableIndex = 8; - const int assemblyNameIndex = 0; - const int assemblyClassIndex = 1; - const int versionMajorIndex = 2; - const int versionMinorIndex = 3; - const int versionBuildIndex = 4; - const int versionRevisionIndex = 5; - const int cultureInfoIndex = 6; - const int publicKeyIndex = 7; + // Execute the SELECT statement + command.CommandText = ListUdtSqlCommand; + using SqlDataReader reader = command.ExecuteReader(); + object[] values = new object[11]; - using (IDataReader reader = command.ExecuteReader()) + while (reader.Read()) { + reader.GetValues(values); + DataRow newRow = dataTypesTable.NewRow(); - object[] values = new object[11]; - while (reader.Read()) - { + newRow[providerDbType] = SqlDbType.Udt; + newRow[columnSize] = values[ColumnSizeSqlIndex]; - reader.GetValues(values); - DataRow newRow = dataTypesTable.NewRow(); + if (values[IsFixedLengthSqlIndex] != DBNull.Value) + { + newRow[isFixedLength] = values[IsFixedLengthSqlIndex]; + } - newRow[providerDbtype] = SqlDbType.Udt; + newRow[isSearchable] = true; + newRow[isLiteralSupported] = false; + if (values[IsNullableSqlIndex] != DBNull.Value) + { + newRow[isNullable] = values[IsNullableSqlIndex]; + } - if (values[columnSizeIndex] != DBNull.Value) - { - newRow[columnSize] = values[columnSizeIndex]; - } + if ((values[AssemblyClassSqlIndex] != DBNull.Value) && + (values[VersionMajorSqlIndex] != DBNull.Value) && + (values[VersionMinorSqlIndex] != DBNull.Value) && + (values[VersionBuildSqlIndex] != DBNull.Value) && + (values[VersionRevisionSqlIndex] != DBNull.Value)) + { - if (values[isFixedLengthIndex] != DBNull.Value) - { - newRow[isFixedLength] = values[isFixedLengthIndex]; - } + StringBuilder nameString = new(); + nameString.Append($"{values[AssemblyClassSqlIndex]}, {values[AssemblyNameSqlIndex]}" + + $", Version={values[VersionMajorSqlIndex]}.{values[VersionMinorSqlIndex]}.{values[VersionBuildSqlIndex]}.{values[VersionRevisionSqlIndex]}"); - newRow[isSearchable] = true; - newRow[isLiteralSupported] = false; - if (values[isNullableIndex] != DBNull.Value) + if (values[CultureInfoSqlIndex] != DBNull.Value) { - newRow[isNullable] = values[isNullableIndex]; + nameString.Append($", Culture={values[CultureInfoSqlIndex]}"); } - if ((values[assemblyNameIndex] != DBNull.Value) && - (values[assemblyClassIndex] != DBNull.Value) && - (values[versionMajorIndex] != DBNull.Value) && - (values[versionMinorIndex] != DBNull.Value) && - (values[versionBuildIndex] != DBNull.Value) && - (values[versionRevisionIndex] != DBNull.Value)) + if (values[PublicKeySqlIndex] != DBNull.Value) { - StringBuilder nameString = new(); - nameString.Append(values[assemblyClassIndex].ToString()); - nameString.Append(", "); - nameString.Append(values[assemblyNameIndex].ToString()); - nameString.Append(", Version="); - - nameString.Append(values[versionMajorIndex].ToString()); - nameString.Append("."); - nameString.Append(values[versionMinorIndex].ToString()); - nameString.Append("."); - nameString.Append(values[versionBuildIndex].ToString()); - nameString.Append("."); - nameString.Append(values[versionRevisionIndex].ToString()); - - if (values[cultureInfoIndex] != DBNull.Value) - { - nameString.Append(", Culture="); - nameString.Append(values[cultureInfoIndex].ToString()); - } + nameString.Append(", PublicKeyToken="); - if (values[publicKeyIndex] != DBNull.Value) + byte[] byteArrayValue = (byte[])values[PublicKeySqlIndex]; +#if NET9_0_OR_GREATER + nameString.Append(Convert.ToHexStringLower(byteArrayValue)); +#elif NET8_0 + nameString.Append(Convert.ToHexString(byteArrayValue).ToLowerInvariant()); +#else + foreach (byte b in byteArrayValue) { - - nameString.Append(", PublicKeyToken="); - - StringBuilder resultString = new(); - byte[] byteArrayValue = (byte[])values[publicKeyIndex]; - foreach (byte b in byteArrayValue) - { - resultString.Append(string.Format("{0,-2:x2}", b)); - } - nameString.Append(resultString.ToString()); + nameString.Append(string.Format("{0,-2:x2}", b)); } +#endif + } - newRow[typeName] = nameString.ToString(); - dataTypesTable.Rows.Add(newRow); - newRow.AcceptChanges(); - } // if assembly name - - }//end while - } // end using + newRow[typeName] = nameString.ToString(); + dataTypesTable.Rows.Add(newRow); + newRow.AcceptChanges(); + } // if assembly name + }//end while } - private void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string ServerVersion) + private static void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) { - - const string sqlCommand = + const string ListTvpsSqlCommand = "select " + "name, " + "is_nullable, " + "max_length " + "from sys.types " + "where is_table_type = 1"; + const int TypeNameSqlIndex = 0; + const int IsNullableSqlIndex = 1; + const int ColumnSizeSqlIndex = 2; // TODO: update this check once the server upgrades major version number!!! // pre 9.0/2005 servers do not have Table types - if (0 > string.Compare(ServerVersion, ServerVersionNormalized10, StringComparison.OrdinalIgnoreCase)) + if (0 > string.Compare(serverVersion, ServerVersionNormalized10, StringComparison.OrdinalIgnoreCase)) { return; } // Execute the SELECT statement - SqlCommand command = connection.CreateCommand(); - command.CommandText = sqlCommand; - DataColumn providerDbtype = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; + using SqlCommand command = connection.CreateCommand(); + command.CommandText = ListTvpsSqlCommand; + + DataColumn providerDbType = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType]; DataColumn columnSize = dataTypesTable.Columns[DbMetaDataColumnNames.ColumnSize]; DataColumn isSearchable = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchable]; DataColumn isLiteralSupported = dataTypesTable.Columns[DbMetaDataColumnNames.IsLiteralSupported]; DataColumn typeName = dataTypesTable.Columns[DbMetaDataColumnNames.TypeName]; DataColumn isNullable = dataTypesTable.Columns[DbMetaDataColumnNames.IsNullable]; - if ((providerDbtype == null) || - (columnSize == null) || - (isSearchable == null) || - (isLiteralSupported == null) || - (typeName == null) || - (isNullable == null)) - { - throw ADP.InvalidXml(); - } + Debug.Assert(providerDbType is not null, "providerDbType column not found"); + Debug.Assert(columnSize is not null, "columnSize column not found"); + Debug.Assert(isSearchable is not null, "isSearchable column not found"); + Debug.Assert(isLiteralSupported is not null, "isLiteralSupported column not found"); + Debug.Assert(typeName is not null, "typeName column not found"); + Debug.Assert(isNullable is not null, "isNullable column not found"); - const int columnSizeIndex = 2; - const int isNullableIndex = 1; - const int typeNameIndex = 0; + using SqlDataReader reader = command.ExecuteReader(); - using (IDataReader reader = command.ExecuteReader()) + object[] values = new object[11]; + while (reader.Read()) { - object[] values = new object[11]; - while (reader.Read()) - { - - reader.GetValues(values); - DataRow newRow = dataTypesTable.NewRow(); + reader.GetValues(values); + DataRow newRow = dataTypesTable.NewRow(); - newRow[providerDbtype] = SqlDbType.Structured; + newRow[providerDbType] = SqlDbType.Structured; + newRow[columnSize] = values[ColumnSizeSqlIndex]; + newRow[isSearchable] = false; + newRow[isLiteralSupported] = false; - if (values[columnSizeIndex] != DBNull.Value) - { - newRow[columnSize] = values[columnSizeIndex]; - } + if (values[IsNullableSqlIndex] != DBNull.Value) + { + newRow[isNullable] = values[IsNullableSqlIndex]; + } - newRow[isSearchable] = false; - newRow[isLiteralSupported] = false; - if (values[isNullableIndex] != DBNull.Value) - { - newRow[isNullable] = values[isNullableIndex]; - } + newRow[typeName] = values[TypeNameSqlIndex]; - if (values[typeNameIndex] != DBNull.Value) - { - newRow[typeName] = values[typeNameIndex]; - dataTypesTable.Rows.Add(newRow); - newRow.AcceptChanges(); - } // if type name - }//end while - } // end using + dataTypesTable.Rows.Add(newRow); + newRow.AcceptChanges(); + }//end while } private DataTable GetDataTypesTable(SqlConnection connection) { // verify the existence of the table in the data set - DataTable dataTypesTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.DataTypes]; - if (dataTypesTable == null) - { - throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.DataTypes); - } + Debug.Assert(CollectionDataSet.Tables.Contains(DbMetaDataCollectionNames.DataTypes), "DataTypes collection not found in the DataSet"); // copy the table filtering out any rows that don't apply to tho current version of the provider - dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, null); + DataTable dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, null); - addUDTsToDataTypesTable(dataTypesTable, connection, ServerVersion); + AddUDTsToDataTypesTable(dataTypesTable, connection, ServerVersion); AddTVPsToDataTypesTable(dataTypesTable, connection, ServerVersion); dataTypesTable.AcceptChanges(); @@ -290,10 +256,6 @@ protected override DataTable PrepareCollection(string collectionName, string[] r } return resultTable; - } - - - } } From dd3b71113e8bc2a68ebd8ffcd3852ec8dc7988ab Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:57:02 +0000 Subject: [PATCH 06/14] Cleanup of DbMetaDataFactory prior to merge Move constants which were only used in one method to that method. Replace an unnecessary string[] allocation with a stack-allocated ReadOnlySpan. Where appropriate, use ?.. Replace == null and != null with pattern matching. Convert checks whether columns exist in a DataTable (and DataTables in a DataSet) to debug assertions, since these are statically built now. Replace use of GetSchemaTable with GetColumnSchema to reduce allocations. Where appropriate, make methods private and/or static. Remove unnecessary ADP.CompareInsensitiveInvariant helper. Simplify IncludeThisColumn to make use of Contains or IndexOf where necessary. --- .../src/Microsoft/Data/Common/AdapterUtil.cs | 13 - .../Data/ProviderBase/DbMetaDataFactory.cs | 250 +++++++----------- .../Data/SqlClient/SqlMetaDataFactory.cs | 2 +- 3 files changed, 93 insertions(+), 172 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index b37f97ac17..72c6d65841 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -1076,12 +1076,6 @@ internal static IndexOutOfRangeException InvalidBufferSizeOrIndex(int numBytes, internal static Exception InvalidDataLength(long length) => IndexOutOfRange(StringsHelper.GetString(Strings.SQL_InvalidDataLength, length.ToString(CultureInfo.InvariantCulture))); - internal static bool CompareInsensitiveInvariant(string strvalue, string strconst) - => 0 == CultureInfo.InvariantCulture.CompareInfo.Compare(strvalue, strconst, CompareOptions.IgnoreCase); - - internal static int DstCompare(string strA, string strB) // this is null safe - => CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, ADP.DefaultCompareOptions); - internal static void SetCurrentTransaction(Transaction transaction) => Transaction.Current = transaction; internal static Exception NonSeqByteAccess(long badIndex, long currIndex, string method) @@ -1092,9 +1086,6 @@ internal static Exception NonSeqByteAccess(long badIndex, long currIndex, string internal static Exception NegativeParameter(string parameterName) => InvalidOperation(StringsHelper.GetString(Strings.ADP_NegativeParameter, parameterName)); - internal static Exception InvalidXmlMissingColumn(string collectionName, string columnName) - => Argument(StringsHelper.GetString(Strings.MDF_InvalidXmlMissingColumn, collectionName, columnName)); - internal static InvalidOperationException AsyncOperationPending() => InvalidOperation(StringsHelper.GetString(Strings.ADP_PendingAsyncOperation)); #endregion @@ -1218,8 +1209,6 @@ internal static InvalidOperationException OpenConnectionRequired(string method, internal static Exception OpenReaderExists(Exception e, bool marsOn) => InvalidOperation(StringsHelper.GetString(Strings.ADP_OpenReaderExists, marsOn ? ADP.Command : ADP.Connection), e); - internal static Exception InvalidXml() => Argument(StringsHelper.GetString(Strings.MDF_InvalidXml)); - internal static Exception InvalidXmlInvalidValue(string collectionName, string columnName) => Argument(StringsHelper.GetString(Strings.MDF_InvalidXmlInvalidValue, collectionName, columnName)); @@ -1242,8 +1231,6 @@ internal static Exception AmbiguousCollectionName(string collectionName) internal static Exception IncorrectNumberOfDataSourceInformationRows() => Argument(StringsHelper.GetString(Strings.MDF_IncorrectNumberOfDataSourceInformationRows)); - internal static Exception MissingRestrictionColumn() => Argument(StringsHelper.GetString(Strings.MDF_MissingRestrictionColumn)); - internal static Exception MissingRestrictionRow() => Argument(StringsHelper.GetString(Strings.MDF_MissingRestrictionRow)); internal static Exception UndefinedPopulationMechanism(string populationMechanism) 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 bad0b4d6b6..d34d8ee93a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -28,11 +28,6 @@ internal class DbMetaDataFactory private const string RestrictionNameKey = "RestrictionName"; private const string ParameterNameKey = "ParameterName"; - // population mechanisms - private const string DataTableKey = "DataTable"; - private const string SqlCommandKey = "SQLCommand"; - private const string PrepareCollectionKey = "PrepareCollection"; - public DbMetaDataFactory(Stream xmlStream, string serverVersion) { ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); @@ -47,7 +42,7 @@ public DbMetaDataFactory(Stream xmlStream, string serverVersion) protected string ServerVersion { get; } - protected DataTable CloneAndFilterCollection(string collectionName, string[] hiddenColumnNames) + protected DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) { DataTable destinationTable; DataColumn[] filteredSourceColumns; @@ -56,7 +51,7 @@ protected DataTable CloneAndFilterCollection(string collectionName, string[] hid DataTable sourceTable = CollectionDataSet.Tables[collectionName]; - if ((sourceTable == null) || (collectionName != sourceTable.TableName)) + if (sourceTable?.TableName != collectionName) { throw ADP.DataTableDoesNotExist(collectionName); } @@ -98,25 +93,25 @@ protected virtual void Dispose(bool disposing) private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) { + Debug.Assert(requestedCollectionRow is not null); + DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey]; DataColumn numberOfRestrictionsColumn = metaDataCollectionsTable.Columns[NumberOfRestrictionsKey]; DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[CollectionNameKey]; DataTable resultTable = null; - - Debug.Assert(requestedCollectionRow != null); string sqlCommand = requestedCollectionRow[populationStringColumn, DataRowVersion.Current] as string; int numberOfRestrictions = (int)requestedCollectionRow[numberOfRestrictionsColumn, DataRowVersion.Current]; string collectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; - if ((restrictions != null) && (restrictions.Length > numberOfRestrictions)) + if ((restrictions is not null) && (restrictions.Length > numberOfRestrictions)) { throw ADP.TooManyRestrictions(collectionName); } - DbCommand command = connection.CreateCommand(); SqlConnection castConnection = connection as SqlConnection; + using SqlCommand command = castConnection.CreateCommand(); command.CommandText = sqlCommand; command.CommandTimeout = Math.Max(command.CommandTimeout, 180); @@ -124,10 +119,9 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri for (int i = 0; i < numberOfRestrictions; i++) { + SqlParameter restrictionParameter = command.CreateParameter(); - DbParameter restrictionParameter = command.CreateParameter(); - - if ((restrictions != null) && (restrictions.Length > i) && (restrictions[i] != null)) + if ((restrictions is not null) && (i < restrictions.Length) && (restrictions[i] is not null)) { restrictionParameter.Value = restrictions[i]; } @@ -142,7 +136,7 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri command.Parameters.Add(restrictionParameter); } - DbDataReader reader = null; + SqlDataReader reader = null; try { try @@ -164,10 +158,10 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri Locale = CultureInfo.InvariantCulture }; - DataTable schemaTable = reader.GetSchemaTable(); - foreach (DataRow row in schemaTable.Rows) + System.Collections.ObjectModel.ReadOnlyCollection colSchema = reader.GetColumnSchema(); + foreach (DbColumn col in colSchema) { - resultTable.Columns.Add(row["ColumnName"] as string, (Type)row["DataType"] as Type); + resultTable.Columns.Add(col.ColumnName, col.DataType); } object[] values = new object[resultTable.Columns.Count]; while (reader.Read()) @@ -183,7 +177,7 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri return resultTable; } - private DataColumn[] FilterColumns(DataTable sourceTable, string[] hiddenColumnNames, DataColumnCollection destinationColumns) + private static DataColumn[] FilterColumns(DataTable sourceTable, ReadOnlySpan hiddenColumnNames, DataColumnCollection destinationColumns) { int columnCount = 0; foreach (DataColumn sourceColumn in sourceTable.Columns) @@ -215,77 +209,62 @@ private DataColumn[] FilterColumns(DataTable sourceTable, string[] hiddenColumnN return filteredSourceColumns; } - internal DataRow FindMetaDataCollectionRow(string collectionName) + private DataRow FindMetaDataCollectionRow(string collectionName) { - bool versionFailure; - bool haveExactMatch; - bool haveMultipleInexactMatches; - string candidateCollectionName; + bool versionFailure = false; + bool haveExactMatch = false; + bool haveMultipleInexactMatches = false; DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - if (metaDataCollectionsTable == null) - { - throw ADP.InvalidXml(); - } + Debug.Assert(metaDataCollectionsTable is not null); DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; - - if (collectionNameColumn == null || (typeof(string) != collectionNameColumn.DataType)) - { - throw ADP.InvalidXmlMissingColumn(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); - } + Debug.Assert(collectionNameColumn is not null && collectionNameColumn.DataType == typeof(string)); DataRow requestedCollectionRow = null; string exactCollectionName = null; // find the requested collection - versionFailure = false; - haveExactMatch = false; - haveMultipleInexactMatches = false; - foreach (DataRow row in metaDataCollectionsTable.Rows) { + string candidateCollectionName = row[collectionNameColumn, DataRowVersion.Current] as string; - candidateCollectionName = row[collectionNameColumn, DataRowVersion.Current] as string; if (string.IsNullOrEmpty(candidateCollectionName)) { throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); } - if (ADP.CompareInsensitiveInvariant(candidateCollectionName, collectionName)) + if (string.Equals(candidateCollectionName, collectionName, StringComparison.InvariantCultureIgnoreCase)) { if (!SupportedByCurrentVersion(row)) { versionFailure = true; } - else + else if (collectionName == candidateCollectionName) { - if (collectionName == candidateCollectionName) + if (haveExactMatch) { - if (haveExactMatch) - { - throw ADP.CollectionNameIsNotUnique(collectionName); - } - requestedCollectionRow = row; - exactCollectionName = candidateCollectionName; - haveExactMatch = true; + throw ADP.CollectionNameIsNotUnique(collectionName); } - else if (!haveExactMatch) + requestedCollectionRow = row; + exactCollectionName = candidateCollectionName; + haveExactMatch = true; + } + else if (!haveExactMatch) + { + // have an inexact match - ok only if it is the only one + if (exactCollectionName is not null) { - // have an inexact match - ok only if it is the only one - if (exactCollectionName != null) - { - // can't fail here becasue we may still find an exact match - haveMultipleInexactMatches = true; - } - requestedCollectionRow = row; - exactCollectionName = candidateCollectionName; + // can't fail here because we may still find an exact match + haveMultipleInexactMatches = true; } + requestedCollectionRow = row; + exactCollectionName = candidateCollectionName; } } } - if (requestedCollectionRow == null) + if (requestedCollectionRow is null) { if (!versionFailure) { @@ -315,33 +294,24 @@ private void FixUpDataSourceInformationRow(DataRow dataSourceInfoRow) dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = ServerVersion; } - private string GetParameterName(string neededCollectionName, int neededRestrictionNumber) { - DataColumn collectionName = null; - DataColumn parameterName = null; - DataColumn restrictionName = null; - DataColumn restrictionNumber = null; + DataTable restrictionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; - string result = null; + Debug.Assert(restrictionsTable is not null); - DataTable restrictionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; - if (restrictionsTable != null) - { - DataColumnCollection restrictionColumns = restrictionsTable.Columns; - if (restrictionColumns != null) - { - collectionName = restrictionColumns[CollectionNameKey]; - parameterName = restrictionColumns[ParameterNameKey]; - restrictionName = restrictionColumns[RestrictionNameKey]; - restrictionNumber = restrictionColumns[RestrictionNumberKey]; - } - } + DataColumnCollection restrictionColumns = restrictionsTable.Columns; + DataColumn collectionName = restrictionColumns[CollectionNameKey]; + DataColumn parameterName = restrictionColumns[ParameterNameKey]; + DataColumn restrictionName = restrictionColumns[RestrictionNameKey]; + DataColumn restrictionNumber = restrictionColumns[RestrictionNumberKey]; - if ((parameterName == null) || (collectionName == null) || (restrictionName == null) || (restrictionNumber == null)) - { - throw ADP.MissingRestrictionColumn(); - } + Debug.Assert(parameterName is not null); + Debug.Assert(collectionName is not null); + Debug.Assert(restrictionName is not null); + Debug.Assert(restrictionNumber is not null); + + string result = null; foreach (DataRow restriction in restrictionsTable.Rows) { @@ -356,7 +326,7 @@ private string GetParameterName(string neededCollectionName, int neededRestricti } } - if (result == null) + if (result is null) { throw ADP.MissingRestrictionRow(); } @@ -366,14 +336,15 @@ private string GetParameterName(string neededCollectionName, int neededRestricti public virtual DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) { - Debug.Assert(CollectionDataSet != null); + const string DataTableKey = "DataTable"; + const string SqlCommandKey = "SQLCommand"; + const string PrepareCollectionKey = "PrepareCollection"; + + Debug.Assert(CollectionDataSet is not null); DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[PopulationMechanismKey]; DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; - - string[] hiddenColumns; - DataRow requestedCollectionRow = FindMetaDataCollectionRow(collectionName); string exactCollectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; @@ -382,7 +353,7 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam for (int i = 0; i < restrictions.Length; i++) { - if ((restrictions[i] != null) && (restrictions[i].Length > 4096)) + if ((restrictions[i] is not null) && (restrictions[i].Length > 4096)) { // use a non-specific error because no new beta 2 error messages are allowed // TODO: will add a more descriptive error in RTM @@ -396,25 +367,17 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam DataTable requestedSchema; switch (populationMechanism) { - case DataTableKey: - if (exactCollectionName == DbMetaDataCollectionNames.MetaDataCollections) - { - hiddenColumns = new string[2]; - hiddenColumns[0] = PopulationMechanismKey; - hiddenColumns[1] = PopulationStringKey; - } - else - { - hiddenColumns = null; - } + ReadOnlySpan hiddenColumns = exactCollectionName == DbMetaDataCollectionNames.MetaDataCollections + ? [ PopulationMechanismKey, PopulationStringKey ] + : []; + // none of the datatable collections support restrictions if (!ADP.IsEmptyArray(restrictions)) { throw ADP.TooManyRestrictions(exactCollectionName); } - requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns); break; @@ -433,42 +396,24 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam return requestedSchema; } - private bool IncludeThisColumn(DataColumn sourceColumn, string[] hiddenColumnNames) + private static bool IncludeThisColumn(DataColumn sourceColumn, ReadOnlySpan hiddenColumnNames) { - - bool result = true; string sourceColumnName = sourceColumn.ColumnName; - switch (sourceColumnName) + return sourceColumnName switch { - - case MinimumVersionKey: - case MaximumVersionKey: - result = false; - break; - - default: - if (hiddenColumnNames == null) - { - break; - } - for (int i = 0; i < hiddenColumnNames.Length; i++) - { - if (hiddenColumnNames[i] == sourceColumnName) - { - result = false; - break; - } - } - break; - } - - return result; + MinimumVersionKey or MaximumVersionKey => false, +#if NET + _ => !hiddenColumnNames.Contains(sourceColumnName), +#else + _ => hiddenColumnNames.IndexOf(sourceColumnName) == -1, +#endif + }; } private DataSet LoadDataSetFromXml(Stream XmlStream) { - DataSet metaDataCollectionsDataSet = new DataSet("NewDataSet") + DataSet metaDataCollectionsDataSet = new("NewDataSet") { Locale = CultureInfo.InvariantCulture }; @@ -532,7 +477,7 @@ private DataSet LoadDataSetFromXml(Stream XmlStream) break; } - if (dataTable != null) + if (dataTable is not null) { LoadDataTable(reader, dataTable, rowFixup); @@ -569,7 +514,7 @@ private static void LoadDataTable(XmlReader reader, DataTable table, Action new DataTable(DbMetaDataCollectionNames.MetaDataCollections) + => new(DbMetaDataCollectionNames.MetaDataCollections) { Columns = { @@ -609,7 +554,7 @@ private static DataTable CreateMetaDataCollectionsDataTable() }; private static DataTable CreateRestrictionsDataTable() - => new DataTable(DbMetaDataCollectionNames.Restrictions) + => new(DbMetaDataCollectionNames.Restrictions) { Columns = { @@ -624,7 +569,7 @@ private static DataTable CreateRestrictionsDataTable() }; private static DataTable CreateDataSourceInformationDataTable() - => new DataTable(DbMetaDataCollectionNames.DataSourceInformation) + => new(DbMetaDataCollectionNames.DataSourceInformation) { Columns = { @@ -649,7 +594,7 @@ private static DataTable CreateDataSourceInformationDataTable() }; private static DataTable CreateDataTypesDataTable() - => new DataTable(DbMetaDataCollectionNames.DataTypes) + => new(DbMetaDataCollectionNames.DataTypes) { Columns = { @@ -681,7 +626,7 @@ private static DataTable CreateDataTypesDataTable() }; private static DataTable CreateReservedWordsDataTable() - => new DataTable(DbMetaDataCollectionNames.ReservedWords) + => new(DbMetaDataCollectionNames.ReservedWords) { Columns = { @@ -698,48 +643,37 @@ protected virtual DataTable PrepareCollection(string collectionName, string[] re private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) { - bool result = true; DataColumnCollection tableColumns = requestedCollectionRow.Table.Columns; DataColumn versionColumn; object version; // check the minimum version first versionColumn = tableColumns[MinimumVersionKey]; - if (versionColumn != null) + if (versionColumn is not null) { version = requestedCollectionRow[versionColumn]; - if (version != null) + + if (version is string minVersion + && string.Compare(ServerVersion, minVersion, StringComparison.OrdinalIgnoreCase) < 0) { - if (version != DBNull.Value) - { - if (0 > string.Compare(ServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) - { - result = false; - } - } + return false; } } // if the minimum version was ok what about the maximum version - if (result) + versionColumn = tableColumns[MaximumVersionKey]; + if (versionColumn is not null) { - versionColumn = tableColumns[MaximumVersionKey]; - if (versionColumn != null) + version = requestedCollectionRow[versionColumn]; + + if (version is string maxVersion + && string.Compare(ServerVersion, maxVersion, StringComparison.OrdinalIgnoreCase) > 0) { - version = requestedCollectionRow[versionColumn]; - if (version != null) - { - if (version != DBNull.Value) - { - if (0 < string.Compare(ServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) - { - result = false; - } - } - } + return false; } } - return result; + + return true; } } } 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 e67295af2d..1296fd39f1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -227,7 +227,7 @@ private DataTable GetDataTypesTable(SqlConnection connection) Debug.Assert(CollectionDataSet.Tables.Contains(DbMetaDataCollectionNames.DataTypes), "DataTypes collection not found in the DataSet"); // copy the table filtering out any rows that don't apply to tho current version of the provider - DataTable dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, null); + DataTable dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, []); AddUDTsToDataTypesTable(dataTypesTable, connection, ServerVersion); AddTVPsToDataTypesTable(dataTypesTable, connection, ServerVersion); From 93c26f90e90cf69cd4a34948ebead8572cf54d9e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:07:27 +0000 Subject: [PATCH 07/14] Merge logic to create CollectionDataSet --- .../Data/ProviderBase/DbMetaDataFactory.cs | 248 +--------------- .../Data/SqlClient/SqlMetaDataFactory.cs | 265 +++++++++++++++++- 2 files changed, 263 insertions(+), 250 deletions(-) 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 d34d8ee93a..e9ba4233b1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -28,19 +28,9 @@ internal class DbMetaDataFactory private const string RestrictionNameKey = "RestrictionName"; private const string ParameterNameKey = "ParameterName"; - public DbMetaDataFactory(Stream xmlStream, string serverVersion) - { - ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); - ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); - - ServerVersion = serverVersion; - - CollectionDataSet = LoadDataSetFromXml(xmlStream); - } - - protected DataSet CollectionDataSet { get; } + protected DataSet CollectionDataSet { get; set; } - protected string ServerVersion { get; } + protected string ServerVersion { get; set; } protected DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) { @@ -285,15 +275,6 @@ private DataRow FindMetaDataCollectionRow(string collectionName) } - private void FixUpDataSourceInformationRow(DataRow dataSourceInfoRow) - { - Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersion)); - Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersionNormalized)); - - dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersion] = ServerVersion; - dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = ServerVersion; - } - private string GetParameterName(string neededCollectionName, int neededRestrictionNumber) { DataTable restrictionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; @@ -411,231 +392,6 @@ private static bool IncludeThisColumn(DataColumn sourceColumn, ReadOnlySpan rowFixup = null; - - Debug.Assert(reader.NodeType == XmlNodeType.Element); - - switch (reader.Name) - { - case "MetaDataCollectionsTable": - dataTable = CreateMetaDataCollectionsDataTable(); - break; - case "RestrictionsTable": - dataTable = CreateRestrictionsDataTable(); - break; - case "DataSourceInformationTable": - dataTable = CreateDataSourceInformationDataTable(); - rowFixup = FixUpDataSourceInformationRow; - break; - case "DataTypesTable": - dataTable = CreateDataTypesDataTable(); - break; - case "ReservedWordsTable": - dataTable = CreateReservedWordsDataTable(); - break; - default: - Debug.Fail($"Unexpected table element name: {reader.Name}"); - break; - } - - if (dataTable is not null) - { - LoadDataTable(reader, dataTable, rowFixup); - - metaDataCollectionsDataSet.Tables.Add(dataTable); - } - } - - return metaDataCollectionsDataSet; - } - - private static void LoadDataTable(XmlReader reader, DataTable table, Action rowFixup) - { - int parentDepth = reader.Depth; - - table.BeginLoadData(); - - // One outer loop per element, each loop reading every property of the row - while (reader.Read() - && reader.Depth == parentDepth + 1) - { - Debug.Assert(reader.NodeType == XmlNodeType.Element); - Debug.Assert(reader.Name == table.TableName); - - int childDepth = reader.Depth; - DataRow row = table.NewRow(); - - // Read every child property. Hardcoded structure - start with the element name, advance to the text, then to the EndElement - while (reader.Read() - && reader.Depth == childDepth + 1) - { - DataColumn column; - bool successfulRead; - - Debug.Assert(reader.NodeType == XmlNodeType.Element); - - column = table.Columns[reader.Name]; - Debug.Assert(column is not null); - - successfulRead = reader.Read(); - Debug.Assert(successfulRead); - Debug.Assert(reader.NodeType == XmlNodeType.Text); - - row[column] = reader.Value; - - successfulRead = reader.Read(); - Debug.Assert(successfulRead); - Debug.Assert(reader.NodeType == XmlNodeType.EndElement); - } - - rowFixup?.Invoke(row); - - table.Rows.Add(row); - - Debug.Assert(reader.NodeType == XmlNodeType.EndElement); - } - - table.EndLoadData(); - table.AcceptChanges(); - } - - private static DataTable CreateMetaDataCollectionsDataTable() - => new(DbMetaDataCollectionNames.MetaDataCollections) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), - new DataColumn(DbMetaDataColumnNames.NumberOfRestrictions, typeof(int)), - new DataColumn(DbMetaDataColumnNames.NumberOfIdentifierParts, typeof(int)), - new DataColumn(PopulationMechanismKey, typeof(string)), - new DataColumn(PopulationStringKey, typeof(string)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(MaximumVersionKey, typeof(string)) - } - }; - - private static DataTable CreateRestrictionsDataTable() - => new(DbMetaDataCollectionNames.Restrictions) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), - new DataColumn(RestrictionNameKey, typeof(string)), - new DataColumn(ParameterNameKey, typeof(string)), - new DataColumn(RestrictionDefaultKey, typeof(string)), - new DataColumn(RestrictionNumberKey, typeof(int)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(MaximumVersionKey, typeof(string)) - } - }; - - private static DataTable CreateDataSourceInformationDataTable() - => new(DbMetaDataCollectionNames.DataSourceInformation) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.DataSourceProductName, typeof(string)), - new DataColumn(DbMetaDataColumnNames.DataSourceProductVersion, typeof(string)), - new DataColumn(DbMetaDataColumnNames.DataSourceProductVersionNormalized, typeof(string)), - new DataColumn(DbMetaDataColumnNames.GroupByBehavior, typeof(GroupByBehavior)), - new DataColumn(DbMetaDataColumnNames.IdentifierPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.IdentifierCase, typeof(IdentifierCase)), - new DataColumn(DbMetaDataColumnNames.OrderByColumnsInSelect, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.ParameterMarkerFormat, typeof(string)), - new DataColumn(DbMetaDataColumnNames.ParameterMarkerPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.ParameterNameMaxLength, typeof(int)), - new DataColumn(DbMetaDataColumnNames.ParameterNamePattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.QuotedIdentifierPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.QuotedIdentifierCase, typeof(IdentifierCase)), - new DataColumn(DbMetaDataColumnNames.StatementSeparatorPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.StringLiteralPattern, typeof(string)), - new DataColumn(DbMetaDataColumnNames.SupportedJoinOperators, typeof(SupportedJoinOperators)) - } - }; - - private static DataTable CreateDataTypesDataTable() - => new(DbMetaDataCollectionNames.DataTypes) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.TypeName, typeof(string)), - new DataColumn(DbMetaDataColumnNames.ProviderDbType, typeof(int)), - new DataColumn(DbMetaDataColumnNames.ColumnSize, typeof(long)), - new DataColumn(DbMetaDataColumnNames.CreateFormat, typeof(string)), - new DataColumn(DbMetaDataColumnNames.CreateParameters, typeof(string)), - new DataColumn(DbMetaDataColumnNames.DataType, typeof(string)), - new DataColumn(DbMetaDataColumnNames.IsAutoIncrementable, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsBestMatch, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsCaseSensitive, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsFixedLength, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsFixedPrecisionScale, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsLong, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsNullable, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsSearchable, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsSearchableWithLike, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.IsUnsigned, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.MaximumScale, typeof(short)), - new DataColumn(DbMetaDataColumnNames.MinimumScale, typeof(short)), - new DataColumn(DbMetaDataColumnNames.IsConcurrencyType, typeof(bool)), - new DataColumn(MaximumVersionKey, typeof(string)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(DbMetaDataColumnNames.IsLiteralSupported, typeof(bool)), - new DataColumn(DbMetaDataColumnNames.LiteralPrefix, typeof(string)), - new DataColumn(DbMetaDataColumnNames.LiteralSuffix, typeof(string)) - } - }; - - private static DataTable CreateReservedWordsDataTable() - => new(DbMetaDataCollectionNames.ReservedWords) - { - Columns = - { - new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)), - new DataColumn(MinimumVersionKey, typeof(string)), - new DataColumn(MaximumVersionKey, typeof(string)) - } - }; - protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) { 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 1296fd39f1..3b56539f80 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -7,8 +7,10 @@ using System.Data; using System.Data.Common; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Text; +using System.Xml; using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; @@ -16,14 +18,33 @@ namespace Microsoft.Data.SqlClient { internal sealed class SqlMetaDataFactory : DbMetaDataFactory { - + // Well-known column names + private const string CollectionNameKey = "CollectionName"; + private const string PopulationMechanismKey = "PopulationMechanism"; + private const string PopulationStringKey = "PopulationString"; + private const string MaximumVersionKey = "MaximumVersion"; + private const string MinimumVersionKey = "MinimumVersion"; + private const string RestrictionDefaultKey = "RestrictionDefault"; + private const string RestrictionNumberKey = "RestrictionNumber"; + private const string NumberOfRestrictionsKey = "NumberOfRestrictions"; + private const string RestrictionNameKey = "RestrictionName"; + private const string ParameterNameKey = "ParameterName"; + + // Well-known server versions private const string ServerVersionNormalized90 = "09.00.0000"; private const string ServerVersionNormalized10 = "10.00.0000"; + private static readonly HashSet s_assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; - public SqlMetaDataFactory(Stream XMLStream, string serverVersion) : - base(XMLStream, serverVersion) - { } + public SqlMetaDataFactory(Stream xmlStream, string serverVersion) + { + ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); + ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); + + ServerVersion = serverVersion; + + CollectionDataSet = LoadDataSetFromXml(xmlStream); + } private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) { @@ -257,5 +278,241 @@ protected override DataTable PrepareCollection(string collectionName, string[] r return resultTable; } + + #region Create MetaDataCollections DataSet from XML + private DataSet LoadDataSetFromXml(Stream XmlStream) + { + DataSet metaDataCollectionsDataSet = new("NewDataSet") + { + Locale = CultureInfo.InvariantCulture + }; + XmlReaderSettings settings = new() + { + XmlResolver = null, + IgnoreComments = true, + IgnoreWhitespace = true + }; + using XmlReader reader = XmlReader.Create(XmlStream, settings); + + // Build up the schema DataSet manually, then load data from XmlStream. The schema of the DataSet is defined at: + // * https://learn.microsoft.com/en-us/sql/connect/ado-net/common-schema-collections + // * https://learn.microsoft.com/en-us/sql/connect/ado-net/sql-server-schema-collections + // Building the schema manually is necessary because DataSet.ReadXml uses XML serialization. This is slow, and it + // increases the binary size of AOT assemblies by approximately 4MB. + + bool readContainer = reader.Read(); + + // Skip past the XML declaration and the outer container element. This XML document is hardcoded; + // these checks will need to be adjusted if its structure changes. + Debug.Assert(readContainer); + Debug.Assert(reader.NodeType == XmlNodeType.XmlDeclaration); + + readContainer = reader.Read(); + Debug.Assert(readContainer); + Debug.Assert(reader.NodeType == XmlNodeType.Element); + Debug.Assert(reader.Name == "MetaData"); + Debug.Assert(reader.Depth == 0); + + // Iterate over each "table element" of the outer container element. + // LoadDataTable will read the child elements of each table element. + while (reader.Read() + && reader.Depth == 1) + { + DataTable dataTable = null; + Action rowFixup = null; + + Debug.Assert(reader.NodeType == XmlNodeType.Element); + + switch (reader.Name) + { + case "MetaDataCollectionsTable": + dataTable = CreateMetaDataCollectionsDataTable(); + break; + case "RestrictionsTable": + dataTable = CreateRestrictionsDataTable(); + break; + case "DataSourceInformationTable": + dataTable = CreateDataSourceInformationDataTable(); + rowFixup = FixUpDataSourceInformationRow; + break; + case "DataTypesTable": + dataTable = CreateDataTypesDataTable(); + break; + case "ReservedWordsTable": + dataTable = CreateReservedWordsDataTable(); + break; + default: + Debug.Fail($"Unexpected table element name: {reader.Name}"); + break; + } + + if (dataTable is not null) + { + LoadDataTable(reader, dataTable, rowFixup); + + metaDataCollectionsDataSet.Tables.Add(dataTable); + } + } + + return metaDataCollectionsDataSet; + } + + private static void LoadDataTable(XmlReader reader, DataTable table, Action rowFixup) + { + int parentDepth = reader.Depth; + + table.BeginLoadData(); + + // One outer loop per element, each loop reading every property of the row + while (reader.Read() + && reader.Depth == parentDepth + 1) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element); + Debug.Assert(reader.Name == table.TableName); + + int childDepth = reader.Depth; + DataRow row = table.NewRow(); + + // Read every child property. Hardcoded structure - start with the element name, advance to the text, then to the EndElement + while (reader.Read() + && reader.Depth == childDepth + 1) + { + DataColumn column; + bool successfulRead; + + Debug.Assert(reader.NodeType == XmlNodeType.Element); + + column = table.Columns[reader.Name]; + Debug.Assert(column is not null); + + successfulRead = reader.Read(); + Debug.Assert(successfulRead); + Debug.Assert(reader.NodeType == XmlNodeType.Text); + + row[column] = reader.Value; + + successfulRead = reader.Read(); + Debug.Assert(successfulRead); + Debug.Assert(reader.NodeType == XmlNodeType.EndElement); + } + + rowFixup?.Invoke(row); + + table.Rows.Add(row); + + Debug.Assert(reader.NodeType == XmlNodeType.EndElement); + } + + table.EndLoadData(); + table.AcceptChanges(); + } + + private void FixUpDataSourceInformationRow(DataRow dataSourceInfoRow) + { + Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersion)); + Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersionNormalized)); + + dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersion] = ServerVersion; + dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = ServerVersion; + } + + private static DataTable CreateMetaDataCollectionsDataTable() + => new(DbMetaDataCollectionNames.MetaDataCollections) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.NumberOfRestrictions, typeof(int)), + new DataColumn(DbMetaDataColumnNames.NumberOfIdentifierParts, typeof(int)), + new DataColumn(PopulationMechanismKey, typeof(string)), + new DataColumn(PopulationStringKey, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + + private static DataTable CreateRestrictionsDataTable() + => new(DbMetaDataCollectionNames.Restrictions) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), + new DataColumn(RestrictionNameKey, typeof(string)), + new DataColumn(ParameterNameKey, typeof(string)), + new DataColumn(RestrictionDefaultKey, typeof(string)), + new DataColumn(RestrictionNumberKey, typeof(int)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + + private static DataTable CreateDataSourceInformationDataTable() + => new(DbMetaDataCollectionNames.DataSourceInformation) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductVersion, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductVersionNormalized, typeof(string)), + new DataColumn(DbMetaDataColumnNames.GroupByBehavior, typeof(GroupByBehavior)), + new DataColumn(DbMetaDataColumnNames.IdentifierPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IdentifierCase, typeof(IdentifierCase)), + new DataColumn(DbMetaDataColumnNames.OrderByColumnsInSelect, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.ParameterMarkerFormat, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ParameterMarkerPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ParameterNameMaxLength, typeof(int)), + new DataColumn(DbMetaDataColumnNames.ParameterNamePattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.QuotedIdentifierPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.QuotedIdentifierCase, typeof(IdentifierCase)), + new DataColumn(DbMetaDataColumnNames.StatementSeparatorPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.StringLiteralPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.SupportedJoinOperators, typeof(SupportedJoinOperators)) + } + }; + + private static DataTable CreateDataTypesDataTable() + => new(DbMetaDataCollectionNames.DataTypes) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.TypeName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ProviderDbType, typeof(int)), + new DataColumn(DbMetaDataColumnNames.ColumnSize, typeof(long)), + new DataColumn(DbMetaDataColumnNames.CreateFormat, typeof(string)), + new DataColumn(DbMetaDataColumnNames.CreateParameters, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataType, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IsAutoIncrementable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsBestMatch, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsCaseSensitive, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsFixedLength, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsFixedPrecisionScale, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsLong, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsNullable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsSearchable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsSearchableWithLike, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsUnsigned, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.MaximumScale, typeof(short)), + new DataColumn(DbMetaDataColumnNames.MinimumScale, typeof(short)), + new DataColumn(DbMetaDataColumnNames.IsConcurrencyType, typeof(bool)), + new DataColumn(MaximumVersionKey, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IsLiteralSupported, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.LiteralPrefix, typeof(string)), + new DataColumn(DbMetaDataColumnNames.LiteralSuffix, typeof(string)) + } + }; + + private static DataTable CreateReservedWordsDataTable() + => new(DbMetaDataCollectionNames.ReservedWords) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + #endregion } } From a9e5bda91fc0de0059d825cf1ce4da661b5fc886 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:10:34 +0000 Subject: [PATCH 08/14] Merge Dispose logic Also implement IDisposable on SqlMetaDataFactory --- .../Microsoft/Data/ProviderBase/DbMetaDataFactory.cs | 10 ---------- .../Microsoft/Data/SqlClient/SqlMetaDataFactory.cs | 12 +++++++++++- 2 files changed, 11 insertions(+), 11 deletions(-) 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 e9ba4233b1..f5b0b4bd96 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -71,16 +71,6 @@ protected DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan return destinationTable; } - public void Dispose() => Dispose(true); - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - CollectionDataSet.Dispose(); - } - } - private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) { Debug.Assert(requestedCollectionRow is not null); 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 3b56539f80..efb4c8dadd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -16,7 +16,7 @@ namespace Microsoft.Data.SqlClient { - internal sealed class SqlMetaDataFactory : DbMetaDataFactory + internal sealed class SqlMetaDataFactory : DbMetaDataFactory, IDisposable { // Well-known column names private const string CollectionNameKey = "CollectionName"; @@ -46,6 +46,16 @@ public SqlMetaDataFactory(Stream xmlStream, string serverVersion) CollectionDataSet = LoadDataSetFromXml(xmlStream); } + public void Dispose() => Dispose(true); + + private void Dispose(bool disposing) + { + if (disposing) + { + CollectionDataSet.Dispose(); + } + } + private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) { const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; From f4d957a5d8b514a160443a28151bcc3339ee4c8c Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:18:12 +0000 Subject: [PATCH 09/14] Merge DataTable population method --- .../Data/ProviderBase/DbMetaDataFactory.cs | 87 +----------------- .../Data/SqlClient/SqlMetaDataFactory.cs | 90 +++++++++++++++++++ 2 files changed, 93 insertions(+), 84 deletions(-) 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 f5b0b4bd96..90af032309 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -32,43 +32,9 @@ internal class DbMetaDataFactory protected string ServerVersion { get; set; } - protected DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) + protected virtual DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) { - DataTable destinationTable; - DataColumn[] filteredSourceColumns; - DataColumnCollection destinationColumns; - DataRow newRow; - - DataTable sourceTable = CollectionDataSet.Tables[collectionName]; - - if (sourceTable?.TableName != collectionName) - { - throw ADP.DataTableDoesNotExist(collectionName); - } - - destinationTable = new DataTable(collectionName) - { - Locale = CultureInfo.InvariantCulture - }; - destinationColumns = destinationTable.Columns; - - filteredSourceColumns = FilterColumns(sourceTable, hiddenColumnNames, destinationColumns); - - foreach (DataRow row in sourceTable.Rows) - { - if (SupportedByCurrentVersion(row)) - { - newRow = destinationTable.NewRow(); - for (int i = 0; i < destinationColumns.Count; i++) - { - newRow[destinationColumns[i]] = row[filteredSourceColumns[i], DataRowVersion.Current]; - } - destinationTable.Rows.Add(newRow); - newRow.AcceptChanges(); - } - } - - return destinationTable; + throw ADP.MethodNotImplemented(); } private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) @@ -157,38 +123,6 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri return resultTable; } - private static DataColumn[] FilterColumns(DataTable sourceTable, ReadOnlySpan hiddenColumnNames, DataColumnCollection destinationColumns) - { - int columnCount = 0; - foreach (DataColumn sourceColumn in sourceTable.Columns) - { - if (IncludeThisColumn(sourceColumn, hiddenColumnNames)) - { - columnCount++; - } - } - - if (columnCount == 0) - { - throw ADP.NoColumns(); - } - - int currentColumn = 0; - DataColumn[] filteredSourceColumns = new DataColumn[columnCount]; - - foreach (DataColumn sourceColumn in sourceTable.Columns) - { - if (IncludeThisColumn(sourceColumn, hiddenColumnNames)) - { - DataColumn newDestinationColumn = new(sourceColumn.ColumnName, sourceColumn.DataType); - destinationColumns.Add(newDestinationColumn); - filteredSourceColumns[currentColumn] = sourceColumn; - currentColumn++; - } - } - return filteredSourceColumns; - } - private DataRow FindMetaDataCollectionRow(string collectionName) { bool versionFailure = false; @@ -367,27 +301,12 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam return requestedSchema; } - private static bool IncludeThisColumn(DataColumn sourceColumn, ReadOnlySpan hiddenColumnNames) - { - string sourceColumnName = sourceColumn.ColumnName; - - return sourceColumnName switch - { - MinimumVersionKey or MaximumVersionKey => false, -#if NET - _ => !hiddenColumnNames.Contains(sourceColumnName), -#else - _ => hiddenColumnNames.IndexOf(sourceColumnName) == -1, -#endif - }; - } - protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) { throw ADP.NotSupported(); } - private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) + protected bool SupportedByCurrentVersion(DataRow requestedCollectionRow) { DataColumnCollection tableColumns = requestedCollectionRow.Table.Columns; DataColumn versionColumn; 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 efb4c8dadd..323cdba674 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -56,6 +56,95 @@ private void Dispose(bool disposing) } } + #region GetSchema Helpers: DataTable Population Method + private static bool IncludeThisColumn(DataColumn sourceColumn, ReadOnlySpan hiddenColumnNames) + { + string sourceColumnName = sourceColumn.ColumnName; + + return sourceColumnName switch + { + MinimumVersionKey or MaximumVersionKey => false, +#if NET + _ => !hiddenColumnNames.Contains(sourceColumnName), +#else + _ => hiddenColumnNames.IndexOf(sourceColumnName) == -1, +#endif + }; + } + + private static DataColumn[] FilterColumns(DataTable sourceTable, ReadOnlySpan hiddenColumnNames, DataColumnCollection destinationColumns) + { + int columnCount = 0; + foreach (DataColumn sourceColumn in sourceTable.Columns) + { + if (IncludeThisColumn(sourceColumn, hiddenColumnNames)) + { + columnCount++; + } + } + + if (columnCount == 0) + { + throw ADP.NoColumns(); + } + + int currentColumn = 0; + DataColumn[] filteredSourceColumns = new DataColumn[columnCount]; + + foreach (DataColumn sourceColumn in sourceTable.Columns) + { + if (IncludeThisColumn(sourceColumn, hiddenColumnNames)) + { + DataColumn newDestinationColumn = new(sourceColumn.ColumnName, sourceColumn.DataType); + destinationColumns.Add(newDestinationColumn); + filteredSourceColumns[currentColumn] = sourceColumn; + currentColumn++; + } + } + return filteredSourceColumns; + } + + protected override DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) + { + DataTable destinationTable; + DataColumn[] filteredSourceColumns; + DataColumnCollection destinationColumns; + DataRow newRow; + + DataTable sourceTable = CollectionDataSet.Tables[collectionName]; + + if (sourceTable?.TableName != collectionName) + { + throw ADP.DataTableDoesNotExist(collectionName); + } + + destinationTable = new DataTable(collectionName) + { + Locale = CultureInfo.InvariantCulture + }; + destinationColumns = destinationTable.Columns; + + filteredSourceColumns = FilterColumns(sourceTable, hiddenColumnNames, destinationColumns); + + foreach (DataRow row in sourceTable.Rows) + { + if (SupportedByCurrentVersion(row)) + { + newRow = destinationTable.NewRow(); + for (int i = 0; i < destinationColumns.Count; i++) + { + newRow[destinationColumns[i]] = row[filteredSourceColumns[i], DataRowVersion.Current]; + } + destinationTable.Rows.Add(newRow); + newRow.AcceptChanges(); + } + } + + return destinationTable; + } + #endregion + + #region GetSchema Helpers: PrepareCollection Population Method private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) { const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; @@ -288,6 +377,7 @@ protected override DataTable PrepareCollection(string collectionName, string[] r return resultTable; } + #endregion #region Create MetaDataCollections DataSet from XML private DataSet LoadDataSetFromXml(Stream XmlStream) From 6d22f3f38fe701a276c562a96383123eecde3df9 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:21:28 +0000 Subject: [PATCH 10/14] Merge ExecuteCommand population method --- .../Data/ProviderBase/DbMetaDataFactory.cs | 125 +---------------- .../Data/SqlClient/SqlMetaDataFactory.cs | 128 ++++++++++++++++++ 2 files changed, 130 insertions(+), 123 deletions(-) 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 90af032309..6bc854cd14 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -37,90 +37,9 @@ protected virtual DataTable CloneAndFilterCollection(string collectionName, Read throw ADP.MethodNotImplemented(); } - private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) + protected virtual DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) { - Debug.Assert(requestedCollectionRow is not null); - - DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey]; - DataColumn numberOfRestrictionsColumn = metaDataCollectionsTable.Columns[NumberOfRestrictionsKey]; - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[CollectionNameKey]; - - DataTable resultTable = null; - string sqlCommand = requestedCollectionRow[populationStringColumn, DataRowVersion.Current] as string; - int numberOfRestrictions = (int)requestedCollectionRow[numberOfRestrictionsColumn, DataRowVersion.Current]; - string collectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; - - if ((restrictions is not null) && (restrictions.Length > numberOfRestrictions)) - { - throw ADP.TooManyRestrictions(collectionName); - } - - SqlConnection castConnection = connection as SqlConnection; - using SqlCommand command = castConnection.CreateCommand(); - - command.CommandText = sqlCommand; - command.CommandTimeout = Math.Max(command.CommandTimeout, 180); - command.Transaction = castConnection?.GetOpenTdsConnection()?.CurrentTransaction?.Parent; - - for (int i = 0; i < numberOfRestrictions; i++) - { - SqlParameter restrictionParameter = command.CreateParameter(); - - if ((restrictions is not null) && (i < restrictions.Length) && (restrictions[i] is not null)) - { - restrictionParameter.Value = restrictions[i]; - } - else - { - // This is where we have to assign null to the value of the parameter. - restrictionParameter.Value = DBNull.Value; - } - - restrictionParameter.ParameterName = GetParameterName(collectionName, i + 1); - restrictionParameter.Direction = ParameterDirection.Input; - command.Parameters.Add(restrictionParameter); - } - - SqlDataReader reader = null; - try - { - try - { - reader = command.ExecuteReader(); - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - throw ADP.QueryFailed(collectionName, e); - } - - // Build a DataTable from the reader - resultTable = new DataTable(collectionName) - { - Locale = CultureInfo.InvariantCulture - }; - - System.Collections.ObjectModel.ReadOnlyCollection colSchema = reader.GetColumnSchema(); - foreach (DbColumn col in colSchema) - { - resultTable.Columns.Add(col.ColumnName, col.DataType); - } - object[] values = new object[resultTable.Columns.Count]; - while (reader.Read()) - { - reader.GetValues(values); - resultTable.Rows.Add(values); - } - } - finally - { - reader?.Dispose(); - } - return resultTable; + throw ADP.MethodNotImplemented(); } private DataRow FindMetaDataCollectionRow(string collectionName) @@ -199,46 +118,6 @@ private DataRow FindMetaDataCollectionRow(string collectionName) } - private string GetParameterName(string neededCollectionName, int neededRestrictionNumber) - { - DataTable restrictionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; - - Debug.Assert(restrictionsTable is not null); - - DataColumnCollection restrictionColumns = restrictionsTable.Columns; - DataColumn collectionName = restrictionColumns[CollectionNameKey]; - DataColumn parameterName = restrictionColumns[ParameterNameKey]; - DataColumn restrictionName = restrictionColumns[RestrictionNameKey]; - DataColumn restrictionNumber = restrictionColumns[RestrictionNumberKey]; - - Debug.Assert(parameterName is not null); - Debug.Assert(collectionName is not null); - Debug.Assert(restrictionName is not null); - Debug.Assert(restrictionNumber is not null); - - string result = null; - - foreach (DataRow restriction in restrictionsTable.Rows) - { - - if (((string)restriction[collectionName] == neededCollectionName) && - ((int)restriction[restrictionNumber] == neededRestrictionNumber) && - (SupportedByCurrentVersion(restriction))) - { - - result = (string)restriction[parameterName]; - break; - } - } - - if (result is null) - { - throw ADP.MissingRestrictionRow(); - } - - return result; - } - public virtual DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) { const string DataTableKey = "DataTable"; 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 323cdba674..562ce70639 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -144,6 +144,134 @@ protected override DataTable CloneAndFilterCollection(string collectionName, Rea } #endregion + #region GetSchema Helpers: ExecuteCommand Population Method + private string GetParameterName(string neededCollectionName, int neededRestrictionNumber) + { + DataTable restrictionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; + + Debug.Assert(restrictionsTable is not null); + + DataColumnCollection restrictionColumns = restrictionsTable.Columns; + DataColumn collectionName = restrictionColumns[CollectionNameKey]; + DataColumn parameterName = restrictionColumns[ParameterNameKey]; + DataColumn restrictionName = restrictionColumns[RestrictionNameKey]; + DataColumn restrictionNumber = restrictionColumns[RestrictionNumberKey]; + + Debug.Assert(parameterName is not null); + Debug.Assert(collectionName is not null); + Debug.Assert(restrictionName is not null); + Debug.Assert(restrictionNumber is not null); + + string result = null; + + foreach (DataRow restriction in restrictionsTable.Rows) + { + + if (((string)restriction[collectionName] == neededCollectionName) && + ((int)restriction[restrictionNumber] == neededRestrictionNumber) && + (SupportedByCurrentVersion(restriction))) + { + + result = (string)restriction[parameterName]; + break; + } + } + + if (result is null) + { + throw ADP.MissingRestrictionRow(); + } + + return result; + } + + protected override DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) + { + Debug.Assert(requestedCollectionRow is not null); + + DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataColumn populationStringColumn = metaDataCollectionsTable.Columns[PopulationStringKey]; + DataColumn numberOfRestrictionsColumn = metaDataCollectionsTable.Columns[NumberOfRestrictionsKey]; + DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[CollectionNameKey]; + + DataTable resultTable = null; + string sqlCommand = requestedCollectionRow[populationStringColumn, DataRowVersion.Current] as string; + int numberOfRestrictions = (int)requestedCollectionRow[numberOfRestrictionsColumn, DataRowVersion.Current]; + string collectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; + + if ((restrictions is not null) && (restrictions.Length > numberOfRestrictions)) + { + throw ADP.TooManyRestrictions(collectionName); + } + + SqlConnection castConnection = connection as SqlConnection; + using SqlCommand command = castConnection.CreateCommand(); + + command.CommandText = sqlCommand; + command.CommandTimeout = Math.Max(command.CommandTimeout, 180); + command.Transaction = castConnection?.GetOpenTdsConnection()?.CurrentTransaction?.Parent; + + for (int i = 0; i < numberOfRestrictions; i++) + { + SqlParameter restrictionParameter = command.CreateParameter(); + + if ((restrictions is not null) && (i < restrictions.Length) && (restrictions[i] is not null)) + { + restrictionParameter.Value = restrictions[i]; + } + else + { + // This is where we have to assign null to the value of the parameter. + restrictionParameter.Value = DBNull.Value; + } + + restrictionParameter.ParameterName = GetParameterName(collectionName, i + 1); + restrictionParameter.Direction = ParameterDirection.Input; + command.Parameters.Add(restrictionParameter); + } + + SqlDataReader reader = null; + try + { + try + { + reader = command.ExecuteReader(); + } + catch (Exception e) + { + if (!ADP.IsCatchableExceptionType(e)) + { + throw; + } + throw ADP.QueryFailed(collectionName, e); + } + + // Build a DataTable from the reader + resultTable = new DataTable(collectionName) + { + Locale = CultureInfo.InvariantCulture + }; + + System.Collections.ObjectModel.ReadOnlyCollection colSchema = reader.GetColumnSchema(); + foreach (DbColumn col in colSchema) + { + resultTable.Columns.Add(col.ColumnName, col.DataType); + } + object[] values = new object[resultTable.Columns.Count]; + while (reader.Read()) + { + reader.GetValues(values); + resultTable.Rows.Add(values); + } + } + finally + { + reader?.Dispose(); + } + return resultTable; + } + #endregion + #region GetSchema Helpers: PrepareCollection Population Method private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) { From 6d216e5771f7282a49ed1ce966ff0b2f3ba33f63 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:46:57 +0000 Subject: [PATCH 11/14] Merge top-level GetSchema method --- .../Data/ProviderBase/DbMetaDataFactory.cs | 138 ------------------ .../Data/SqlClient/SqlMetaDataFactory.cs | 138 ++++++++++++++++++ 2 files changed, 138 insertions(+), 138 deletions(-) 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 6bc854cd14..8c226fa5ae 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -42,144 +42,6 @@ protected virtual DataTable ExecuteCommand(DataRow requestedCollectionRow, strin throw ADP.MethodNotImplemented(); } - private DataRow FindMetaDataCollectionRow(string collectionName) - { - bool versionFailure = false; - bool haveExactMatch = false; - bool haveMultipleInexactMatches = false; - - DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - Debug.Assert(metaDataCollectionsTable is not null); - - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; - Debug.Assert(collectionNameColumn is not null && collectionNameColumn.DataType == typeof(string)); - - DataRow requestedCollectionRow = null; - string exactCollectionName = null; - - // find the requested collection - foreach (DataRow row in metaDataCollectionsTable.Rows) - { - string candidateCollectionName = row[collectionNameColumn, DataRowVersion.Current] as string; - - if (string.IsNullOrEmpty(candidateCollectionName)) - { - throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); - } - - if (string.Equals(candidateCollectionName, collectionName, StringComparison.InvariantCultureIgnoreCase)) - { - if (!SupportedByCurrentVersion(row)) - { - versionFailure = true; - } - else if (collectionName == candidateCollectionName) - { - if (haveExactMatch) - { - throw ADP.CollectionNameIsNotUnique(collectionName); - } - requestedCollectionRow = row; - exactCollectionName = candidateCollectionName; - haveExactMatch = true; - } - else if (!haveExactMatch) - { - // have an inexact match - ok only if it is the only one - if (exactCollectionName is not null) - { - // can't fail here because we may still find an exact match - haveMultipleInexactMatches = true; - } - requestedCollectionRow = row; - exactCollectionName = candidateCollectionName; - } - } - } - - if (requestedCollectionRow is null) - { - if (!versionFailure) - { - throw ADP.UndefinedCollection(collectionName); - } - else - { - throw ADP.UnsupportedVersion(collectionName); - } - } - - if (!haveExactMatch && haveMultipleInexactMatches) - { - throw ADP.AmbiguousCollectionName(collectionName); - } - - return requestedCollectionRow; - - } - - public virtual DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) - { - const string DataTableKey = "DataTable"; - const string SqlCommandKey = "SQLCommand"; - const string PrepareCollectionKey = "PrepareCollection"; - - Debug.Assert(CollectionDataSet is not null); - - DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[PopulationMechanismKey]; - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; - DataRow requestedCollectionRow = FindMetaDataCollectionRow(collectionName); - string exactCollectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; - - if (!ADP.IsEmptyArray(restrictions)) - { - - for (int i = 0; i < restrictions.Length; i++) - { - if ((restrictions[i] is not null) && (restrictions[i].Length > 4096)) - { - // use a non-specific error because no new beta 2 error messages are allowed - // TODO: will add a more descriptive error in RTM - throw ADP.NotSupported(); - } - } - } - - string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string; - - DataTable requestedSchema; - switch (populationMechanism) - { - case DataTableKey: - ReadOnlySpan hiddenColumns = exactCollectionName == DbMetaDataCollectionNames.MetaDataCollections - ? [ PopulationMechanismKey, PopulationStringKey ] - : []; - - // none of the datatable collections support restrictions - if (!ADP.IsEmptyArray(restrictions)) - { - throw ADP.TooManyRestrictions(exactCollectionName); - } - - requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns); - break; - - case SqlCommandKey: - requestedSchema = ExecuteCommand(requestedCollectionRow, restrictions, connection); - break; - - case PrepareCollectionKey: - requestedSchema = PrepareCollection(exactCollectionName, restrictions, connection); - break; - - default: - throw ADP.UndefinedPopulationMechanism(populationMechanism); - } - - return requestedSchema; - } - protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) { 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 562ce70639..8931ded64c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -46,6 +46,144 @@ public SqlMetaDataFactory(Stream xmlStream, string serverVersion) CollectionDataSet = LoadDataSetFromXml(xmlStream); } + private DataRow FindMetaDataCollectionRow(string collectionName) + { + bool versionFailure = false; + bool haveExactMatch = false; + bool haveMultipleInexactMatches = false; + + DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + Debug.Assert(metaDataCollectionsTable is not null); + + DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; + Debug.Assert(collectionNameColumn is not null && collectionNameColumn.DataType == typeof(string)); + + DataRow requestedCollectionRow = null; + string exactCollectionName = null; + + // find the requested collection + foreach (DataRow row in metaDataCollectionsTable.Rows) + { + string candidateCollectionName = row[collectionNameColumn, DataRowVersion.Current] as string; + + if (string.IsNullOrEmpty(candidateCollectionName)) + { + throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); + } + + if (string.Equals(candidateCollectionName, collectionName, StringComparison.InvariantCultureIgnoreCase)) + { + if (!SupportedByCurrentVersion(row)) + { + versionFailure = true; + } + else if (collectionName == candidateCollectionName) + { + if (haveExactMatch) + { + throw ADP.CollectionNameIsNotUnique(collectionName); + } + requestedCollectionRow = row; + exactCollectionName = candidateCollectionName; + haveExactMatch = true; + } + else if (!haveExactMatch) + { + // have an inexact match - ok only if it is the only one + if (exactCollectionName is not null) + { + // can't fail here because we may still find an exact match + haveMultipleInexactMatches = true; + } + requestedCollectionRow = row; + exactCollectionName = candidateCollectionName; + } + } + } + + if (requestedCollectionRow is null) + { + if (!versionFailure) + { + throw ADP.UndefinedCollection(collectionName); + } + else + { + throw ADP.UnsupportedVersion(collectionName); + } + } + + if (!haveExactMatch && haveMultipleInexactMatches) + { + throw ADP.AmbiguousCollectionName(collectionName); + } + + return requestedCollectionRow; + + } + + public DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) + { + const string DataTableKey = "DataTable"; + const string SqlCommandKey = "SQLCommand"; + const string PrepareCollectionKey = "PrepareCollection"; + + Debug.Assert(CollectionDataSet is not null); + + DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[PopulationMechanismKey]; + DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; + DataRow requestedCollectionRow = FindMetaDataCollectionRow(collectionName); + string exactCollectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; + + if (!ADP.IsEmptyArray(restrictions)) + { + + for (int i = 0; i < restrictions.Length; i++) + { + if ((restrictions[i] is not null) && (restrictions[i].Length > 4096)) + { + // use a non-specific error because no new beta 2 error messages are allowed + // TODO: will add a more descriptive error in RTM + throw ADP.NotSupported(); + } + } + } + + string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string; + + DataTable requestedSchema; + switch (populationMechanism) + { + case DataTableKey: + ReadOnlySpan hiddenColumns = exactCollectionName == DbMetaDataCollectionNames.MetaDataCollections + ? [PopulationMechanismKey, PopulationStringKey] + : []; + + // none of the datatable collections support restrictions + if (!ADP.IsEmptyArray(restrictions)) + { + throw ADP.TooManyRestrictions(exactCollectionName); + } + + requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns); + break; + + case SqlCommandKey: + requestedSchema = ExecuteCommand(requestedCollectionRow, restrictions, connection); + break; + + case PrepareCollectionKey: + requestedSchema = PrepareCollection(exactCollectionName, restrictions, connection); + break; + + default: + throw ADP.UndefinedPopulationMechanism(populationMechanism); + } + + return requestedSchema; + } + public void Dispose() => Dispose(true); private void Dispose(bool disposing) From 1188903a9e1d73862a226603e33060288782a660 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:50:04 +0000 Subject: [PATCH 12/14] Merge SupportedByCurrentVersion and properties, remove now-unused base class methods --- .../Data/ProviderBase/DbMetaDataFactory.cs | 54 ------------------- .../Data/SqlClient/SqlMetaDataFactory.cs | 45 ++++++++++++++-- 2 files changed, 42 insertions(+), 57 deletions(-) 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 8c226fa5ae..f512b503b5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -27,59 +27,5 @@ internal class DbMetaDataFactory private const string NumberOfRestrictionsKey = "NumberOfRestrictions"; private const string RestrictionNameKey = "RestrictionName"; private const string ParameterNameKey = "ParameterName"; - - protected DataSet CollectionDataSet { get; set; } - - protected string ServerVersion { get; set; } - - protected virtual DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) - { - throw ADP.MethodNotImplemented(); - } - - protected virtual DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) - { - throw ADP.MethodNotImplemented(); - } - - protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) - { - throw ADP.NotSupported(); - } - - protected bool SupportedByCurrentVersion(DataRow requestedCollectionRow) - { - DataColumnCollection tableColumns = requestedCollectionRow.Table.Columns; - DataColumn versionColumn; - object version; - - // check the minimum version first - versionColumn = tableColumns[MinimumVersionKey]; - if (versionColumn is not null) - { - version = requestedCollectionRow[versionColumn]; - - if (version is string minVersion - && string.Compare(ServerVersion, minVersion, StringComparison.OrdinalIgnoreCase) < 0) - { - return false; - } - } - - // if the minimum version was ok what about the maximum version - versionColumn = tableColumns[MaximumVersionKey]; - if (versionColumn is not null) - { - version = requestedCollectionRow[versionColumn]; - - if (version is string maxVersion - && string.Compare(ServerVersion, maxVersion, StringComparison.OrdinalIgnoreCase) > 0) - { - return false; - } - } - - return true; - } } } 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 8931ded64c..452f019cec 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -36,6 +36,10 @@ internal sealed class SqlMetaDataFactory : DbMetaDataFactory, IDisposable private static readonly HashSet s_assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; + private DataSet CollectionDataSet { get; } + + private string ServerVersion { get; } + public SqlMetaDataFactory(Stream xmlStream, string serverVersion) { ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); @@ -46,6 +50,41 @@ public SqlMetaDataFactory(Stream xmlStream, string serverVersion) CollectionDataSet = LoadDataSetFromXml(xmlStream); } + private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) + { + DataColumnCollection tableColumns = requestedCollectionRow.Table.Columns; + DataColumn versionColumn; + object version; + + // check the minimum version first + versionColumn = tableColumns[MinimumVersionKey]; + if (versionColumn is not null) + { + version = requestedCollectionRow[versionColumn]; + + if (version is string minVersion + && string.Compare(ServerVersion, minVersion, StringComparison.OrdinalIgnoreCase) < 0) + { + return false; + } + } + + // if the minimum version was ok what about the maximum version + versionColumn = tableColumns[MaximumVersionKey]; + if (versionColumn is not null) + { + version = requestedCollectionRow[versionColumn]; + + if (version is string maxVersion + && string.Compare(ServerVersion, maxVersion, StringComparison.OrdinalIgnoreCase) > 0) + { + return false; + } + } + + return true; + } + private DataRow FindMetaDataCollectionRow(string collectionName) { bool versionFailure = false; @@ -242,7 +281,7 @@ private static DataColumn[] FilterColumns(DataTable sourceTable, ReadOnlySpan hiddenColumnNames) + private DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan hiddenColumnNames) { DataTable destinationTable; DataColumn[] filteredSourceColumns; @@ -323,7 +362,7 @@ private string GetParameterName(string neededCollectionName, int neededRestricti return result; } - protected override DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) + private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) { Debug.Assert(requestedCollectionRow is not null); @@ -622,7 +661,7 @@ private DataTable GetDataTypesTable(SqlConnection connection) return dataTypesTable; } - protected override DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) + private DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) { SqlConnection sqlConnection = (SqlConnection)connection; DataTable resultTable = null; From 707c87fabcc683347c02a2cc8fe11b0d96611417 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:53:22 +0000 Subject: [PATCH 13/14] Replace unnecessary private properties with variables --- .../Data/SqlClient/SqlMetaDataFactory.cs | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) 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 452f019cec..bba1226d4c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -36,18 +36,17 @@ internal sealed class SqlMetaDataFactory : DbMetaDataFactory, IDisposable private static readonly HashSet s_assemblyPropertyUnsupportedEngines = new() { 6, 9, 11 }; - private DataSet CollectionDataSet { get; } - - private string ServerVersion { get; } + private readonly DataSet _collectionDataSet; + private readonly string _serverVersion; public SqlMetaDataFactory(Stream xmlStream, string serverVersion) { ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); - ServerVersion = serverVersion; + _serverVersion = serverVersion; - CollectionDataSet = LoadDataSetFromXml(xmlStream); + _collectionDataSet = LoadDataSetFromXml(xmlStream); } private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) @@ -63,7 +62,7 @@ private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) version = requestedCollectionRow[versionColumn]; if (version is string minVersion - && string.Compare(ServerVersion, minVersion, StringComparison.OrdinalIgnoreCase) < 0) + && string.Compare(_serverVersion, minVersion, StringComparison.OrdinalIgnoreCase) < 0) { return false; } @@ -76,7 +75,7 @@ private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) version = requestedCollectionRow[versionColumn]; if (version is string maxVersion - && string.Compare(ServerVersion, maxVersion, StringComparison.OrdinalIgnoreCase) > 0) + && string.Compare(_serverVersion, maxVersion, StringComparison.OrdinalIgnoreCase) > 0) { return false; } @@ -91,7 +90,7 @@ private DataRow FindMetaDataCollectionRow(string collectionName) bool haveExactMatch = false; bool haveMultipleInexactMatches = false; - DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataTable metaDataCollectionsTable = _collectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; Debug.Assert(metaDataCollectionsTable is not null); DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; @@ -167,9 +166,9 @@ public DataTable GetSchema(DbConnection connection, string collectionName, strin const string SqlCommandKey = "SQLCommand"; const string PrepareCollectionKey = "PrepareCollection"; - Debug.Assert(CollectionDataSet is not null); + Debug.Assert(_collectionDataSet is not null); - DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; + DataTable metaDataCollectionsTable = _collectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[PopulationMechanismKey]; DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; DataRow requestedCollectionRow = FindMetaDataCollectionRow(collectionName); @@ -229,7 +228,7 @@ private void Dispose(bool disposing) { if (disposing) { - CollectionDataSet.Dispose(); + _collectionDataSet.Dispose(); } } @@ -288,7 +287,7 @@ private DataTable CloneAndFilterCollection(string collectionName, ReadOnlySpan Date: Mon, 1 Dec 2025 06:55:38 +0000 Subject: [PATCH 14/14] Remove DbMetaDataFactory --- .../src/Microsoft.Data.SqlClient.csproj | 3 -- .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 -- .../src/Microsoft/Data/Common/AdapterUtil.cs | 7 +---- .../Data/ProviderBase/DbMetaDataFactory.cs | 31 ------------------- .../Data/SqlClient/SqlMetaDataFactory.cs | 3 +- 5 files changed, 2 insertions(+), 45 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 32b9e76c98..6cf72729f6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -156,9 +156,6 @@ Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs - - Microsoft\Data\ProviderBase\DbMetaDataFactory.cs - Microsoft\Data\ProviderBase\DbReferenceCollection.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 36a87b4eb2..b73ca12d2a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -404,9 +404,6 @@ Microsoft\Data\SqlClient\Diagnostics\SqlDiagnosticListener.cs - - Microsoft\Data\ProviderBase\DbMetaDataFactory.cs - Microsoft\Data\ProviderBase\DbReferenceCollection.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index 72c6d65841..3df6cde89d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -1172,7 +1172,7 @@ internal static Exception InvalidCommandTimeout(int value, [CallerMemberName] st => Argument(StringsHelper.GetString(Strings.ADP_InvalidCommandTimeout, value.ToString(CultureInfo.InvariantCulture)), property); #endregion -#region DbMetaDataFactory +#region SqlMetaDataFactory internal static Exception DataTableDoesNotExist(string collectionName) => Argument(StringsHelper.GetString(Strings.MDF_DataTableDoesNotExist, collectionName)); @@ -1226,11 +1226,6 @@ internal static Exception UndefinedCollection(string collectionName) internal static Exception AmbiguousCollectionName(string collectionName) => Argument(StringsHelper.GetString(Strings.MDF_AmbiguousCollectionName, collectionName)); - internal static Exception MissingDataSourceInformationColumn() => Argument(StringsHelper.GetString(Strings.MDF_MissingDataSourceInformationColumn)); - - internal static Exception IncorrectNumberOfDataSourceInformationRows() - => Argument(StringsHelper.GetString(Strings.MDF_IncorrectNumberOfDataSourceInformationRows)); - internal static Exception MissingRestrictionRow() => Argument(StringsHelper.GetString(Strings.MDF_MissingRestrictionRow)); internal static Exception UndefinedPopulationMechanism(string populationMechanism) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs deleted file mode 100644 index f512b503b5..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Data.Common; -using Microsoft.Data.SqlClient; -using System; -using System.Data; -using System.Data.Common; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Xml; - -namespace Microsoft.Data.ProviderBase -{ - internal class DbMetaDataFactory - { - // well known column names - private const string CollectionNameKey = "CollectionName"; - private const string PopulationMechanismKey = "PopulationMechanism"; - private const string PopulationStringKey = "PopulationString"; - private const string MaximumVersionKey = "MaximumVersion"; - private const string MinimumVersionKey = "MinimumVersion"; - private const string RestrictionDefaultKey = "RestrictionDefault"; - private const string RestrictionNumberKey = "RestrictionNumber"; - private const string NumberOfRestrictionsKey = "NumberOfRestrictions"; - private const string RestrictionNameKey = "RestrictionName"; - private const string ParameterNameKey = "ParameterName"; - } -} 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 bba1226d4c..ca3dec0658 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -12,11 +12,10 @@ using System.Text; using System.Xml; using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; namespace Microsoft.Data.SqlClient { - internal sealed class SqlMetaDataFactory : DbMetaDataFactory, IDisposable + internal sealed class SqlMetaDataFactory : IDisposable { // Well-known column names private const string CollectionNameKey = "CollectionName";