DAAB contribution challenges with DatabaseSyntheticConfigSettings

Topics: Data Access Application Block
Apr 24, 2014 at 2:17 PM
Greetings,

I am working to extend the DAAB for use with Teradata. On the whole this is going well (I'm a novice programmer), but I have a challenge with the DatabaseSyntheticConfigSettings (DSCS) class which is called by DatabaseProviderFactory. DSCS contains a handful of private DbProviderMapping instances that map, for example, OracleDatabase as a default Oracle provider. Clearly, no such mapping exists for Teradata. The default GenericDatabase works well for many unit tests, but there are some that fail because it is necessary to use functionality in my TdDatabase class directly (for instance: "Parameter discovery is not supported for connections using GenericDatabase").

DSCS is able to map OracleDatabase since this class is defined (yet now deprecated) in the base DAAB library itself. I currently have the Teradata contribution in a separate project; I cannot add a reference to the Teradata project since it references the DAAB - this would create a circular dependency. I could add the Teradata content into the DAAB itself, but then it becomes a project for my use only; then there's no chance to put this up on Nuget, and anyone who wants to do the same thing has to reinvent the wheel. Or I can derive new classes from DatabaseProviderFactory and DatabaseSyntheticConfigSettings, but that seems to defeat the purpose of a single all-purpose DatabaseProviderFactory.

I would think this would be a general challenge for anyone who wishes to extend the DAAB, regardless of database type. Anyone have recommendations how to proceed?
Apr 28, 2014 at 7:51 AM
You should be able to create a custom Database implementation for Teradata similar to what is done for the entlibcontrib OracleDatabase. To avoid circular dependencies you would have to break the TeradataDatabase into its own assembly that just references DAAB.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
May 4, 2014 at 12:16 PM
Yes, I've already created a custom Database implementation for Teradata and have a number of unit tests passing. The problem is that I cannot use DatabaseProviderFactory to create my Database instances because of its dependency on DatabaseSyntheticConfigSettings; I have to generate a TdDatabase instance directly instead of using DatabaseProviderFactory (compare this with the Setup method of the ODP.NET unit tests). The point of my note above is that there is Oracle-specific content in the DAAB (specifically DatabaseSyntheticConfigSettings) which appears to be leveraged by the ODP.NET contribution. Oracle is the only database for which database-specific content exists in the DAAB (apart from SQL Server, but that is understandable), and ODP.NET is in turn the only DAAB extension which exists for EL6.

My hypothesis is that if the defaultOracleMapping field were removed from DatabaseSyntheticConfigSettings, then the ODP.NET unit tests would fail, and ODP.NET also could not use DatabaseProviderFactory - if it were removed, then DatabaseProviderFactory could return instances only for SqlDatabase and GenericDatabase. If I verified this hypothesis via an experiment, could the Entlib team help to find a more extensible solution?
May 6, 2014 at 6:18 AM
There are a variety of database implementations for Enterprise Library (e.g. DB2, MySql) so there isn't an issue with only Oracle & SQL Server working. It looks like only Oracle ODP.NET has been moved to Enterprise Library 6 but it should be fairly easy for someone to move the other providers to Enterprise Library 6 since not much really changed in the Data Access Application Block (functionally or with respect to design) between releases.

Enterprise Library 6 has a reference to the deprecated System.Data.OracleClient which I think is the Oracle references you are seeing. ODP.NET uses a different DbProviderFactory: Oracle.DataAccess.Client.

Typically, the database being used will supply a database provider (e.g. Oracle Data Provider for .NET (ODP.NET)) which needs to be installed (DbProviderFactory ( usually set up in machine.config). Next an Enterprise Library provider mapping will map the invariant provider name in the DbProviderFactory to an Enterprise Library database type. It is this mapping that allows a specific database to be returned instead of a GenericDatabase.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
May 9, 2014 at 1:56 AM
Thanks for the response. Let me look into this (hopefully this weekend) and see if I can implement my code the way you recommend.
Jul 11, 2014 at 11:13 PM
I am returning to this topic after a bit of a delay. I have confirmed that the Teradata database provider is registered under <DbProviderFactories> in machine.config. I am, however, unclear about your statement "Next an Enterprise Library provider mapping will map the invariant provider name in the DbProviderFactory to an Enterprise Library database type". I find that DbProviderMapping is used in DatabaseSyntheticConfigSettings to map SqlDatabase, OracleDatabase, and GenericDatabase; but it is not clear how to use DbProviderMapping (assuming this is the class to which you refer) to map a new database without modifying the base DAAB block. Can you provide further information?
Jul 15, 2014 at 5:53 AM
Yes, you would need to map the database type to the provider name so that Enterprise Library constructs the proper Database object (and not a GenericDatabase). See Oracle ODP.NET Data Provider for sample configuration.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jul 15, 2014 at 9:13 PM
I made the recommended modifications to my app.config file, but still get a generic database from DatabaseProviderFactory. I've pasted my app.config file below in case you see something I've omitted. I tried all the different version settings that seem relevant (not sure if this even matters). TdConnectionSettings is based closely on the OracleConnectionSettings class which is called by the Data.OdpNet test cases. Note that Teradata.Client.Provider is already registered in machine.config, hence the empty DbProviderFactories below.

I'll attempt to map the method call sequence so it's a little clearer why I still suspect DatabaseSyntheticConfigSettings. In the meanwhile, any further suggestions on what I may be missing?
<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data"/>
    <section name="TdConnectionSettings" type="Data.Teradata.Configuration.TdConnectionSettings, Data.Teradata"/>
    <section name="spikeSettings" type="Microsoft.Practices.EnterpriseLibrary.Data.Tests.Configuration.SpikeSettings, Microsoft.Practices.EnterpriseLibrary.Data.Tests"/>
  </configSections>

  <system.data>
    <DbProviderFactories>
    </DbProviderFactories>
  </system.data>

  <dataConfiguration defaultDatabase="Service_Dflt">
    <providerMappings>
      <add databaseType="Data.Teradata.TdDatabase, Data.Teradata, Version=1.0.0.0" name="Teradata.Client.Provider"/>
    </providerMappings>
  </dataConfiguration>

  <connectionStrings>
    <add name="Service_Dflt" providerName="System.Data.SqlClient" connectionString="server=(local)\SQLEXPRESS;database=Northwind;Integrated Security=true"/>
    <add name="TdTest" providerName="Teradata.Client.Provider" connectionString="Data Source=192.168.219.129;user id=dbadmin;password=dbadmin"/>
  </connectionStrings>

  <spikeSettings>
    <sampleData>
      <add name="Sales" foo="This is the foo value" bar="42"/>
    </sampleData>
  </spikeSettings>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
  </startup>
</configuration>
Jul 15, 2014 at 9:42 PM
Actually, let me restate the problem above with some new information. Any call to the Create() method on a DatabaseProviderFactory instance winds up in this private method within DatabaseSyntheticConfigSettings:
        private static DbProviderMapping GetDefaultMapping(string dbProviderName)
        {
            // try to short circuit by default name
            if (DbProviderMapping.DefaultSqlProviderName.Equals(dbProviderName))
                return defaultSqlMapping;

            if (DbProviderMapping.DefaultOracleProviderName.Equals(dbProviderName))
                return defaultOracleMapping;

            // get the default based on type
            var providerFactory = DbProviderFactories.GetFactory(dbProviderName);

            if (SqlClientFactory.Instance == providerFactory)
                return defaultSqlMapping;

#pragma warning disable 612, 618
            if (OracleClientFactory.Instance == providerFactory)
                return defaultOracleMapping;
#pragma warning restore 612, 618

            return null;
        }
The DbProviderFactories.GetFactory(dbProviderName) method returns a Teradata.Client.Provider.TdFactory instance... however, since it is neither a SqlClientFactory.Instance nor an OracleClientFactory.Instance, this method returns null. Because the DbProviderMapping is null, the private method GetProviderMapping method within DatabaseSyntheticConfigSettings returns the generic database:
        private static DbProviderMapping GetProviderMapping(string dbProviderName, DatabaseSettings databaseSettings)
        {
            if (databaseSettings != null)
            {
                var existingMapping = databaseSettings.ProviderMappings.Get(dbProviderName);
                if (existingMapping != null)
                {
                    return existingMapping;
                }
            }

            var defaultMapping = GetDefaultMapping(dbProviderName);
            return defaultMapping ?? GetGenericMapping();
        }

        private static DbProviderMapping GetGenericMapping()
        {
            return defaultGenericMapping;
        }
So I know definitively that a TdProviderFactory is returned by DbProviderFactories, but there is no logic in DatabaseSyntheticConfigSettings to handle it, so I can never get anything back from DatabaseProviderFactory except a generic database. The test cases in ODP.NET follow this exact same flow, but they succeed in returning an OracleDatabase due to the comparison with OracleClientFactory.Instance in GetDefaultMapping.

Let me know if you agree with the assessment, or if I'm still missing something. If the former, I'll log this as an issue.
Jul 15, 2014 at 10:18 PM
Actually, I don't agree. The default logic shouldn't be executed as there is a custom providerMapping defined. Also, other Database types such as Db2, MySQL, ODB.NET are working.

Do you also have a TdDatabaseData configuration class in addition to the TdDatabase (and the TdDatabase is attributed with [ConfigurationElementType(typeof(TdDatabaseData))]?

It would probably be easier to see what is going on if you could create a functional sample project that demonstrates the behavior (and includes your data access provider classes).

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jul 15, 2014 at 11:26 PM
I do have a TdDatabaseData config class, modeled closely on the ODP.NET implementation. My TdDatabase class is attributed as you mention above; I had commented it out (I'm not sure why) but after restoring it and doing a quick run through the various providerMappings in my app.config, I still get only a generic database.

I can provide my Data.Teradata and Data.Teradata.Tests projects to you. I don't see a way to attach objects here; is use of the entlib.support@live.com appropriate or do you have a preferred way for me to get these to you?
Jul 16, 2014 at 3:13 AM
Yes, you can zip up your projects and email or upload to a file share (e.g. OneDrive).

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to