
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
732 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
Perhaps the most fundamental difference between classic ADO and ADO.NET is that ADO.NET is a managed library of code, therefore it plays by the same rules as any managed library. The types that make up ADO.NET use the CLR memory management protocol, adhere to the same type system (classes, interfaces, enums, structures, and delegates), and can be accessed by any .NET language.
From a programmatic point of view, the bulk of ADO.NET is represented by a core assembly named System.Data.dll. Within this binary, you will find a good number of namespaces (see Figure 22-1), many of which represent the types of a particular ADO.NET data provider (defined shortly).
Figure 22-1. System.Data.dll is the core ADO.NET assembly.
As it turns out, most Visual Studio 2008 project templates automatically reference this key data access library. However, you will need to update your code files to import the namespaces you wish to use; for example:
using System;
// Bring in some ADO.NET namespaces! using System.Data;
using System.Data.SqlClient;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
}
}
}
Do understand that there are other ADO.NET-centric assemblies beyond System.Data.dll (such as System.Data.OracleClient.dll) that you may need to manually reference in your current project using the Add Reference dialog box.
■Note With the release of .NET 3.5, ADO.NET has received many additional assemblies/namespaces that facilitate ADO.NET/LINQ integration. Chapter 24 will examine LINQ-centric aspects of ADO.NET.



CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
735 |
Now, to be sure, a data provider will supply you with other types beyond the objects shown in Figure 22-2. However, these core objects define a common baseline across all data providers.
The Microsoft-Supplied ADO.NET Data Providers
Microsoft’s .NET distribution ships with numerous data providers, including a provider for Oracle, SQL Server, and OLE DB/ODBC-style connectivity. Table 22-2 documents the namespace and containing assembly for each Microsoft ADO.NET data provider.
Table 22-2. Microsoft ADO.NET Data Providers
Data Provider |
Namespace |
Assembly |
OLE DB |
System.Data.OleDb |
System.Data.dll |
Microsoft SQL Server |
System.Data.SqlClient |
System.Data.dll |
Microsoft SQL Server Mobile |
System.Data.SqlServerCe |
System.Data.SqlServerCe.dll |
ODBC |
System.Data.Odbc |
System.Data.dll |
Oracle |
System.Data.OracleClient |
System.Data.OracleClient.dll |
|
|
|
■Note There is no specific data provider that maps directly to the Jet engine (and therefore Microsoft Access). If you wish to interact with an Access data file, you can do so using the OLE DB or ODBC data provider.
The OLE DB data provider, which is composed of the types defined in the System.Data.OleDb namespace, allows you to access data located in any data store that supports the classic COM-based OLE DB protocol. Using this provider, you may communicate with any OLE DB–compliant database simply by tweaking the Provider segment of your connection string.
Be aware, however, that the OLE DB provider interacts with various COM objects behind the scenes, which can affect the performance of your application. By and large, the OLE DB data provider is only useful if you are interacting with a DBMS that does not define a specific .NET data provider. However, given the fact that these days any DBMS worth its salt should have a custom ADO.NET data provider for download, System.Data.OleDb should be considered a legacy namespace that has little use in the .NET 3.5 world (this is even more the case with the advent of the data provider factory model introduced under .NET 2.0).
■Note There is one case in which using the types of System.Data.OleDb is necessary; specifically if you need to communicate with Microsoft SQL Server version 6.5 or earlier. The System.Data.SqlClient namespace can only communicate with Microsoft SQL Server version 7.0 or higher.
The Microsoft SQL Server data provider offers direct access to Microsoft SQL Server data stores, and only SQL Server data stores (version 7.0 and greater). The System.Data.SqlClient namespace contains the types used by the SQL Server provider and offers the same basic functionality as the OLE DB provider. The key difference is that the SQL Server provider bypasses the OLE DB layer and thus gives numerous performance benefits. As well, the Microsoft SQL Server data provider allows you to gain access to the unique features of this particular DBMS.
The remaining Microsoft-supplied providers (System.Data.OracleClient, System.Data.Odbc, and System.Data.SqlClientCe) provide access to Oracle databases, interactivity with ODBC connections, and the SQL Server Mobile edition DBMS (commonly used by handheld devices, such as a

736 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
Pocket PC). The ODBC types defined within the System.Data.Odbc namespace are typically only useful if you need to communicate with a given DBMS for which there is no custom .NET data provider (in that ODBC is a widespread model that provides access to a number of data stores).
Obtaining Third-Party ADO.NET Data Providers
In addition to the data providers that ship from Microsoft, numerous third-party data providers exist for various open source and commercial databases. While you will most likely be able to obtain an ADO.NET data provider directly from the database vendor, you should be aware of the following site (please note that this URL is subject to change): http://www.sqlsummit.com/DataProv.htm.
This website is one of many sites that documents each known ADO.NET data provider and provides links for more information and downloads. Here you will find numerous ADO.NET providers, including SQLite, DB2, MySQL, PostgreSQL, and Sybase (among others).
Given the large number of ADO.NET data providers, the examples in this chapter will make use of the Microsoft SQL Server data provider (System.Data.SqlClient.dll). Recall that this provider allows you to communicate with Microsoft SQL Server version 7.0 and higher, including SQL Server 2005 Express Edition. If you intend to use ADO.NET to interact with another DBMS, you should have no problem doing so once you understand the material presented in the pages that follow.
Additional ADO.NET Namespaces
In addition to the .NET namespaces that define the types of a specific data provider, the .NET base class libraries provide a number of additional ADO.NET-centric namespaces, some of which are shown in Table 22-3.
Table 22-3. Select Additional ADO.NET-Centric Namespaces
Namespace |
Meaning in Life |
Microsoft.SqlServer.Server |
This namespace provides types that facilitate CLR and SQL Server |
|
2005 integration services. |
System.Data |
This namespace defines the core ADO.NET types used by all data |
|
providers, including common interfaces and numerous types that |
|
represent the disconnected layer (DataSet, DataTable, etc.). |
System.Data.Common |
This namespace contains types shared between all ADO.NET data |
|
providers, including the common abstract base classes. |
System.Data.Sql |
This namespace contains types that allow you to discover |
|
Microsoft SQL Server instances installed on the current local |
|
network. |
System.Data.SqlTypes |
This namespace contains native data types used by Microsoft SQL |
|
Server. Although you are always free to use the corresponding CLR |
|
data types, the SqlTypes are optimized to work with SQL Server. |
|
|
Do understand that this chapter will not examine each and every type within each and every ADO.NET namespace (that task would require a large book in and of itself). However, it is quite important for you to understand the types within the System.Data namespace.

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
737 |
The Types of the System.Data Namespace
Of all the ADO.NET namespaces, System.Data is the lowest common denominator. You simply cannot build ADO.NET applications without specifying this namespace in your data access applications. This namespace contains types that are shared among all ADO.NET data providers, regardless of the underlying data store. In addition to a number of database-centric exceptions (NoNullAllowedException, RowNotInTableException, MissingPrimaryKeyException, and the like),
System.Data contains types that represent various database primitives (tables, rows, columns, constraints, etc.), as well as the common interfaces implemented by data provider objects. Table 22-4 lists some of the core types to be aware of.
Table 22-4. Core Members of the System.Data Namespace
Type |
Meaning in Life |
Constraint |
Represents a constraint for a given DataColumn object |
DataColumn |
Represents a single column within a DataTable object |
DataRelation |
Represents a parent/child relationship between two DataTable objects |
DataRow |
Represents a single row within a DataTable object |
DataSet |
Represents an in-memory cache of data consisting of any number of |
|
interrelated DataTable objects |
DataTable |
Represents a tabular block of in-memory data |
DataTableReader |
Allows you to treat a DataTable as a fire-hose cursor (forward only, read-only |
|
data access) |
DataView |
Represents a customized view of a DataTable for sorting, filtering, searching, |
|
editing, and navigation |
IDataAdapter |
Defines the core behavior of a data adapter object |
IDataParameter |
Defines the core behavior of a parameter object |
IDataReader |
Defines the core behavior of a data reader object |
IDbCommand |
Defines the core behavior of a command object |
IDbDataAdapter |
Extends IDataAdapter to provide additional functionality of a data adapter |
|
object |
IDbTransaction |
Defines the core behavior of a transaction object |
|
|
A vast majority of the classes within System.Data are used when programming against the disconnected layer of ADO.NET. In the next chapter, you will get to know the details of the DataSet and its related cohorts (DataTable, DataRelation, DataRow, etc.) and how to use them (and a related data adapter) to represent and manipulate client side copies of remote data.
However, your next task is to examine the core interfaces of System.Data at a high level, to better understand the common functionality offered by any data provider. You will learn specific details throughout this chapter, so for the time being let’s simply focus on the overall behavior of each interface type.
The Role of the IDbConnection Interface
First up is the IDbConnection type, which is implemented by a data provider’s connection object. This interface defines a set of members used to configure a connection to a specific data store, and it also allows you to obtain the data provider’s transaction object. Here is the formal definition of
IDbConnection:

738CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
public interface IDbConnection : IDisposable
{
string ConnectionString { get; set; } int ConnectionTimeout { get; } string Database { get; } ConnectionState State { get; } IDbTransaction BeginTransaction();
IDbTransaction BeginTransaction(IsolationLevel il); void ChangeDatabase(string databaseName);
void Close();
IDbCommand CreateCommand(); void Open();
}
■Note Like many other types in the .NET base class libraries, the Close() method is functionally equivalent to calling the Dispose() method directly or indirectly within a C# using scope (see Chapter 8).
The Role of the IDbTransaction Interface
As you can see, the overloaded BeginTransaction() method defined by IDbConnection provides access to the provider’s transaction object. Using the members defined by IDbTransaction, you are able to programmatically interact with a transactional session and the underlying data store:
public interface IDbTransaction : IDisposable
{
IDbConnection Connection { get; } IsolationLevel IsolationLevel { get; } void Commit();
void Rollback();
}
The Role of the IDbCommand Interface
Next, we have the IDbCommand interface, which will be implemented by a data provider’s command object. Like other data access object models, command objects allow programmatic manipulation of SQL statements, stored procedures, and parameterized queries. In addition, command objects provide access to the data provider’s data reader type via the overloaded ExecuteReader() method:
public interface IDbCommand : IDisposable
{
string CommandText { get; set; } int CommandTimeout { get; set; }
CommandType CommandType { get; set; } IDbConnection Connection { get; set; } IDataParameterCollection Parameters { get; } IDbTransaction Transaction { get; set; } UpdateRowSource UpdatedRowSource { get; set; } void Cancel();
IDbDataParameter CreateParameter(); int ExecuteNonQuery();
IDataReader ExecuteReader();
IDataReader ExecuteReader(CommandBehavior behavior);

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
739 |
object ExecuteScalar(); void Prepare();
}
The Role of the IDbDataParameter and IDataParameter
Interfaces
Notice that the Parameters property of IDbCommand returns a strongly typed collection that implements IDataParameterCollection. This interface provides access to a set of IDbDataParameter- compliant class types (e.g., parameter objects):
public interface IDbDataParameter : IDataParameter
{
byte Precision { get; set; } byte Scale { get; set; } int Size { get; set; }
}
IDbDataParameter extends the IDataParameter interface to obtain the following additional behaviors:
public interface IDataParameter
{
DbType DbType { get; set; } ParameterDirection Direction { get; set; } bool IsNullable { get; }
string ParameterName { get; set; } string SourceColumn { get; set; }
DataRowVersion SourceVersion { get; set; } object Value { get; set; }
}
As you will see, the functionality of the IDbDataParameter and IDataParameter interfaces allows you to represent parameters within a SQL command (including stored procedures) via specific ADO.NET parameter objects rather than hard-coded string literals.
The Role of the IDbDataAdapter and IDataAdapter Interfaces
Data adapters are used to push and pull DataSets to and from a given data store. Given this, the IDbDataAdapter interface defines a set of properties that are used to maintain the SQL statements for the related select, insert, update, and delete operations:
public interface IDbDataAdapter : IDataAdapter
{
IDbCommand DeleteCommand { get; set; } IDbCommand InsertCommand { get; set; } IDbCommand SelectCommand { get; set; } IDbCommand UpdateCommand { get; set; }
}
In addition to these four properties, an ADO.NET data adapter also picks up the behavior defined in the base interface, IDataAdapter. This interface defines the key function of a data adapter type: the ability to transfer DataSets between the caller and underlying data store using the Fill() and Update() methods. As well, the IDataAdapter interface allows you to map database column names to more user-friendly display names via the TableMappings property:

740CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
public interface IDataAdapter
{
MissingMappingAction MissingMappingAction { get; set; } MissingSchemaAction MissingSchemaAction { get; set; } ITableMappingCollection TableMappings { get; }
int Fill(System.Data.DataSet dataSet);
DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType); IDataParameter[] GetFillParameters();
int Update(DataSet dataSet);
}
The Role of the IDataReader and IDataRecord Interfaces
The next key interface to be aware of is IDataReader, which represents the common behaviors supported by a given data reader object. When you obtain an IDataReader-compatible type from an ADO.NET data provider, you are able to iterate over the result set in a forward-only, read-only manner.
public interface IDataReader : IDisposable, IDataRecord
{
int Depth { get; } bool IsClosed { get; }
int RecordsAffected { get; } void Close();
DataTable GetSchemaTable(); bool NextResult();
bool Read();
}
Finally, as you can see, IDataReader extends IDataRecord, which defines a good number of members that allow you to extract a strongly typed value from the stream, rather than casting the generic System.Object retrieved from the data reader’s overloaded indexer method. Here is a partial listing of the various GetXXX() methods defined by IDataRecord (see the .NET Framework 3.5 SDK documentation for a complete listing):
public interface IDataRecord
{
int FieldCount { get; }
object this[ string name ] { get; } object this[ int i ] { get; }
bool GetBoolean(int i); byte GetByte(int i); char GetChar(int i);
DateTime GetDateTime(int i); Decimal GetDecimal(int i); float GetFloat(int i); short GetInt16(int i);
int GetInt32(int i); long GetInt64(int i);
...
bool IsDBNull(int i);
}

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
741 |
■Note The IDataReader.IsDBNull() method can be used to programmatically discover if a specified field is set to null before obtaining a value from the data reader (to avoid triggering a runtime exception). Also recall that C# supports nullable data types (see Chapter 4), which are ideal for interacting with data columns that could be empty.
Abstracting Data Providers Using Interfaces
At this point, you should have a better idea of the common functionality found among all .NET data providers. Recall that even though the exact names of the implementing types will differ among data providers, you are able to program against these types in a similar manner—that’s the beauty of interface-based polymorphism. For example, if you define a method that takes an IDbConnection parameter, you can pass in any ADO.NET connection object:
public static void OpenConnection(IDbConnection cn)
{
// Open the incoming connection for the caller. cn.Open();
}
■Note Interfaces are not strictly required; the same end result could be achieved using abstract base classes (such as DbConnection) as parameters or return values.
The same holds true for member return values. For example, consider the following simple C# Console Application project (named MyConnectionFactory), which allows you to obtain a specific connection object based on the value of a custom enumeration. For diagnostic purposes, we will simply print out the underlying connection object via reflection services:
//Need these to get definitions of common interfaces,
//and various connection objects for our test.
using System; using System.Data;
using System.Data.SqlClient; using System.Data.Odbc; using System.Data.OleDb;
// Need to reference System.Data.OracleClient.dll to nab this namespace! using System.Data.OracleClient;
namespace MyConnectionFactory
{
// A list of possible providers. enum DataProvider
{ SqlServer, OleDb, Odbc, Oracle, None }
class Program
{
static void Main(string[] args)
{
Console.WriteLine("**** Very Simple Connection Factory *****\n");