
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
752 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
/>
</appSettings>
</configuration>
■Note We will examine connection strings in more detail in just a bit. However, be aware that if you select your AutoLot database icon within the Server Explorer, you can copy and paste the correct connection string from the Connection String property of the Visual Studio 2008 Properties Window.
Now that you have a proper *.config file, you can read in the provider and cnStr values using the ConfigurationManager.AppSettings() method. The provider value will be passed to
DbProviderFactories.GetFactory() to obtain the data provider–specific factory type. The cnStr value will be used to set the ConnectionString property of the DbConnection-derived type. Assuming you have imported the System.Data and System.Data.Common namespaces, update your Main() method as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Data Provider Factories *****\n");
//Get Connection string/provider from *.config. string dp =
ConfigurationManager.AppSettings["provider"]; string cnStr =
ConfigurationManager.AppSettings["cnStr"];
//Get the factory provider.
DbProviderFactory df = DbProviderFactories.GetFactory(dp);
//Now make connection object.
DbConnection cn = df.CreateConnection();
Console.WriteLine("Your connection object is a: {0}", cn.GetType().FullName); cn.ConnectionString = cnStr;
cn.Open();
//Make command object.
DbCommand cmd = df.CreateCommand();
Console.WriteLine("Your command object is a: {0}", cmd.GetType().FullName); cmd.Connection = cn;
cmd.CommandText = "Select * From Inventory";
//Print out data with data reader.
//Because we specified CommandBehavior.CloseConnection, we
//don't need to explicitly call Close() on the connection.
DbDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
Console.WriteLine("Your data reader object is a: {0}", dr.GetType().FullName);
Console.WriteLine("\n***** Current Inventory *****");
while (dr.Read())
Console.WriteLine("-> Car #{0} is a {1}.", dr["CarID"], dr["Make"].ToString().Trim());
dr.Close();
Console.ReadLine();
}


754 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
Of course, based on your experience with ADO.NET, you may be a bit unsure exactly what the connection, command, and data reader objects are actually doing. Don’t sweat the details for the time being (quite a few pages remain in this chapter, after all!). At this point, just understand that with the ADO.NET data provider factory model, it is possible to build a single code base that can consume various data providers in a declarative manner.
A Potential Drawback with the Provide Factory Model
Although this is a very powerful model, you must make sure that the code base does indeed make use only of types and methods that are common to all providers via the members of the abstract base classes. Therefore, when authoring your code base, you will be limited to the members exposed by DbConnection, DbCommand, and the other types of the System.Data.Common namespace.
Given this, you may find that this “generalized” approach will prevent you from directly accessing some of the bells and whistles of a particular DBMS. If you must be able to invoke specific members of the underlying provider (SqlConnection for example), you can do so via an explicit cast. When doing so, however, your code base will become a bit harder to maintain (and less flexible) given that you must add a number of runtime checks.
The <connectionStrings> Element
Currently our connection string data is within the <appSettings> element of our *.config file. Application configuration files may define an element named <connectionStrings>. Within this element, you are able to define any number of name/value pairs that can be programmatically read into memory using the ConfigurationManager.ConnectionStrings indexer. One advantage of this approach (rather than using the <appSettings> element and the ConfigurationManager.AppSettings indexer) is that you can define multiple connection strings for a single application in a consistent manner.
To illustrate, update your current App.config file as follows (note that each connection string is documented using the name and connectionString attributes rather than the key and value attributes as found in <appSettings>):
<configuration>
<appSettings>
<!-- Which provider? -->
<add key="provider" value="System.Data.SqlClient" /> </appSettings>
<!-- Here are the connection strings -->
<connectionStrings>
<add name ="AutoLotSqlProvider" connectionString = "Data Source=(local)\SQLEXPRESS;
Integrated Security=SSPI;Initial Catalog=AutoLot"/> <add name ="AutoLotOleDbProvider" connectionString = "Provider=SQLOLEDB;Data Source=(local)\SQLEXPRESS; Integrated Security=SSPI;Initial Catalog=AutoLot"/> </connectionStrings>
</configuration>
With this, you can now update your Main() method as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Data Provider Factories *****\n");
string dp = ConfigurationManager.AppSettings["provider"];

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
755 |
string cnStr = ConfigurationManager.ConnectionStrings["AutoLotSqlProvider"].ConnectionString;
...
}
At this point, you have an application that is able to display the results of the Inventory table of the AutoLot database using a neutral code base. As you have seen, by offloading the provider name and connection string to an external *.config file, the data provider factory model will dynamically load the correct provider in the background. With this first example behind us, we can now dive into the details of working with the connected layer of ADO.NET.
■Note Now that you understand the role of ADO.NET data provider factories, the remaining examples in this book will make explicit use of the types within the System.Data.SqlClient namespace just to keep focused on the task at hand. If you are using a different database management system (such as Oracle), you will need to update your code base accordingly.
■Source Code The DataProviderFactory project is included under the Chapter 22 subdirectory.
Understanding the Connected Layer of ADO.NET
Recall that the connected layer of ADO.NET allows you to interact with a database using the connection, command, and data reader objects of your data provider. Although you have already made use of these objects in the previous DataProviderFactory application, let’s walk through the process once again in detail using an expanded example. When you wish to connect to a database and read the records using a data reader object, you need to perform the following steps:
1.Allocate, configure, and open your connection object.
2.Allocate and configure a command object, specifying the connection object as a constructor argument or via the Connection property.
3.Call ExecuteReader() on the configured command object.
4.Process each record using the Read() method of the data reader.
To get the ball rolling, create a brand-new Console Application named AutoLotDataReader and import the System.Data and System.Data.SqlClient namespaces. The goal is to open a connection (via the SqlConnection object) and submit a SQL query (via the SqlCommand object) to obtain all records within the Inventory table. At this point, you will use a SqlDataReader to print out the results using the type indexer. Here is the complete code within Main(), with analysis to follow:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Data Readers *****\n");
// Create an open a connection.
SqlConnection cn = new SqlConnection(); cn.ConnectionString =
@"Data Source=(local)\SQLEXPRESS;Integrated Security=SSPI;" +

