Data Access

In recent years, Microsoft has promoted an alphabet soup of database access technologies. First was ODBC. Then came DAO, RDO, ADO, and OLE DB, to name just a few. The .NET Framework has its own database API called ADO.NET. The bad news is that despite its name, ADO.NET has little in common with ADO. The good news is that learning the basics of ADO.NET requires all of about 15 minutes.

The classes that make up ADO.NET are found in the System.Data namespace and its descendants. Some ADO.NET classes, such as DataSet, are generic and work with virtually any kind of database. Others, such as DataReader, come in two distinctly different flavors: one for Microsoft SQL Server databases (SqlDataReader) and one for all others (OleDbDataReader). Sql classes belong to the System.Data.SqlClient namespace. They use a managed provider (that is, a database access layer that consists solely of managed code) that鈥檚 optimized to work with Microsoft SQL Server databases. Significantly, Sql classes work only with SQL Server. OleDb classes, on the other hand, can be used with any database for which an OLE DB provider that is compatible with the .NET Framework is available. They tend to be somewhat slower than Sql classes because they鈥檙e not optimized for any particular database and because they rely on a combination of managed and unmanaged code, but they鈥檙e also more generic, enabling you to switch databases without having to rewrite your application. OleDb classes are defined in the System.Data.OleDb namespace.

ADO.NET is covered in detail in Chapter 12. The next several sections of this chapter offer an introductory look at ADO.NET, which will help you understand some of the data-aware sample programs presented in intervening chapters. For readers accustomed to working with traditional database APIs, the sections that follow also provide an educational first look at data access in the era of .NET.

DataReaders

One of the most common tasks that data-aware applications are asked to perform involves executing a query and outputting the results. For managed applications, the DataReader class is the perfect tool for the job. DataReader objects expose the results of database queries as fast, forward-only, read-only streams of data. DataReaders come in two flavors: SqlDataReader for SQL Server databases and OleDbDataReader for other types of databases. The following example uses SqlDataReader to query the Pubs database that comes with Microsoft SQL Server for all the records in the 鈥淭itles鈥?table. It then writes the 鈥淭itle鈥?field of each record to a console window:

SqlConnection聽connection聽=
聽聽聽聽new聽SqlConnection聽("server=localhost;uid=sa;pwd=;database=pubs");
connection.Open聽();
SqlCommand聽command聽=
聽聽聽聽new聽SqlCommand聽("select聽*聽from聽titles",聽connection);
SqlDataReader聽reader聽=聽command.ExecuteReader聽();
while聽(reader.Read聽())
聽聽聽聽Console.WriteLine聽(reader.GetString聽(1));
connection.Close聽();

The SqlConnection object represents the database connection. Open opens a connection, and Close closes it. SqlCommand encapsulates the query used to extract records from the database. Calling ExecuteReader on the SqlCommand object executes the command and returns a SqlDataReader object. Reading the records returned by the query is as simple as calling SqlDataReader.Read repeatedly until it returns null.

I purposely didn鈥檛 include exception handling code in this sample to keep the code as simple and uncluttered as possible. In the real world, you鈥檒l want to use try/catch/finally to recover gracefully from errors and to ensure that the database connection is closed even in the face of inopportune exceptions:

SqlConnection聽connection聽=
聽聽聽聽new聽SqlConnection聽("server=localhost;uid=sa;pwd=;database=pubs");

try聽{
聽聽聽聽connection.Open聽();
聽聽聽聽SqlCommand聽command聽=
聽聽聽聽聽聽聽聽new聽SqlCommand聽("select聽*聽from聽titles",聽connection);
聽聽聽聽SqlDataReader聽reader聽=聽command.ExecuteReader聽();
聽聽聽聽while聽(reader.Read聽())
聽聽聽聽聽聽聽聽Console.WriteLine聽(reader.GetString聽(1));
}
catch聽(SqlException聽e)聽{
聽聽聽聽Console.WriteLine聽(e.Message);
}
finally聽{
聽聽聽聽connection.Close聽();
}

Tailoring this code to work with databases other than Microsoft SQL Server is a simple matter of changing the Sql classes to OleDb classes and modifying the connection string accordingly.

Inserts, Updates, and Deletes

A Command object鈥檚 ExecuteReader method executes a query and returns a DataReader encapsulating the results. The complementary ExecuteNonQuery method performs inserts, updates, and deletes. The following code uses a SQL INSERT command to add a record to SQL Server鈥檚 Pubs database:

SqlConnection聽connection聽=
聽聽聽聽new聽SqlConnection聽("server=localhost;uid=sa;pwd=;database=pubs");

try聽{
聽聽聽聽connection.Open聽();
聽聽聽聽string聽sqlcmd聽=
聽聽聽聽聽聽聽 "insert聽into聽titles聽(title_id,聽title,聽type,聽pub_id, " +
聽聽聽聽聽聽聽 "price,聽advance,聽royalty,聽ytd_sales,聽notes,聽pubdate) " +
聽聽聽聽聽聽聽 "values聽('BU1001',聽'Programming聽Microsoft.NET', " +
聽聽聽聽聽聽聽 "'Business',聽'1389',聽NULL,聽NULL,聽NULL,聽NULL, " +
聽聽聽聽聽聽聽 "'Learn聽to聽program聽Microsoft.NET',聽'Jan聽01聽2002')";
聽聽聽聽SqlCommand聽command聽=聽new聽SqlCommand聽(sqlcmd,聽connection);
聽聽聽聽command.ExecuteNonQuery聽();
}
catch聽(SqlException聽e)聽{
聽聽聽聽Console.WriteLine聽(e.Message);
}
finally聽{
聽聽聽聽connection.Close聽();
}

To update or delete a record (or set of records), you simply replace the INSERT command with an UPDATE or DELETE command. Of course, there are other ways to add, modify, and remove records. The full range of options is discussed in Chapter 12.

DataSets and DataAdapters

DataSet, which belongs to the System.Data namespace, is the centerpiece of ADO.NET. A DataSet is an in-memory database capable of holding multiple tables and even of modeling constraints and relationships. Used in combination with SqlDataAdapter and OleDbDataAdapter, DataSet can handle virtually all the needs of modern-day data access applications and is frequently used in lieu of DataReader to facilitate random read/write access to back-end databases.

The following code fragment uses SqlDataAdapter and DataSet to query a database and display the results. It鈥檚 functionally equivalent to the SqlData颅Reader example presented earlier:

SqlDataAdapter聽adapter聽=聽new聽SqlDataAdapter聽(
聽聽聽 "select聽*聽from聽titles",
聽聽聽 "server=localhost;uid=sa;pwd=;database=pubs"
);
DataSet聽ds聽=聽new聽DataSet聽();
adapter.Fill聽(ds);
foreach聽(DataRow聽row聽in聽ds.Tables[0].Rows)
聽聽聽聽Console.WriteLine聽(row[1]);

SqlDataAdapter serves as a liaison between DataSet objects and physical data sources. In this example it executes a query, but it鈥檚 capable of performing inserts, updates, and deletes, too. For details, see鈥攜ou guessed it鈥擟hapter 12.