5.9. Write Database-Agnostic Code
In developing ADO.NET, Microsoft set out to create a new data access architecture that would be more flexible, better performing, and more easily extensible than its previous COM-based OLE DB and ADO architectures. They did this by creating a model where every data source must supply its own data provider: a set of managed classes that allow you to connect to a particular data source (e.g., SQL Server, Oracle), execute commands, and retrieve data. In order to ensure that these providers are consistent, each implements a standard set of interfaces. However, this approach creates major challenges for developers who want to write provider-agnostic codefor example, a basic database routine that can be used equally well with the SQL Server provider or the Oracle provider. Usually, you use provider-agnostic code because you aren't sure what type of database the final version of an application will use, or because you anticipate the need to migrate to a different database in the future.
Note: Want a way to write database code that isn't bound to a specific data source? This challenge becomes a whole lot easier in . NET 2.0.
.NET 2.0 takes major steps to facilitate generic database coding by introducing a new factory model. (A factory model is a pattern where one class has the exclusive responsibility for creating instances of other classes.) In this model, you can use a database provider factory to build the ADO.NET connections, commands, and many other types of objects required for a particular database. The factory automatically returns the type of object that you need for your data source (e.g., a SqlCommand or an OracleCommand), but when you write your code, you don't worry about these details. Instead, you write generic commands without regard to the particular details of the data source.
5.9.1. How do I do that?
In provider-agnostic code, you still use all the same strongly typed objects. However, your code manipulates these objects using common interfaces. For example, every command object, whether it's used for SQL Server or Oracle, implements the common IDbCommand interface, which guarantees a basic set of methods and properties.
Note: Because provider-agnostic code attempts to be as generic as possible, it's more difficult to properly optimize a database. As a result, this technique isn't suitable for most large-scale enterprise applications.
Provider-agnostic code is structured so that you specify the type of database you're using early on, usually by reading some information from a configuration file. You use this information to retrieve a DbProviderFactory for your database. Here's an example where the factory string is hardcoded:
Dim Factory As String = "System.Data.SqlClient" Dim Provider As DbProviderFactory Provider = DbProviderFactories.GetFactory(Factory)
In this example, the code uses the shared GetFactory( ) method of the System.Data.Common.DbProviderFactories class. It specifies a string that identifies the provider name. For example, if you use the string System.Data.SqlClient, the GetFactory() method returns a System.Data.SqlClient.SqlClientFactory object. The DbProviderFactories class can create factories for all the data providers included with .NET, because they are explicitly configured in the machine.config configuration file on the current computer. Essentially, the configuration record tells the DbProviderFactories class to create a SqlClientFactory when the programmer passes the exact string "System.Data.SqlClient." If you develop your own provider, you can also register it to work in this way (although that task is beyond the scope of this lab).
The SqlClientFactory object has the built-in smarts to create all the objects used by the SQL Server provider. However, your code can be completely generic. Instead of interacting with the specific SqlClientFactory class type, it should use the generic base class DbProviderFactory. That way, your code can work with any type of DbProviderFactory, and therefore support any database provider.
Once you have the DbProviderFactory, you can create other types of strongly typed ADO.NET objects using a set of common methods by using the CreateXxx() methods. These include:
All these methods create a provider-specific version of the object they name.
To get a better understanding of how generic database code works, it helps to try out a complete example that can switch from one data provider to another on the fly. First of all, you need to create an application configuration file that stores all the provider-specific details. To do this, create a console application and open the app.config file. Add the following three settings, which specify the factory name, the connection string for the database, and the query to perform:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="Factory" value="System.Data.SqlClient" /> <add key="Connection" value= "Data Source=localhost;Initial Catalog=Northwind;Integrated Security=SSPI" /> <add key="Query" value="SELECT * FROM Orders" /> </appSettings> </configuration>
This example uses the SQL Server provider to connect to the Northwind database and retrieve a list of all the records in the Orders table.
Now you can retrieve the configuration file information and use it with the DbProviderFactories class to create every ADO.NET provider object you need. In Example 5-9, the query is executed, a DataSet is filled, and a list of OrderID values is displayed in the console window.
Example 5-9. Using DbProviderFactories to write database-agnostic code
Imports System.Data.Common Imports System.Configuration Module GenericDatabaseTest Public Sub Main( ) ' Get all the information from the configuration file. Dim Factory, Connection, Query As String Factory = ConfigurationManager.AppSettings("Factory") Connection = ConfigurationSettings.AppSettings("Connection") Query = ConfigurationManager.AppSettings("Query") ' Get the factory for this provider. Dim Provider As DbProviderFactory Provider = DbProviderFactories.GetFactory(Factory) ' Use the factory to create a connection. Dim con As DbConnection = Provider.CreateConnection( ) con.ConnectionString = Connection ' Use the factory to create a data adapter ' and fill a DataSet. Dim Adapter As DbDataAdapter = Provider.CreateDataAdapter Adapter.SelectCommand = Provider.CreateCommand( ) Adapter.SelectCommand.Connection = con Adapter.SelectCommand.CommandText = Query Dim ds As New DataSet Adapter.Fill(ds, "Orders") ' Display the retrieved information. For Each Row As DataRow In ds.Tables("Orders").Rows Console.WriteLine(Row("OrderID")) Next End Sub End Module
Mostly, this is a fairly pedestrian piece of data access logic. The only exciting part is that you can switch from one provider to another without modifying any of the code or recompiling. You just need to modify the provider information and connection string in the configuration file. For example, make these changes to the configuration file to access the same table through the slower OLE DB provider interface:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="Factory" value="System.Data.OleDb" /> <add key="Connection" value= "Provider=SQLOLEDB;Data Source=localhost;Initial Catalog=Northwind;Integrated Security=SSPI" /> <add key="Query" value="SELECT * FROM Orders" /> </appSettings> </configuration>
After saving the configuration file, you can run the application again. It will work just as well, displaying the same list of order records.
5.9.2. What about...
...the challenges you'll encounter in writing database-agnostic programs? The new factory approach is a giant leap forward for those who want to write provider-agnostic code. However, a slew of problems (some minor and some more significant) still remain. These include:
5.9.3. Where can I learn more?
Unfortunately, there isn't much documentation yet in the MSDN Help about provider-agnostic coding. However, you can get a good overview with additional examples from the Microsoft whitepaper at http://msdn.microsoft.com/library/en-us/dnvs05/html/vsgenerics.asp.