756 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
"Initial Catalog=AutoLot"; cn.Open();
// Create a SQL command object.
string strSQL = "Select * From Inventory"; SqlCommand myCommand = new SqlCommand(strSQL, cn);
//Obtain a data reader a la ExecuteReader().
SqlDataReader myDataReader;
myDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
//Loop over the results.
while (myDataReader.Read())
{
Console.WriteLine("-> Make: {0}, PetName: {1}, Color: {2}.", myDataReader["Make"].ToString().Trim(), myDataReader["PetName"].ToString().Trim(), myDataReader["Color"].ToString().Trim());
}
//Because we specified CommandBehavior.CloseConnection, we
//don't need to explicitly call Close() on the connection. myDataReader.Close();
Console.ReadLine();
}
}
Working with Connection Objects
The first step to take when working with a data provider is to establish a session with the data source using the connection object (which, as you recall, derives from DbConnection). .NET connection objects are provided with a formatted connection string, which contains a number of name/value pairs separated by semicolons. This information is used to identify the name of the machine you wish to connect to, required security settings, the name of the database on that machine, and other data provider–specific information.
As you can infer from the preceding code, the Initial Catalog name refers to the database you are attempting to establish a session with. The Data Source name identifies the name of the machine that maintains the database. Here, (local) allows you to define a single token to specify the current local machine (regardless of the literal name of said machine) while the \SQLEXPRESS token informs the SQL server provider you are connecting to the default SQL Server Express edition installation (if you created AutoLot on a full version of SQL Server 2005 or earlier, simply specify
Data Source=(local)).
Beyond this you are able to supply any number of tokens that represent security credentials. Here, we are setting the Integrated Security to SSPI (which is the equivalent to true), which uses the current Windows account credentials for user authentication.
■Note Look up the ConnectionString property of your data provider’s connection object in the .NET Framework 3.5 SDK documentation to learn about each name/value pair for your specific DBMS. As you will quickly notice, a single segment (such as Integrated Security) can be set to multiple, redundant values (e.g., SSPI, true, and yes behave identically for the Integrated Security value). Furthermore, you may find multiple terms for the same task (for example, Initial Catalog and Database are interchangeable).

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
757 |
Once your construction string has been established, a call to Open() establishes your connection with the DBMS. In addition to the ConnectionString, Open(), and Close() members, a connection object provides a number of members that let you configure additional settings regarding your connection, such as timeout settings and transactional information. Table 22-5 lists some (but not all) members of the DbConnection base class.
Table 22-5. Members of the DbConnection Type
Member |
Meaning in Life |
BeginTransaction() |
This method is used to begin a database transaction. |
ChangeDatabase() |
This method changes the database on an open connection. |
ConnectionTimeout |
This read-only property returns the amount of time to wait while |
|
establishing a connection before terminating and generating an error |
|
(the default value is 15 seconds). If you wish to change the default, specify |
|
a “Connect Timeout” segment in the connection string (e.g., Connect |
|
Timeout=30). |
Database |
This property gets the name of the database maintained by the connection |
|
object. |
DataSource |
This property gets the location of the database maintained by the |
|
connection object. |
GetSchema() |
This method returns a DataSet that contains schema information from the |
|
data source. |
State |
This property sets the current state of the connection, represented by the |
|
ConnectionState enumeration. |
|
|
As you can see, the properties of the DbConnection type are typically read-only in nature and are only useful when you wish to obtain the characteristics of a connection at runtime. When you wish to override default settings, you must alter the construction string itself. For example, the connection string sets the connection timeout setting from 15 seconds to 30 seconds:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Data Readers *****\n");
SqlConnection cn = new SqlConnection(); cn.ConnectionString =
@"Data Source=(local)\SQLEXPRESS;" +
"Integrated Security=SSPI;Initial Catalog=AutoLot;Connect Timeout=30"; cn.Open();
// New helper function (see below).
ShowConnectionStatus(cn);
...
}
In the preceding code, notice you have now passed your connection object as a parameter to a new static helper method in the Program class named ShowConnectionStatus(), implemented as follows (be sure to import the System.Data.Common namespace to get the definition of DbConnection):
static void ShowConnectionStatus(DbConnection cn)
{
// Show various stats about current connection object.
Console.WriteLine("***** Info about your connection *****");
Console.WriteLine("Database location: {0}", cn.DataSource);

758 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
Console.WriteLine("Database name: {0}", cn.Database); Console.WriteLine("Timeout: {0}", cn.ConnectionTimeout); Console.WriteLine("Connection state: {0}\n", cn.State.ToString());
}
While most of these properties are self-explanatory, the State property is worth special mention. Although this property may be assigned any value of the ConnectionState enumeration:
public enum ConnectionState
{
Broken, Closed, Connecting, Executing, Fetching, Open
}
the only valid ConnectionState values are ConnectionState.Open and ConnectionState.Closed (the remaining members of this enum are reserved for future use). Also, understand that it is always safe to close a connection whose connection state is currently ConnectionState.Closed.
Working with ConnectionStringBuilder Objects
Working with connection strings programmatically can be a bit cumbersome, given that they are often represented as string literals, which are difficult to maintain and error-prone at best. The Microsoft-supplied ADO.NET data providers support connection string builder objects, which allow you to establish the name/value pairs using strongly typed properties. Consider the following update to the current Main() method:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Data Readers *****\n");
// Create a connection string via the builder object.
SqlConnectionStringBuilder cnStrBuilder = new SqlConnectionStringBuilder();
cnStrBuilder.InitialCatalog = "AutoLot"; cnStrBuilder.DataSource = @"(local)\SQLEXPRESS"; cnStrBuilder.ConnectTimeout = 30; cnStrBuilder.IntegratedSecurity = true;
SqlConnection cn = new SqlConnection(); cn.ConnectionString = cnStrBuilder.ConnectionString; cn.Open();
ShowConnectionStatus(cn);
...
}
In this iteration, you create an instance of SqlConnectionStringBuilder, set the properties accordingly, and obtain the internal string via the ConnectionString property. Also note that you make use of the default constructor of the type. If you so choose, you can also create an instance of your data provider’s connection string builder object by passing in an existing connection string as a starting point (which can be helpful when you are reading these values dynamically from an App.config file). Once you have hydrated the object with the initial string data, you can change specific name/value pairs using the related properties, for example:

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
759 |
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Data Readers *****\n");
// Assume you really obtained the cnStr value from a *.config file. string cnStr = @"Data Source=(local)\SQLEXPRESS;" +
"Integrated Security=SSPI;Initial Catalog=AutoLot";
SqlConnectionStringBuilder cnStrBuilder = new SqlConnectionStringBuilder(cnStr);
// Change timeout value. cnStrBuilder.ConnectTimeout = 5;
...
}
Working with Command Objects
Now that you better understand the role of the connection object, the next order of business is to check out how to submit SQL queries to the database in question. The SqlCommand type (which derives from DbCommand) is an OO representation of a SQL query, table name, or stored procedure. The type of command is specified using the CommandType property, which may take any value from the CommandType enum:
public enum CommandType
{
StoredProcedure,
TableDirect,
Text // Default value.
}
When creating a command object, you may establish the SQL query as a constructor parameter or directly via the CommandText property. Also when you are creating a command object, you need to specify the connection to be used. Again, you may do so as a constructor parameter or via the Connection property:
static void Main(string[] args)
{
...
SqlConnection cn = new SqlConnection();
...
//Create command object via ctor args. string strSQL = "Select * From Inventory";
SqlCommand myCommand = new SqlCommand(strSQL, cn);
//Create another command object via properties.
SqlCommand testCommand = new SqlCommand(); testCommand.Connection = cn; testCommand.CommandText = strSQL;
...
}
Realize that at this point, you have not literally submitted the SQL query to the AutoLot database, but rather prepared the state of the command object for future use. Table 22-6 highlights some additional members of the DbCommand type.

