The .NET framework offers a provider model for ADO.NET. It is possible to register ADO.NET providers that can be used to dynamically create connections and other kinds of objects:
var myProvider = DbProviderFactories.GetFactory("MyProvider");
using (var con = myProvider.CreateConnection())
using (var cmd = con.CreateCommand())
{
con.ConnectionString = "My connection string";
con.Open();
//do something with cmd
}
The provider name was also mandatory for connection strings specified in configuration files:
<connectionStrings>
<addconnectionString="My connection string"name="MyConnection"providerName="MyProvider"/>
</connectionStrings>
var provider = DbProvider0Factories.GetFactory(ConfigurationManager.ConnectionStrings["MyConnection"].ProviderName);
Before .NET 4.5, this was controlled through the DbProviderFactories section of Machine.config, and it was possible to register our own in a local Web/App.config file. Presently, the built-in providers – SqlClientFactory, OdbcFactory, OleDbFactory and OracleClientFactory - are no longer configured through a file, but are set automatically by the framework, therefore, cannot be easily changed. The registered providers can be inspected through the GetFactoryClasses() method of DbProviderFactories:
var providers = DbProviderFactories.GetFactories();
Using some reflection, however, it is possible to switch a built-in provider for a custom one. Why would you want to do that? Well, for example, to add custom profiling and logging to a connection or command.
The most important base classes in the ADO.NET model are DbConnection and DbCommand. DbCommand can be obtained through an existing DbConnection or from a DbProviderFactory. So, if we want to intercept DbCommand. we need to create custom DbConnection and DbCommand classes:
publicclass WrapperCommand : DbCommand
{
privatestaticreadonly PropertyInfo canRaiseEventsProp = typeof(Component).GetProperty("CanRaiseEvents", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty);
privatestaticreadonly MethodInfo disposeMethod = typeof(Component).GetMethod("Dispose", BindingFlags.NonPublic | BindingFlags.Instance);
privatestaticreadonly MethodInfo getServiceMethod = typeof(Component).GetMethod("GetService", BindingFlags.NonPublic | BindingFlags.Instance);
privatereadonly DbCommand original;
public WrapperCommand(DbCommand original)
{
this.original = original;
}
publicoverridevoid Prepare()
{
this.original.Prepare();
}
publicoverridestring CommandText
{
get { returnthis.original.CommandText; }
set { this.original.CommandText = value; }
}
publicoverrideint CommandTimeout
{
get { returnthis.original.CommandTimeout; }
set { this.original.CommandTimeout = value; }
}
publicoverride CommandType CommandType
{
get { returnthis.original.CommandType; }
set { this.original.CommandType = value; }
}
publicoverride UpdateRowSource UpdatedRowSource
{
get { returnthis.original.UpdatedRowSource; }
set { this.original.UpdatedRowSource = value; }
}
protectedoverride DbConnection DbConnection
{
get { returnthis.original.Connection; }
set { this.original.Connection = value; }
}
protectedoverride DbParameterCollection DbParameterCollection
{
get { returnthis.original.Parameters; }
}
protectedoverride DbTransaction DbTransaction
{
get { returnthis.original.Transaction; }
set { this.original.Transaction = value; }
}
publicoverridebool DesignTimeVisible
{
get { returnthis.original.DesignTimeVisible; }
set { this.original.DesignTimeVisible = value; }
}
publicoverridevoid Cancel()
{
this.original.Cancel();
}
protectedoverride DbParameter CreateDbParameter()
{
returnthis.original.CreateParameter();
}
protectedoverride DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
returnthis.original.ExecuteReader(behavior);
}
publicoverrideint ExecuteNonQuery()
{
returnthis.original.ExecuteNonQuery();
}
publicoverrideobject ExecuteScalar()
{
returnthis.original.ExecuteScalar();
}
protectedoverridebool CanRaiseEvents
{
get
{
return (bool)canRaiseEventsProp.GetValue(this.original, null);
}
}
publicoverride ObjRef CreateObjRef(Type requestedType)
{
returnthis.original.CreateObjRef(requestedType);
}
protectedoverridevoid Dispose(bool disposing)
{
disposeMethod.Invoke(this.original, newobject[] { disposing });
}
protectedoverride Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
{
returnthis.original.ExecuteReaderAsync(behavior, cancellationToken);
}
publicoverride Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken)
{
returnthis.original.ExecuteNonQueryAsync(cancellationToken);
}
publicoverride Task<object> ExecuteScalarAsync(CancellationToken cancellationToken)
{
returnthis.original.ExecuteScalarAsync(cancellationToken);
}
protectedoverrideobject GetService(Type service)
{
return getServiceMethod.Invoke(this.original, newobject[] { service });
}
publicoverrideobject InitializeLifetimeService()
{
returnthis.original.InitializeLifetimeService();
}
publicoverride ISite Site
{
get { returnthis.original.Site; }
set { this.original.Site = value; }
}
}
publicclass WrapperConnection : DbConnection
{
privatestaticreadonly MethodInfo disposeMethod = typeof(Component).GetMethod("Dispose", BindingFlags.NonPublic | BindingFlags.Instance);
privatestaticreadonly MethodInfo getServiceMethod = typeof(Component).GetMethod("GetService", BindingFlags.NonPublic | BindingFlags.Instance);
privatestaticreadonly PropertyInfo canRaiseEventsProp = typeof(Component).GetProperty("CanRaiseEvents", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty);
privatestaticreadonly MethodInfo onStateChangeMethod = typeof(DbConnection).GetMethod("OnStateChange", BindingFlags.NonPublic | BindingFlags.Instance);
privatereadonly DbConnection original;
public WrapperConnection(DbConnection original)
{
this.original = original;
}
protectedoverride DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
{
returnthis.original.BeginTransaction(isolationLevel);
}
publicoverridevoid Close()
{
this.original.Close();
}
publicoverridevoid ChangeDatabase(string databaseName)
{
this.original.ChangeDatabase(databaseName);
}
publicoverridevoid Open()
{
this.original.Open();
}
publicoverridestring ConnectionString
{
get { returnthis.original.ConnectionString; }
set { this.original.ConnectionString = value; }
}
publicoverridestring Database
{
get { returnthis.original.Database; }
}
publicoverride ConnectionState State
{
get { returnthis.original.State; }
}
publicoverridestring DataSource
{
get { returnthis.original.DataSource; }
}
publicoverridestring ServerVersion
{
get { returnthis.original.ServerVersion; }
}
protectedoverride DbCommand CreateDbCommand()
{
returnnew WrapperCommand(this.original.CreateCommand());
}
publicoverride ISite Site
{
get { returnthis.original.Site; }
set { this.original.Site = value; }
}
protectedoverride DbProviderFactory DbProviderFactory
{
get { return WrapperProviderFactory.Instance; }
}
publicoverrideobject InitializeLifetimeService()
{
returnthis.original.InitializeLifetimeService();
}
protectedoverridevoid Dispose(bool disposing)
{
disposeMethod.Invoke(this.original, newobject[] { disposing });
}
protectedoverrideobject GetService(Type service)
{
return getServiceMethod.Invoke(this.original, newobject[] { service });
}
protectedoverridebool CanRaiseEvents
{
get { return (bool)canRaiseEventsProp.GetValue(this.original, null); }
}
publicoverride ObjRef CreateObjRef(Type requestedType)
{
returnthis.original.CreateObjRef(requestedType);
}
publicoverrideint ConnectionTimeout
{
get { returnthis.original.ConnectionTimeout; }
}
publicoverridevoid EnlistTransaction(System.Transactions.Transaction transaction)
{
this.original.EnlistTransaction(transaction);
}
publicoverride DataTable GetSchema()
{
returnthis.original.GetSchema();
}
publicoverride DataTable GetSchema(string collectionName)
{
returnthis.original.GetSchema(collectionName);
}
publicoverride DataTable GetSchema(string collectionName, string[] restrictionValues)
{
returnthis.original.GetSchema(collectionName, restrictionValues);
}
protectedoverridevoid OnStateChange(StateChangeEventArgs stateChange)
{
onStateChangeMethod.Invoke(this.original, newobject[] { stateChange });
}
publicoverride Task OpenAsync(CancellationToken cancellationToken)
{
returnthis.original.OpenAsync(cancellationToken);
}
publicoverrideevent StateChangeEventHandler StateChange
{
add { this.original.StateChange += value; }
remove { this.original.StateChange -= value; }
}
}
Our implementations need to take preexisting objects, to which they will delegate the actual work. Some reflection is needed to access non-public members. In order to make some use of it, we could add some virtual methods or events, for example, to add custom code before and after SQL commands are executed.
Next we need some provider factory that can be registered and replace the default functionality of some provider, here’s a possible implementation:
publicsealedclass WrapperProviderFactory : DbProviderFactory
{
privatestaticreadonly FieldInfo providerTableField = typeof(DbProviderFactories).GetField("_providerTable", BindingFlags.NonPublic | BindingFlags.Static);
privatestaticreadonly FieldInfo instanceField = typeof(WrapperProviderFactory).GetField("Instance", BindingFlags.Public | BindingFlags.Static);
publicstaticreadonly WrapperProviderFactory Instance;
privatereadonly DbProviderFactory original;
private WrapperProviderFactory(DbProviderFactory original)
{
this.original = original;
DbProviderFactories.GetFactoryClasses();
var dt = providerTableField.GetValue(null) as DataTable;
var row = dt.Rows.Find(original.GetType().Namespace);
dt.Columns["AssemblyQualifiedName"].ReadOnly = false;
row["AssemblyQualifiedName"] = typeof(WrapperProviderFactory).AssemblyQualifiedName;
dt.Columns["AssemblyQualifiedName"].ReadOnly = true;
}
publicstaticvoid Initialize(DbProviderFactory original)
{
instanceField.SetValue(null, new WrapperProviderFactory(original));
}
publicoverride DbConnection CreateConnection()
{
returnnew WrapperConnection(this.original.CreateConnection());
}
publicoverride DbCommand CreateCommand()
{
returnnew WrapperCommand(this.original.CreateCommand());
}
//other method overrides
}
The requirements for a custom DbProviderFactory are simple:
- It must have a public static field named Instance;
- Providers must reside in a namespace that is used to identify them (System.Data.SqlClient for SQL Server provider, etc).
If you are curious, the first call to GetFactoryClasses() is required so that .NET can setup internally its structures and populate them with the default providers.
Our delegation first has the framework create the list of providers and then modify it so as to replace the provider that we want. We will need to initialize our provider by passing it an existing provider (Initialize) before doing something else:
WrapperProviderFactory.Initialize(SqlClientFactory.Instance);
And that’s it. As soon as you call this, your wrapper implementation becomes registered, and whenever the named provider is retrieved, .NET will return this implementation.