
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
742 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
// Get a specific connection.
IDbConnection myCn = GetConnection(DataProvider.SqlServer); Console.WriteLine("Your connection is a {0}", myCn.GetType().Name);
// Open, use and close connection...
Console.ReadLine();
}
//This method returns a specific connection object
//based on the value of a DataProvider enum. static IDbConnection GetConnection(DataProvider dp)
{
IDbConnection conn = null; switch (dp)
{
case DataProvider.SqlServer: conn = new SqlConnection(); break;
case DataProvider.OleDb:
conn = new OleDbConnection(); break;
case DataProvider.Odbc:
conn = new OdbcConnection(); break;
case DataProvider.Oracle:
conn = new OracleConnection(); break;
}
return conn;
}
}
}
The benefit of working with the general interfaces of System.Data (or for that matter, the abstract base classes of System.Data.Common) is that you have a much better chance of building a flexible code base that can evolve over time. For example, perhaps today you are building an application targeting Microsoft SQL Server, but what if your company switches to Oracle months down the road? If you build a solution that hard-codes the MS SQL Server–specific types of System.Data. SqlClient, you will obviously need to edit, recompile, and redeploy the assembly should the backend database management system change.
Increasing Flexibility Using Application Configuration Files
To further increase the flexibility of your ADO.NET applications, you could incorporate a client-side *.config file that makes use of custom key/value pairs within the <appSettings> element. Recall from Chapter 15 that custom data stored within a *.config file can be programmatically obtained using types within the System.Configuration namespace. For example, assume you have specified a data provider value within a configuration file as follows:
<configuration>
<appSettings>
<!-- This key value maps to one of our enum values-->
<add key="provider" value="SqlServer"/> </appSettings>
</configuration>




746 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
Save (and then close) your new table and be sure you name this new database object as Inventory. At this point, you should see the Inventory table under the Tables node of the Server Explorer. Right-click the Inventory table icon and select Show Table Data. Enter a handful of new automobiles of your choosing (to make it interesting, be sure to have some cars that have identical colors and makes). Figure 22-6 shows one possible list of inventory.
Figure 22-6. Populating the Inventory table
Authoring the GetPetName() Stored Procedure
Later in this chapter in the section “Executing a Stored Procedure,” we will examine how to make use of ADO.NET to invoke stored procedures. As you may already know, stored procedures are routines stored within a particular database that operate often on table data to yield a return value. We will add a single stored procedure that will return an automobile’s pet name based on the supplied CarID value. To do so, simply right-click the Stored Procedures node of the AutoLot database within the Server Explorer and select Add New Stored Procedure. Enter the following within the resulting editor:
CREATE PROCEDURE GetPetName @carID int,
@petName char(10) output AS
SELECT @petName = PetName from Inventory where CarID = @carID
When you save your procedure, it will automatically be named GetPetName, based on your CREATE PROCEDURE statement. Once you are done, you should see your new stored procedure within the Server Explorer (see Figure 22-7).
■Note Stored procedures are not required to return data using output parameters as shown here; however, doing so will set the stage for talking about the Direction property of the SqlParameter later in this chapter in the section “Executing a Stored Procedure.”


748 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
Figure 22-9. Populating the Customers table
Our final table, Orders, will be used to represent the automobile a given customer is interested in purchasing by mapping OrderID values to CarID/CustID values. Figure 22-10 shows the structure of our final table (again note that OrderID is the primary key).
Figure 22-10. Designing the Orders table
Now, add data to your Orders table. Assuming that the OrderID value begins at 1000, select a unique CarID for each CustID value (see Figure 22-11).
Figure 22-11. Populating the Orders table
Given the entries used in this text, we can see that Dave Brenner (CustID = 1) is interested in the red Volkswagen (CarID = 2), while Pat Walton (CustID = 3) has her eye on the tan Volkswagen (CarID = 8).

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
749 |
Visually Creating Table Relationships
The final task is to establish parent/child table relationships between the Customers, Orders, and Inventory tables. Doing so using Visual Studio 2008 is quite simple, as we can elect to insert a new database diagram by right-clicking the Database Diagrams node of the AutoLot database in the Server Explorer. Once you do so, be sure to select each of the tables from the resulting dialog box before clicking the Add button.
To establish the relationships between the tables, begin by clicking the CarID key of the Inventory table and (while holding down the mouse button) drag to the CarID field of the Orders table. Once you release the mouse, accept all defaults from the resulting dialog boxes.
Now, repeat the same process to map the CustID key of the Customers table to the CustID field of the Orders table. Once you are finished, you should find the class dialog box shown in Figure 22-12 (note that I enabled the display of the table relationships by right-clicking the designer and selecting Show Relationship Labels).
Figure 22-12. The interconnected Orders, Inventory, and Customers tables
With this, the AutoLot database is complete! While it is a far cry from a real-world corporate database, it will most certainly serve our purposes over the remainder of this book. Now that we have a database to test with, let’s dive into the details of the ADO.NET data provider factory model.
The ADO.NET Data Provider Factory Model
The .NET data provider factory pattern allows us to build a single code base using generalized data access types. Furthermore, using application configuration files (and the <connectionStrings> subelement), we are able to obtain providers and connection strings declaratively without the need to recompile or redeploy the assembly.
To understand the data provider factory implementation, recall from Table 22-1 that the objects within a data provider each derive from the same base classes defined within the System.Data. Common namespace:

750CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
•DbCommand: Abstract base class for all command objects
•DbConnection: Abstract base class for all connection objects
•DbDataAdapter: Abstract base class for all data adapter objects
•DbDataReader: Abstract base class for all data reader objects
•DbParameter: Abstract base class for all parameter objects
•DbTransaction: Abstract base class for all transaction objects
In addition, each of the Microsoft-supplied data providers contains a class type deriving from System.Data.Common.DbProviderFactory. This base class defines a number of methods that retrieve provider-specific data objects. Here is a snapshot of the relevant members of DbProviderFactory:
public abstract class DbProviderFactory
{
...
public virtual DbCommand CreateCommand();
public virtual DbCommandBuilder CreateCommandBuilder(); public virtual DbConnection CreateConnection();
public virtual DbConnectionStringBuilder CreateConnectionStringBuilder(); public virtual DbDataAdapter CreateDataAdapter();
public virtual DbDataSourceEnumerator CreateDataSourceEnumerator(); public virtual DbParameter CreateParameter();
}
To obtain the DbProviderFactory-derived type for your data provider, the System.Data.Common namespace provides a class type named DbProviderFactories (note the plural in this type’s name). Using the static GetFactory() method, you are able to obtain the specific DbProviderFactory object of the specified data provider, for example:
static void Main(string[] args)
{
// Get the factory for the SQL data provider.
DbProviderFactory sqlFactory = DbProviderFactories.GetFactory("System.Data.SqlClient");
...
// Get the factory for the Oracle data provider.
DbProviderFactory oracleFactory = DbProviderFactories.GetFactory("System.Data.OracleClient");
...
}
Of course, rather than obtaining a factory using a hard-coded string literal, you could read in this information from a client-side *.config file (much like the previous MyConnectionFactory example). You will do so in just a bit. However, in any case, once you have obtained the factory for your data provider, you are able to obtain the associated provider-specific data objects (connections, commands, data readers, etc.).
Registered Data Provider Factories
Before you build a full example of working with ADO.NET data provider factories, it is important to note that the DbProviderFactories type is able to fetch factories for only a subset of all possible data providers. The list of valid provider factories is recorded within the <DbProviderFactories> element within the machine.config file for your .NET 3.5 installation (note that the value of the invariant attribute is identical to the value passed into the DbProviderFactories.GetFactory() method):

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
751 |
<system.data>
<DbProviderFactories>
<add name="Odbc Data Provider" invariant="System.Data.Odbc" description=".Net Framework Data Provider for Odbc" type="System.Data.Odbc.OdbcFactory,
System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add name="OleDb Data Provider" invariant="System.Data.OleDb" description=".Net Framework Data Provider for OleDb" type="System.Data.OleDb.OleDbFactory,
System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add name="OracleClient Data Provider" invariant="System.Data.OracleClient" description=".Net Framework Data Provider for Oracle" type="System.Data.OracleClient.OracleClientFactory, System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add name="SqlClient Data Provider" invariant="System.Data.SqlClient" description=".Net Framework Data Provider for SqlServer" type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</DbProviderFactories>
</system.data>
If you wish to leverage a similar data provider factory pattern for DMBSs not accounted for in the machine.config file, it is technically possible to add new invariant values that point to shared assemblies in the GAC. However, you must ensure that the data provider is ADO.NET 2.0 compliant and works with the data provider factory model.
A Complete Data Provider Factory Example
For a complete example, let’s create a new C# Console Application (named DataProviderFactory) that prints out the automobile inventory of the AutoLot database. For this initial example, we will hard-code the data access logic directly within the DataProviderFactory.exe assembly (just to keep things simple for the time being). However, once we begin to dig into the details of the ADO.NET programming model, we will isolate our data logic to a specific .NET code library that will be used throughout the remainder of this text.
First, add a reference to the System.Configuration.dll assembly and import the System. Configuration namespace. Next, insert an App.config file to the current project and define an empty <appSettings> element. Add a new key named provider that maps to the namespace name of the data provider you wish to obtain (System.Data.SqlClient). As well, define a connection string that represents a connection to the AutoLot database:
<?xml version="1.0" encoding="utf-8" ?> <configuration>
<appSettings>
<!-- Which provider? -->
<add key="provider" value="System.Data.SqlClient" />
<!-- Which connection string? -->
<add key="cnStr" value=
"Data Source=(local)\SQLEXPRESS;Initial Catalog=AutoLot;Integrated Security=True"