DbDataReader on .Net Framework, provides an API called DataTable GetSchemaTable()
which was returns the metadata of the columns
being read using the DbDataReader object. Because of the removal of DataTable in .Net Core this API had to be removed.
DbDataReader.GetSchemaTable()
needs a replacement in .Net core.
The data structure DbColumn along with the necessary attributes which should be present for the various ADO.Net providers to effectively provide the metadata for the columns is provided in the issue.
namespace System.Data.Common
{
// DbColumn contains the base attributes that are common to most Database columns. An indexer
// has been provided in case a property added by a provider needs to be accessed without a dependency on the provider implementation.
public class DbColumn
{
public virtual bool AllowDBNull { get; set; }
public virtual string BaseCatalogName { get; set; }
public virtual string BaseColumnName { get; set; }
public virtual string BaseSchemaName { get; set; }
public virtual string BaseServerName { get; set; }
public virtual string BaseTableName { get; set; }
public virtual string ColumnName { get; set; }
public virtual int ColumnOrdinal { get; set; }
public virtual int ColumnSize { get; set; }
public virtual bool IsAliased { get; set; }
public virtual bool IsAutoIncrement { get; set; }
public virtual bool IsExpression { get; set; }
public virtual bool IsHidden { get; set; }
public virtual bool IsIdentity { get; set; }
public virtual bool IsKey { get; set; }
public virtual bool IsLong { get; set; }
public virtual bool IsReadOnly { get; set; }
public virtual bool IsUnique { get; set; }
public virtual int NumericPrecision { get; set; }
public virtual int NumericScale { get; set; }
public virtual string UdtAssemblyQualifiedName { get; set; }
public virtual Type DataType { get; set; }
public virtual string DataTypeName { get; set; }
public virtual object this[string property] {}
}
}
The next part of the API is to expose a ReadOnlyCollection from the DbDataReader
The right way of exposing the API would be to add an API in the DbDataReader class called
ReadOnlyCollection<DbColumn> DbDataReader.GetColumnSchema()
This is however not possible in .Net Core current version as it would hinder portability of apps created on .Net core with .Net framework.
Based on the contents of the link above, the following rule is allowed
Adding an abstract member to a public type when there are no accessible (public or protected) constructors, or the type is sealed
The restriction is due to type forwarding, which needs the calls to DbDataReader to be forwarded to the .Net framework's DbDataReader.
We introduce an interface called IDbColumnSchemaGenerator, in .Net core which can be implemented by a type which can provide the Column Metadata.
For DbDataReader, this interface needs to be implemented by the subclasses of the DbDataReader
to provide the Column metadata provided by the DbDataReader
namespace System.Data.Common
{
public interface IDbColumnSchemaGenerator
{
System.Collections.ObjectModel.ReadOnlyCollection<DbColumn> GetColumnSchema();
}
}
The subclasses of DbDataReader in the ADO.Net providers implement this interface.
public class SqlDataReader : DbDataReader, IDbColumnSchemaGenerator
{
public ReadOnlyCollection<DbColumn> GetColumnSchema(){
return ... ;
}
}
In .Net core, to allow the Column Metadata to be exposed from the DbDataReader, an extension to the DbDataReader will be provided. This extension has the same signature as the interface method.
using System.Collections.ObjectModel;
namespace System.Data.Common
{
public static class DbDataReaderExtension
{
public static System.Collections.ObjectModel.ReadOnlyCollection<DbColumn> GetColumnSchema(this DbDataReader reader)
{
if(reader is IDbColumnSchemaGenerator)
{
return ((IDbColumnSchemaGenerator)reader).GetColumnSchema();
}
throw new NotImplementedException());
}
}
}
It is recommended that the extension be used by consumers, for compatibility with .Net Framework. The details are mentioned in the compatibility section below.
The ADO.Net providers are the producers of the Column Schema. The interface will be implemented by the sub-classes of DbDataReader
in the ADO.Net providers.
E.g.
class SqlDataReader : DbDataReader, IDbColumnSchemaGenerator
{
...
public ReadOnlyCollection<DbColumn> GetDbColumnSchema()
{
ReadOnlyCollection<DbColumn> metadata = ... ;
// Build the list of columns metadata. Each columns metadata is represented by the DbColumn
return metadata; //
}
...
}
The consumers can retrieve the column metadata using an extension method on the DbDataReader. This extension will be provided in System.Data.Common
For a library consuming the DbDataReader
the code will look like the following:
using System.Data.Common;
namespace org.example.my
{
...
public void ProcessMetadata(DbDataReader reader)
{
ICollection<DbColumn> columnMetadata = reader.GetColumnSchema();
}
...
}
The extension method will have a different implementation in the partial facade to get the Schema. The implementation will use the DbDataReader.GetSchemaTable() api which returns the schema as a Datatable. This Datatable will be mapped to a ReadOnlyCollection and returned by the implementation of the extension in the partial facade.
In the next version of .Net Framework we add a virtual method on the DbDataReader to allow access to schema using ReadOnlyCollection<DbColumn>
The method will have the same signature as the extension method.
namespace System.Data.Common
{
public abstract class DbDataReader : ...
{
...
public virtual ReadOnlyCollection<DbColumn> GetColumnSchema()
{
// Map Datatable to the DbColumn as default implementation
// Providers can override the implementation for their requirement.
// The implementation will be similar to the implementation in
// partial facade implementation for .Net core v1.0
}
}
}
For a vNext of .Net core which targets .Net Framework with the API added to it, we can introduce the same API on .Net core with a default implementation in the base class DbDataReader
. At this point the providers have the option of providing the metadata either by overriding the method in the base class or by implementing the interface.
The base class implementation would check if the interface is implemented by the subclass and will provide the interface implementation while querying the base class API. The providers which do not implement the interface will have the option of overriding the base class method or implementing the interface.
The base class virtual function will have the same signature as the interface.
namespace System.Data.Common
{
public abstract class DbDataReader : ...
{
...
public virtual ReadOnlyCollection<DbColumn> GetColumnSchema()
{
if(this is IDbColumnSchemaGenerator)
{
return ((IDBColumnSchemaGenerator)this).GetColumnSchema();
}
throw new NotImplementedException();
}
}
}
The vNext of .Net core will have a changed implementation of the Extension method where the extension method will call the base class function to retrieve the schema. This will allow consumer DLL compiled on .Net core v1.0 to be able to run on vNext without any changes.
Any code compiled on .Net Core vNext will compile against the base class function as the member methods get precedence over the extensino method.
A consumer written on .Net Core on v1.0
In this case, the code will be compiled with the Extension method.
using System.Data.Common;
namespace org.example.my
{
...
public void ProcessMetadata(DbDataReader reader)
{
ICollection<DbColumn> columnMetadata = reader.GetColumnSchema();
}
...
}
In vNext of .Net core
If the consumer is recompiled, the code will be compiled with the base class DbDataReader which has the virtual method added to it. The base class virtual method takes precedence
using System.Data.Common;
namespace org.example.my
{
...
public void ProcessMetadata(DbDataReader reader)
{
ICollection<DbColumn> columnMetadata = reader.GetColumnSchema();
}
...
}
The producers in the .Net Core v1.0 don't have to implement the interface IDbColumnSchemaGenerator in v1.0
In vNext, they can continue to implement the interface and not change any code. However, they can choose to override the base class method to provide the Column Schema as well. Either ways the consumer will not need to change their code to consume the Schema
For new provider implementation, it is however recommended that they override
the base class virtual method in DbDataReader for Columns Metadata.