760 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
Table 22-6. Members of the DbCommand Type
Member |
Meaning in Life |
CommandTimeout |
Gets or sets the time to wait while executing the command before |
|
terminating the attempt and generating an error. The default is 30 seconds. |
Connection |
Gets or sets the DbConnection used by this instance of the DbCommand. |
Parameters |
Gets the collection of DbParameter types used for a parameterized query. |
Cancel() |
Cancels the execution of a command. |
ExecuteReader() |
Returns the data provider’s DbDataReader object, which provides forward- |
|
only, read-only access to the underlying data. |
ExecuteNonQuery() |
Issues the command text to the data store where no results are expected or |
|
desired. |
ExecuteScalar() |
A lightweight version of the ExecuteNonQuery() method, designed |
|
specifically for singleton queries (such as obtaining a record count). |
ExecuteXmlReader() |
Microsoft SQL Server (2000 and higher) is capable of returning result sets as |
|
XML. As you might suspect, this method returns a System.Xml.XmlReader |
|
that allows you to process the incoming stream of XML. |
Prepare() |
Creates a prepared (or compiled) version of the command on the data |
|
source. As you may know, a prepared query executes slightly faster and is |
|
useful when you wish to execute the same query multiple times. |
|
|
■Note As illustrated later in this chapter, the SqlCommand object defines additional members that facilitate asynchronous database interactions.
Working with Data Readers
Once you have established the active connection and SQL command, the next step is to submit the query to the data source. As you might guess, you have a number of ways to do so. The DbDataReader type (which implements IDataReader) is the simplest and fastest way to obtain information from a data store. Recall that data readers represent a read-only, forward-only stream of data returned one record at a time. Given this, it should stand to reason that data readers are useful only when submitting SQL selection statements to the underlying data store.
Data readers are useful when you need to iterate over large amounts of data very quickly and have no need to maintain an in-memory representation. For example, if you request 20,000 records from a table to store in a text file, it would be rather memory-intensive to hold this information in a DataSet. A better approach is to create a data reader that spins over each record as rapidly as possible. Be aware, however, that data reader objects (unlike data adapter objects, which you’ll examine later) maintain an open connection to their data source until you explicitly close the session.
Data reader objects are obtained from the command object via a call to ExecuteReader(). When invoking this method, you may optionally instruct the reader to automatically close down the related connection object by specifying CommandBehavior.CloseConnection.

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
761 |
The following use of the data reader leverages the Read() method to determine when you have reached the end of your records (via a false return value). For each incoming record, you are making use of the type indexer to print out the make, pet name, and color of each automobile. Also note that you call Close() as soon as you are finished processing the records, to free up the connection object:
static void Main(string[] args)
{
...
//Obtain a data reader via ExecuteReader().
SqlDataReader myDataReader;
myDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
//Loop over the results.
while (myDataReader.Read())
{
Console.WriteLine("-> Make: {0}, PetName: {1}, Color: {2}.", myDataReader["Make"].ToString().Trim(), myDataReader["PetName"].ToString().Trim(), myDataReader["Color"].ToString().Trim());
}
myDataReader.Close();
Console.ReadLine();
}
■Note The trimming of the string data shown here is only used to remove trailing blank spaces in the database entries; it is not directly related to ADO.NET! Whether this is necessary or not depends on the column definitions and the data placed in the table and isn’t always required.
The indexer of a data reader object has been overloaded to take either a string (representing the name of the column) or an int (representing the column’s ordinal position). Thus, you could clean up the current reader logic (and avoid hard-coded string names) with the following update (note the use of the FieldCount property):
while (myDataReader.Read())
{
Console.WriteLine("***** Record *****");
for (int i = 0; i < myDataReader.FieldCount; i++)
{
Console.WriteLine("{0} = {1} ", myDataReader.GetName(i), myDataReader.GetValue(i).ToString().Trim());
}
Console.WriteLine();
}
If you compile and run your project, you should be presented with a list of all automobiles in the Inventory table of the AutoLot database (see Figure 22-15).