Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Implementation of DbProviderFactories. (#25410)
Browse files Browse the repository at this point in the history
* Implements #19826: DbConnection doesn't offer an internal property ProviderFactory

* Added test for #19826. Due to project misconfiguration it fails locally.

* Removed writeline

* Added comment to illustrate the purpose of the property which currently is dead code.

* Implementation of DbProviderFactories, ported from NetFx and adjusted to CoreFx

See #4571, #19825 and #19826

* Update for project files to get a netcoreapp test set

* Changes to project system to get the stuff compiled. Failed.

* Various changes:

- Updated config/csproj files for netcoreapp for tests.
- Properly moved DbProviderFactories file to right location
- Added DbProviderFactories type to ref project, split across 2 files
(one for netcoreapp specific methods)
- Changed SetFactory to ConfigureFactory
- Changed generic type argument to normal method parameter
- Removed default parameter values and added overloads

* Added tests for DbProviderFactories.

* Better subclass testing added and more tests added

* Moved all hard-coded strings to SR/Strings.resx. Added test for factory retrieval using a connection

* Removal of now redundant comment. See: #19885 (review)

* Comment correction for bad English

* Refactored API a bit, see: dotnet/standard#356 (comment)

* Added tests for reworked API. Added tests for wrong type passing

* Reverting change in sln file: See #19885 (review)

* Almost done implementation using DataTable internal storage. Refactoring now to concurrentdictionary

* Final work for #20903

* Corrections of the implementation->

- registrations of assembly type name are now deferred
- storage is now a struct, as the typename has to be registrated as well.
- corrected a wrong nameof() call.
- added tests for the change behavior in RegisterFactory(string, string)

* Final implementation

* Corrections made to code by request of @saurabh500

* Github for windows didn't commit this file in last commit... :/

* Again correcting component elements. These are automatically inserted so if they're back again, I can't remove them

* ... annnnd another component element. Last one I hope

* @divega requested changes

- Corrected sln file netcoreapp->netstandard
- Corrected wording in exception messages.
- Component elements are back, I'll leave them now, they get back regardless.
- Renamed column name constants to have the 'ColumnName' suffix for more clarity
- Made Instance constant be initialized with nameof(Instance)
- Added using for System.Reflection so less verbose code

* More @divega requested changes

- factored out a clone into its own method (GetProviderTypeFromTypeName)
- Reverted 'nameof(Instance)' to string, and renamed field
- Removed redundant null/length check in ctor of ProviderRegistration ctor

* Last nit removal for @divega
  • Loading branch information
FransBouma authored and saurabh500 committed Dec 5, 2017
1 parent 5cbb754 commit 7caa955
Show file tree
Hide file tree
Showing 11 changed files with 544 additions and 12 deletions.
7 changes: 7 additions & 0 deletions src/System.Data.Common/ref/System.Data.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2211,6 +2211,13 @@ protected DbProviderFactory() { }
public virtual System.Data.Common.DbDataSourceEnumerator CreateDataSourceEnumerator() { throw null; }
public virtual System.Data.Common.DbParameter CreateParameter() { throw null; }
}
public static partial class DbProviderFactories
{
public static DbProviderFactory GetFactory(string providerInvariantName) { throw null; }
public static DbProviderFactory GetFactory(DataRow providerRow) { throw null; }
public static DbProviderFactory GetFactory(DbConnection connection) { throw null; }
public static DataTable GetFactoryClasses() { throw null; }
}
[System.AttributeUsageAttribute((System.AttributeTargets)(128), AllowMultiple = false, Inherited = true)]
public sealed partial class DbProviderSpecificTypePropertyAttribute : System.Attribute
{
Expand Down
3 changes: 3 additions & 0 deletions src/System.Data.Common/ref/System.Data.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
<ItemGroup>
<Compile Include="System.Data.Common.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
<Compile Include="System.Data.Common.netcoreapp.cs"/>
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netfx'">
<Reference Include="mscorlib" />
<Reference Include="System" />
Expand Down
22 changes: 22 additions & 0 deletions src/System.Data.Common/ref/System.Data.Common.netcoreapp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.

// ------------------------------------------------------------------------------
// Changes to this file must follow the http://aka.ms/api-review process.
// ------------------------------------------------------------------------------

using System.Collections.Generic;

namespace System.Data.Common
{
public static partial class DbProviderFactories
{
public static void RegisterFactory(string providerInvariantName, string factoryTypeAssemblyQualifiedName) { throw null; }
public static void RegisterFactory(string providerInvariantName, Type factoryType) { throw null; }
public static void RegisterFactory(string providerInvariantName, DbProviderFactory factory) { throw null; }
public static bool TryGetFactory(string providerInvariantName, out DbProviderFactory factory) { throw null; }
public static bool UnregisterFactory(string providerInvariantName) { throw null; }
public static IEnumerable<string> GetProviderInvariantNames() { throw null; }
}
}
5 changes: 5 additions & 0 deletions src/System.Data.Common/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -512,4 +512,9 @@
<data name="DataColumns_RemoveExpression" xml:space="preserve"><value>Cannot remove this column, because it is part of an expression: {0} = {1}.</value></data>
<data name="DataRow_RowInsertTwice" xml:space="preserve"><value>The rowOrder value={0} has been found twice for table named '{1}'.</value></data>
<data name="Xml_ElementTypeNotFound" xml:space="preserve"><value>Cannot find ElementType name='{0}'.</value></data>
<data name="ADP_DbProviderFactories_InvariantNameNotFound" xml:space="preserve"><value>The specified invariant name '{0}' wasn't found in the list of registered .NET Data Providers.</value></data>
<data name="ADP_DbProviderFactories_NoInstance" xml:space="preserve"><value>The requested .NET Data Provider's implementation does not have an Instance field of a System.Data.Common.DbProviderFactory derived type.</value></data>
<data name="ADP_DbProviderFactories_FactoryNotLoadable" xml:space="preserve"><value>The registered .NET Data Provider's DbProviderFactory implementation type '{0}' couldn't be loaded.</value></data>
<data name="ADP_DbProviderFactories_NoAssemblyQualifiedName" xml:space="preserve"><value>The missing .NET Data Provider's assembly qualified name is required.</value></data>
<data name="ADP_DbProviderFactories_NotAFactoryType" xml:space="preserve"><value>The type '{0}' doesn't inherit from DbProviderFactory.</value></data>
</root>
45 changes: 34 additions & 11 deletions src/System.Data.Common/src/System.Data.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
<Compile Include="System\Data\ConstraintCollection.cs" />
<Compile Include="System\Data\ConstraintConverter.cs" />
<Compile Include="System\Data\ConstraintEnumerator.cs" />
<Compile Include="System\Data\DataColumn.cs" />
<Compile Include="System\Data\DataColumn.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\DataColumnChangeEvent.cs" />
<Compile Include="System\Data\DataColumnChangeEventHandler.cs" />
<Compile Include="System\Data\DataColumnCollection.cs" />
Expand All @@ -78,10 +80,14 @@
<Compile Include="System\Data\DataRowVersion.cs" />
<Compile Include="System\Data\DataRowView.cs" />
<Compile Include="System\Data\DataSerializationFormat.cs" />
<Compile Include="System\Data\DataSet.cs" />
<Compile Include="System\Data\DataSet.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\DataSetDateTime.cs" />
<Compile Include="System\Data\DataSysAttribute.cs" />
<Compile Include="System\Data\DataTable.cs" />
<Compile Include="System\Data\DataTable.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\DataTableClearEvent.cs" />
<Compile Include="System\Data\DataTableClearEventHandler.cs" />
<Compile Include="System\Data\DataTableCollection.cs" />
Expand All @@ -91,9 +97,13 @@
<Compile Include="System\Data\DataTableReader.cs" />
<Compile Include="System\Data\DataTableReaderListener.cs" />
<Compile Include="System\Data\DataTableTypeConverter.cs" />
<Compile Include="System\Data\DataView.cs" />
<Compile Include="System\Data\DataView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\DataViewListener.cs" />
<Compile Include="System\Data\DataViewManager.cs" />
<Compile Include="System\Data\DataViewManager.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\DataViewManagerListItemTypeDescriptor.cs" />
<Compile Include="System\Data\DataViewRowState.cs" />
<Compile Include="System\Data\DataViewSetting.cs" />
Expand Down Expand Up @@ -135,7 +145,9 @@
<Compile Include="System\Data\RecordManager.cs" />
<Compile Include="System\Data\StatementCompletedEventArgs.cs" />
<Compile Include="System\Data\StatementCompletedEventHandler.cs" />
<Compile Include="System\Data\RelatedView.cs" />
<Compile Include="System\Data\RelatedView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\RelationshipConverter.cs" />
<Compile Include="System\Data\Rule.cs" />
<Compile Include="System\Data\SchemaSerializationMode.cs" />
Expand All @@ -160,7 +172,9 @@
<Compile Include="System\Data\Common\BooleanStorage.cs" />
<Compile Include="System\Data\Common\ByteStorage.cs" />
<Compile Include="System\Data\Common\CharStorage.cs" />
<Compile Include="System\Data\Common\DataAdapter.cs" />
<Compile Include="System\Data\Common\DataAdapter.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\Common\DataColumnMapping.cs" />
<Compile Include="System\Data\Common\DataColumnMappingCollection.cs" />
<Compile Include="System\Data\Common\DataCommonEventSource.cs" />
Expand All @@ -170,9 +184,15 @@
<Compile Include="System\Data\Common\DataTableMappingCollection.cs" />
<Compile Include="System\Data\Common\DateTimeOffsetStorage.cs" />
<Compile Include="System\Data\Common\DateTimeStorage.cs" />
<Compile Include="System\Data\Common\DbCommand.cs" />
<Compile Include="System\Data\Common\DBCommandBuilder.cs" />
<Compile Include="System\Data\Common\DbConnection.cs" />
<Compile Include="System\Data\Common\DbCommand.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\Common\DBCommandBuilder.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\Common\DbConnection.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\Common\DbConnectionOptions.cs" />
<Compile Include="$(CommonPath)\System\Data\Common\DbConnectionOptions.Common.cs">
<Link>System\Data\Common\DbConnectionOptions.Common.cs</Link>
Expand All @@ -183,7 +203,9 @@
<Compile Include="System\Data\Common\DbConnectionStringBuilder.cs" />
<Compile Include="System\Data\Common\DbConnectionStringCommon.cs" />
<Compile Include="System\Data\Common\DbConnectionStringBuilderDescriptor.cs" />
<Compile Include="System\Data\Common\DbDataAdapter.cs" />
<Compile Include="System\Data\Common\DbDataAdapter.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="System\Data\Common\DbDataReader.cs" />
<Compile Include="System\Data\Common\DbDataReaderExtensions.cs" />
<Compile Include="System\Data\Common\DbDataRecord.cs" />
Expand All @@ -195,6 +217,7 @@
<Compile Include="System\Data\Common\DbParameter.cs" />
<Compile Include="System\Data\Common\DbParameterCollection.cs" />
<Compile Include="System\Data\Common\DbProviderFactory.cs" />
<Compile Include="System\Data\Common\DbProviderFactories.cs" />
<Compile Include="System\Data\Common\DbProviderSpecificTypePropertyAttribute.cs" />
<Compile Include="System\Data\Common\DBSchemaRow.cs" />
<Compile Include="System\Data\Common\DBSchemaTable.cs" />
Expand Down
2 changes: 2 additions & 0 deletions src/System.Data.Common/src/System/Data/Common/DbConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ protected DbConnection() : base()
/// </summary>
protected virtual DbProviderFactory DbProviderFactory => null;

internal DbProviderFactory ProviderFactory => DbProviderFactory;

[Browsable(false)]
public abstract string ServerVersion { get; }

Expand Down
200 changes: 200 additions & 0 deletions src/System.Data.Common/src/System/Data/Common/DbProviderFactories.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// 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 System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace System.Data.Common
{
public static partial class DbProviderFactories
{
private struct ProviderRegistration
{
internal ProviderRegistration(string factoryTypeAssemblyQualifiedName, DbProviderFactory factoryInstance)
{
this.FactoryTypeAssemblyQualifiedName = factoryTypeAssemblyQualifiedName;
this.FactoryInstance = factoryInstance;
}

internal string FactoryTypeAssemblyQualifiedName { get; }
/// <summary>
/// The cached instance of the type in <see cref="FactoryTypeAssemblyQualifiedName"/>. If null, this registation is seen as a deferred registration
/// and <see cref="FactoryTypeAssemblyQualifiedName"/> is checked the first time when this registration is requested through GetFactory().
/// </summary>
internal DbProviderFactory FactoryInstance { get; }
}

private static ConcurrentDictionary<string, ProviderRegistration> _registeredFactories = new ConcurrentDictionary<string, ProviderRegistration>();
private const string AssemblyQualifiedNameColumnName = "AssemblyQualifiedName";
private const string InvariantNameColumnName = "InvariantName";
private const string NameColumnName = "Name";
private const string DescriptionColumnName = "Description";
private const string ProviderGroupColumnName = "DbProviderFactories";
private const string InstanceFieldName = "Instance";

public static bool TryGetFactory(string providerInvariantName, out DbProviderFactory factory)
{
factory = GetFactory(providerInvariantName, throwOnError: false);
return factory != null;
}

public static DbProviderFactory GetFactory(string providerInvariantName)
{
return GetFactory(providerInvariantName, throwOnError: true);
}

public static DbProviderFactory GetFactory(DataRow providerRow)
{
ADP.CheckArgumentNull(providerRow, nameof(providerRow));

DataColumn assemblyQualifiedNameColumn = providerRow.Table.Columns[AssemblyQualifiedNameColumnName];
if (null == assemblyQualifiedNameColumn)
{
throw ADP.Argument(SR.ADP_DbProviderFactories_NoAssemblyQualifiedName);
}

string assemblyQualifiedName = providerRow[assemblyQualifiedNameColumn] as string;
if (string.IsNullOrWhiteSpace(assemblyQualifiedName))
{
throw ADP.Argument(SR.ADP_DbProviderFactories_NoAssemblyQualifiedName);
}

return GetFactoryInstance(GetProviderTypeFromTypeName(assemblyQualifiedName));
}


public static DbProviderFactory GetFactory(DbConnection connection)
{
ADP.CheckArgumentNull(connection, nameof(connection));

return connection.ProviderFactory;
}

public static DataTable GetFactoryClasses()
{
DataColumn nameColumn = new DataColumn(NameColumnName, typeof(string)) { ReadOnly = true };
DataColumn descriptionColumn = new DataColumn(DescriptionColumnName, typeof(string)) { ReadOnly = true };
DataColumn invariantNameColumn = new DataColumn(InvariantNameColumnName, typeof(string)) { ReadOnly = true };
DataColumn assemblyQualifiedNameColumn = new DataColumn(AssemblyQualifiedNameColumnName, typeof(string)) { ReadOnly = true };

DataTable toReturn = new DataTable(ProviderGroupColumnName) { Locale = CultureInfo.InvariantCulture };
toReturn.Columns.AddRange(new[] { nameColumn, descriptionColumn, invariantNameColumn, assemblyQualifiedNameColumn });
toReturn.PrimaryKey = new[] { invariantNameColumn };
foreach(var kvp in _registeredFactories)
{
DataRow newRow = toReturn.NewRow();
newRow[InvariantNameColumnName] = kvp.Key;
newRow[AssemblyQualifiedNameColumnName] = kvp.Value.FactoryTypeAssemblyQualifiedName;
newRow[NameColumnName] = string.Empty;
newRow[DescriptionColumnName] = string.Empty;
toReturn.AddRow(newRow);
}
return toReturn;
}

public static IEnumerable<string> GetProviderInvariantNames()
{
return _registeredFactories.Keys.ToList();
}

public static void RegisterFactory(string providerInvariantName, string factoryTypeAssemblyQualifiedName)
{
ADP.CheckArgumentLength(providerInvariantName, nameof(providerInvariantName));
ADP.CheckArgumentLength(factoryTypeAssemblyQualifiedName, nameof(factoryTypeAssemblyQualifiedName));

// this method performs a deferred registration: the type name specified is checked when the factory is requested for the first time.
_registeredFactories[providerInvariantName] = new ProviderRegistration(factoryTypeAssemblyQualifiedName, null);
}

public static void RegisterFactory(string providerInvariantName, Type providerFactoryClass)
{
RegisterFactory(providerInvariantName, GetFactoryInstance(providerFactoryClass));
}

public static void RegisterFactory(string providerInvariantName, DbProviderFactory factory)
{
ADP.CheckArgumentLength(providerInvariantName, nameof(providerInvariantName));
ADP.CheckArgumentNull(factory, nameof(factory));

_registeredFactories[providerInvariantName] = new ProviderRegistration(factory.GetType().AssemblyQualifiedName, factory);
}

public static bool UnregisterFactory(string providerInvariantName)
{
return !string.IsNullOrWhiteSpace(providerInvariantName) && _registeredFactories.TryRemove(providerInvariantName, out _);
}

private static DbProviderFactory GetFactory(string providerInvariantName, bool throwOnError)
{
if (throwOnError)
{
ADP.CheckArgumentLength(providerInvariantName, nameof(providerInvariantName));
}
else
{
if (string.IsNullOrWhiteSpace(providerInvariantName))
{
return null;
}
}
bool wasRegistered = _registeredFactories.TryGetValue(providerInvariantName, out ProviderRegistration registration);
if (!wasRegistered)
{
return throwOnError ? throw ADP.Argument(SR.Format(SR.ADP_DbProviderFactories_InvariantNameNotFound, providerInvariantName)) : (DbProviderFactory)null;
}
DbProviderFactory toReturn = registration.FactoryInstance;
if (toReturn == null)
{
// Deferred registration, do checks now on the type specified and register instance in storage.
// Even in the case of throwOnError being false, this will throw when an exception occurs checking the registered type as the user has to be notified the
// registration is invalid, even though the registration is there.
toReturn = GetFactoryInstance(GetProviderTypeFromTypeName(registration.FactoryTypeAssemblyQualifiedName));
RegisterFactory(providerInvariantName, toReturn);
}
return toReturn;
}

private static DbProviderFactory GetFactoryInstance(Type providerFactoryClass)
{
ADP.CheckArgumentNull(providerFactoryClass, nameof(providerFactoryClass));
if (!providerFactoryClass.IsSubclassOf(typeof(DbProviderFactory)))
{
throw ADP.Argument(SR.Format(SR.ADP_DbProviderFactories_NotAFactoryType, providerFactoryClass.FullName));
}

FieldInfo providerInstance = providerFactoryClass.GetField(InstanceFieldName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static);
if (null == providerInstance)
{
throw ADP.InvalidOperation(SR.ADP_DbProviderFactories_NoInstance);
}
if (!providerInstance.FieldType.IsSubclassOf(typeof(DbProviderFactory)))
{
throw ADP.InvalidOperation(SR.ADP_DbProviderFactories_NoInstance);
}
object factory = providerInstance.GetValue(null);
if (null == factory)
{
throw ADP.InvalidOperation(SR.ADP_DbProviderFactories_NoInstance);
}
return (DbProviderFactory)factory;
}


private static Type GetProviderTypeFromTypeName(string assemblyQualifiedName)
{
Type providerType = Type.GetType(assemblyQualifiedName);
if (null == providerType)
{
throw ADP.Argument(SR.Format(SR.ADP_DbProviderFactories_FactoryNotLoadable, assemblyQualifiedName));
}
return providerType;
}
}
}
1 change: 1 addition & 0 deletions src/System.Data.Common/tests/Configurations.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildConfigurations>
netcoreapp;
netstandard;
</BuildConfigurations>
</PropertyGroup>
Expand Down
Loading

0 comments on commit 7caa955

Please sign in to comment.