Skip to content

Instantly share code, notes, and snippets.

@saurabh500
Last active February 4, 2016 23:03
Show Gist options
  • Save saurabh500/d27f5fcbfa7bcf12623b to your computer and use it in GitHub Desktop.
Save saurabh500/d27f5fcbfa7bcf12623b to your computer and use it in GitHub Desktop.
GetColumnSchema API

API for GetColumnSchema

Problem

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.

Progress

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.

https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/breaking-change-rules.md

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.

Proposal

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.

Producer

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; // 
		}
	...
}

Consumer

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();
			}
		...
}

Compatibility of libraries written on .Net Core with .Net Framework

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.

vNext

Change in .Net Framework (Desktop)

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 
		}
	}

}

.Net Core: vNext

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.

Examples

Consumers

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();
            }
        ...
}

Producers

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment