Chapter 6: Data Manipulation with ADO.NET

by Steve Walther

In this chapter you will learn the following:

How to open a database connection and perform common database operations such as inserting, updating, and deleting database records.

How to use a DataReader to represent records retrieved from a database table.

How to use parameters and execute stored procedures from an ASP.NET page.

How to create a memory resident database using DataSets and DataTables.

atabase access is a crucial component of almost any ASP.NET application. Fortunately, the .NET framework contains a rich set of classes and controls for working with database data in your ASP.NET pages.

We'll approach the subject of database access by dividing the topic into two chapters. In this chapter, you'll be given an overview of ADO.NET, the data access technology built into the .NET framework. You'll learn how to use the ADO.NET classes to perform standard database tasks such as modifying and accessing database data. We'll also cover some of the more advanced features of ADO.NET, such as stored procedure support and filtering and sorting data.

In the next chapter, "Data Presentation," you'll learn how to use ADO.NET with ASP.NET controls. You'll learn how to use ADO.NET to bind database data to standard Web Controls, such as the DropDownList and RadioButtonList controls. You'll also learn how to display and edit database data using the more specialized data controls such as the Repeater, DataList, and DataGrid controls.

1. Overview of ADO.NET

Let's begin with a quick tour of ADO.NET. The .NET Framework contains several namespaces with dozens of classes devoted to database access. However, for the purposes of explaining ADO.NET in this chapter, I'll make a rough division of these classes into three groups.

The first group consists of the following three classes:

If you plan to build your ASP.NET application with Microsoft SQL Server (version 7.0 or greater), these are the classes you'll use most often. These classes enable you to execute SQL statements and quickly retrieve data from a database query.

The SqlConnection class represents an open connection to a Microsoft SQL Server database. The SqlCommand class represents a SQL statement or stored procedure. Finally, the SqlDataReader class represents the results from a database query. We'll go into the details of using each of these classes in the next section of this chapter.


Note

If you have used the ActiveX Data Objects (ADO), these three classes should be very familiar. The SqlConnection and SqlCommand classes are similar to the ADO Connection and Command objects, with the important exception that they work only with Microsoft SQL Server.

The SqlDataReader class is similar to an ADO Recordset object opened with a fast, forward-only cursor. However, unlike a Recordset object, the SqlDataReader class does not support alternative cursor types and it only works with Microsoft SQL Server.


These classes work only with Microsoft SQL Server. If you need to work with another type of database, such as an Access or Oracle database, you will need to use the following classes:

Notice that these classes have the same names as the ones in the previous group, except these classes start with OleDb rather than Sql.

Why did Microsoft duplicate these classes, creating one version specifically for SQL Server and one version for non–SQL Server databases? By creating two sets of classes, Microsoft was able to optimize the performance of the first set of classes specifically for SQL Server.

The OleDb classes use OLEDB providers to connect to a database. The Sql classes, on the other hand, communicate with Microsoft SQL Server directly on the level of the Tabular Data Stream (TDS) protocol. TDS is the low-level proprietary protocol used by SQL Server to handle client and server communication. By bypassing OLEDB and ODBC and working directly with TDS, you get dramatic performance benefits.

There's one last group of classes that we'll work with in this chapter. This group contains the following classes:

You can use the classes in this group to build a memory-resident representation of a database (an in-memory database). A DataSet represents the in-memory database itself. Once you create a DataSet, you can populate it with one or more DataTables that represent database tables. You create the DataTables with the help of either the SqlDataAdapter or OleDbDataAdapter class. You can then define various relationships between the tables with the DataRelation class and create filtered or sorted views on the DataTables with DataViews.

Why would you want to build an in-memory database? In certain situations, it is useful to have all the data from a database table available to your ASP.NET application in such a way that it is disconnected from the underlying database. For example, using the classes from this group, you can cache one or more database tables in your server's memory and use the same data in multiple ASP.NET pages. You'll see some other applications of these classes in the final section of this chapter.


Note

If you are an experienced ADO developer, it might be helpful to think of a DataTable as a disconnected, client-side, static Recordset.


2. Using ADO.NET to Accomplish Common Database Tasks

In this section, you'll learn how to use ADO.NET classes to perform common database tasks, such as retrieving database data, inserting new data, updating data, and deleting data. We'll focus on the ADO.NET classes for performing these tasks in this chapter and worry about constructing a friendly user interface for performing these tasks in the following chapter.

Whenever you need to work with database data in an ASP.NET page, you should import the proper namespaces so you can easily access the ADO.NET classes in your page. There are three namespaces that you'll use:

If you are accessing a SQL Server database (version 7.0 or greater), you'll need to include these two Import statements:

<%@ Import Namespace="System.Data" %>
<%@ Import NameSpace="System.Data.SqlClient" %>

If you are accessing a database other than Microsoft SQL Server 7.0 or greater (for example, Microsoft Access, Oracle, Microsoft SQL Server 6.5 or just about any data source that has an OLE DB Provider), you would use the following two Import statements:

<%@ Import Namespace="System.Data" %>
<%@ Import NameSpace="System.Data.OleDb" %>

Note

You can use the classes from the System.Data.OleDb namespace with Microsoft SQL Server. You might want to do this if you want your ASP.NET page to be compatible with any database. For example, you might want your page to work with both Microsoft SQL Server and Oracle. However, you will lose all the speed advantages of the SQL-specific classes if you use the System.Data.OleDb namespace.


Opening a Database Connection

The first thing you'll need to do in order to access a database is create and open a database connection. Once again, you'll create the connection in different ways, depending on the type of database that you want to access.

Listing 6.2.1 shows how you would create and open a connection for a Microsoft SQL Server database (this page is included on the CD as SQLConnection.aspx).

Listing 6.2.1 Opening a Connection to SQL Server

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3: <%
 4: Dim myConnection As SqlConnection
 5: myConnection = New SqlConnection( "server=localhost;database=Pubs;uid=sa" )
 6:
 7: myConnection.Open()
 8: %>
 9: Connection Opened!

The first two lines in Listing 6.2.1 import the necessary namespaces for working with SQL Server. Next, in lines 4 and 5, an instance of the SqlConnection class is created named myConnection. The myConnection class is initialized by passing a connection string as a parameter to the constructor for the SqlConnection class. Finally, the connection is actually opened by calling the Open() method of the SqlConnection class.

The connection string contains all the necessary location and authentication information to connect to SQL Server. In the code in Listing 6.2.1, the connection string contains the name of the server, the name of the database, and the SQL Server login and password.


Note

Notice that you do not specify a provider parameter for the connection string when using the SqlConnection class. The classes in the System.Data.SqlClient namespace do not use an OLEDB provider, ADO, ODBC, or any other intermediate interface to SQL Server. The classes work directly with the TDS (Tabular Data Stream) protocol.

Furthermore, you cannot use a data source name (DSN) when opening a connection with the SqlConnection class. If you really want to use a DSN with SQL Server, you must use the classes in the System.Data.OleDb namespace instead.


You would use similar code to create a connection to a Microsoft Access database. In Listing 6.2.2, a database connection is created and opened for a Microsoft Access database named Authors (this page is included on the CD as OleDbConnection.aspx).

Listing 6.2.2 Opening a Connection to Microsoft Access

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.OleDb" %>
 3: <%
 4: Dim myConnection As OleDbConnection
 5: myConnection = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA
 Source=c:\authors.mdb" )
 6:
 7: myConnection.Open()
 8: %>
 9: Connection Opened!

Because we are creating a connection for Microsoft Access, we must import the System.Data.OleDb namespace rather than the System.Data.SqlClient namespace. Next, we must create an instance of the OleDbConnection class and initialize it with a connection string appropriate for Microsoft Access. Finally, calling the Open() method of the OleDbConnection class actually opens the database connection.

In the code contained in Listing 6.2.2, we pass the name of the OLEDB provider for Microsoft Access (Microsoft.Jet.OLEDB.4.0) and the path to the Access database on the server. If you wanted to connect to another type of database, you would need to specify a different provider. For example, to connect to an Oracle database, you would use the MSDAORA provider. (This provider is automatically installed as part of the Microsoft Data Access Components.)


Note

If you prefer, you can use a data source name (DSN) with the OleDbConnection class to open a database connection. For example, after you create a System DSN named myDSN, you can connect using this:

myConnection = New OleDbConnection( "DSN=myDSN" )

Realize, however, that opening a connection in this way forces you to use the OLEDB for ODBC provider rather than the native OLEDB provider for your database. Typically, but not always, this will result in slower performance.


By default, when you call the Open() method with either the SqlConnection or the OleDbConnection classes, the connection is given 15 seconds to open before timing out. You can override this default behavior by setting the ConnectionTimeout property before you open the database connection. For example, to allow up to 90 seconds to open a connection, you would use a statement like the following:

MyConnection.ConnectionTimeout = 90

Inserting Data Using ADO.NET

Now that you know how to create and open a database connection, we can use this connection to add new data to a database table. We'll add the new data with the SQL INSERT command. Here's the syntax for a basic INSERT command:

INSERT tablename ( column1, column2... ) VALUES ( value1, value2... )

You insert new records into a table by listing the table columns and listing the values that you want to insert into the columns. For example, imagine that you have a table named Authors that has both a FirstName and a LastName column. The following statement inserts a new author named Ralph Ellison:

INSERT Authors ( FirstName, LastName ) VALUES ( 'Ralph', 'Ellison' )

Three steps are required for executing a SQL INSERT command in an ASP.NET page:

  1. Create and open a database connection.

  2. Create a database command that represents the SQL INSERT statement to execute.

  3. Execute the command.

The ASP.NET page in Listing 6.2.3 uses the classes from the System.Data.SqlClient namespace to insert a new record into a SQL Server database table (this page is included on the CD as SQLINSERTcommand.aspx).

Listing 6.2.3 Executing an INSERT Command

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7:
 8: myConnection = New SqlConnection( "server=localhost;uid=sa;
pwd=secret;database=myDataPubs" ) 9: myConnection.Open() 10: myCommand = New SqlCommand( "Insert testTable ( col1 ) Values ( 'Hello' )", myConnection ) 11: myCommand.ExecuteNonQuery() 12: myConnection.Close() 13: %> 14: New Record Inserted! 15:

The first two statements in Listing 6.2.3 are used to import the necessary namespaces. Next, in lines 8 and 9, a connection to a SQL database is created. An instance of the SQLConnection class is initialized with a connection string for the myData database located on the local server. Next, the SqlConnection is opened with the Open() method.

In the statement that follows, an instance of the SqlCommand class is created. The SqlCommand class is initialized with two parameters: the command to execute and the connection to use for executing the command.

In line 10, a string containing an INSERT statement is passed to the instance of the SqlCommand class. The INSERT statement inserts a new record into a table named testTable with a single column named col1.

Finally, the command is executed by calling the ExecuteNonQuery() method of the SqlCommand class. The ExecuteNonQuery() method sends the SQL command to the database server. The method is called the ExecuteNonQuery() method because it is used to execute non-row-returning SQL commands.

The code in Listing 6.2.3 works only with Microsoft SQL Server (version 7.0 or greater). To work with other databases, you would need to modify the code to use the classes from the System.Data.OleDb namespace rather than the System.Data.SqlClient namespace. The ASP.NET page in Listing 6.2.4 demonstrates how you would add a new record to a Microsoft Access database table (this page is included on the CD as OleDbINSERTcommand.aspx).

Listing 6.2.4 Adding a Record to Access

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.OleDb" %>
 3:
 4: <%
 5: Dim myConnection As OleDbConnection
 6: Dim myCommand As OleDbCommand
 7:
 8: myConnection = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA 
 Source=c:authors.mdb" )
 9: myConnection.Open()
10: myCommand = New OleDbCommand( "Insert INTO Authors ( Author ) Values
 ( 'Simpson' )", myConnection )
11: myCommand.ExecuteNonQuery()
12: myConnection.Close()
13: %>
14: New Record Inserted!
15:

The code in Listing 6.2.4 executes an INSERT statement that adds a new record to a Microsoft Access table named authors.mdb. Notice that Microsoft Access requires you to use the keyword INTO with the INSERT statement (you use INSERT INTO Authors rather than INSERT Authors).


Note

The apostrophe character (') can cause problems when you're inserting data into a database table. For example, imagine that you want to add a new author named O'Leary to the Authors table. You might try to execute the following statement:

INSERT INTO Authors ( Author ) Values ( 'O'Leary' )

This statement will generate an error because the apostrophe in O'Leary will be interpreted as marking the end of the SQL string. To get around this problem, you'll need to double up your apostrophes. For example, use the following statement to add O'Leary to a database table:

INSERT INTO Authors ( Author ) Values ( 'O''Leary' )

Updating Data Using ADO.NET

To update existing records in a database table, you use the SQL UPDATE command. Here's the syntax for the basic UPDATE command:

UPDATE tablename SET column1 = value1, column2 = value2...
WHERE search condition

You update a table by setting certain columns to certain values where a certain search condition is true. For example, imagine that you have a database table named Authors that has a column named LastName. The following statement sets the value of the LastName column to 'Smith' wherever the column has a value of 'Bennet':

UPDATE Authors SET LastName = 'Smith'
WHERE LastName = 'Bennet'

You execute an UPDATE command within an ASP.NET page by completing the following steps:

  1. Create and open a database connection.

  2. Create a database command that represents the SQL UPDATE statement to execute.

  3. Execute the command.

For example, the ASP.NET page contained in Listing 6.2.5 updates a record in a SQL Server database (this page is included on the CD as SQLUPDATEcommand.aspx).

Listing 6.2.5 Updating a SQL Database

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7:
 8: myConnection = New SqlConnection( "server=localhost;uid=sa;
pwd=secret;database=myDataPubs" ) 9: myConnection.Open() 10: myCommand = New SqlCommand( "UPDATE Authors SET LastName='Smith' WHERE LastName='Bennett'", myConnection ) 11: myCommand.ExecuteNonQuery() 12: myConnection.Close() 13: %> 14: Record Updated! 15:

The first two statements in Listing 6.2.5 import the necessary namespaces for working with the SQL ADO.NET classes. Next, in lines 8 and 9, a database connection is created and opened for the local server. In the statement that follows, an instance of the SqlCommand class is created by passing a SQL command and SqlConnection to the constructor for the class.

The SQL UPDATE command is executed when the ExecuteNonQuery() method of the SqlCommand class is called. At this point, the UPDATE statement is transmitted to SQL Server and executed.

The code in Listing 6.2.5 will only work with Microsoft SQL Server (version 7.0 and greater). If you want to update a record in another type of database, you will need to use the ADO.NET classes from the System.Data.OleDb namespace rather than the System.Data.SqlClient namespace.

The ASP.NET page contained in Listing 6.2.6 modifies a record in a Microsoft Access database (the page is included on the CD as OleDbUPDATEcommand.aspx).

Listing 6.2.6 Updating an Access Database

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.OleDb" %>
 3:
 4: <%
 5: Dim myConnection As OleDbConnection
 6: Dim myCommand As OleDbCommand
 7:
 8: myConnection = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA
 Source=c:\authors.mdb" )
 9: myConnection.Open()
10: myCommand = New OleDbCommand( "UPDATE Authors SET Author='Bennett' 
 WHERE Author = 'Simpson'", myConnection )
11: myCommand.ExecuteNonQuery()
12: myConnection.Close
13: %>
14: Record Updated!
15:

One difference between the SQL UPDATE command and the SQL INSERT command is that the SQL UPDATE command might affect more than one record at a time. When you execute an UPDATE command, the command changes every record that satisfies the UPDATE command's WHERE clause.

You can determine the number of records affected by an UPDATE command within an ASP.NET page by grabbing the value returned by the ExecuteNonQuery() method. The page contained in Listing 6.2.7 illustrates this method (this page is included on the CD as SQLUPDATERecordsAffected.aspx).

Listing 6.2.7 Records Affected by the UPDATE Command

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7: Dim recordsAffected As Integer
 8:
 9: myConnection = New SqlConnection( "server=localhost;uid=sa;
pwd=secret;database=myDataPubs" ) 10: myConnection.Open() 11: myCommand = New SqlCommand( "UPDATE testTable SET col1='hello' WHERE col1='fred'", myConnection ) 12: recordsAffected = myCommand.ExecuteNonQuery() 13: Response.Write( "The UPDATE statement modified " & recordsAffected.toString() & " records!" ) 14: myConnection.Close 15: %> 16:

Deleting Data Using ADO.NET

You can delete data from a database by using the SQL DELETE command. The syntax for a basic DELETE command takes the following form:

DELETE tablename WHERE search condition

For example, if you want to delete all the rows from a table named Authors in which the LastName column has the value 'Bennet', use the following statement:

DELETE Authors WHERE LastName = 'Bennet'

To execute a DELETE command from within an ASP.NET page, you must complete the following steps:

  1. Create and open a database connection.

  2. Create a database command that represents the SQL DELETE statement to execute.

  3. Execute the command.

For example, the ASP.NET page in Listing 6.2.8 demonstrates how you can delete a record from a SQL Server database table (this page is included on the CD as SQLDELETEcommand.aspx).

Listing 6.2.8 Deleting a Record from SQL Server

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7:
 8: myConnection = New SqlConnection( "server=localhost;uid=sa;
pwd=secret;database=myDataPubs" ) 9: myConnection.Open() 10: myCommand = New SqlCommand( "DELETE testTable WHERE col1='fred'", myConnection ) 11: myCommand.ExecuteNonQuery() 12: myConnection.Close() 13: %> 14: Record Deleted! 15:

The first two lines in Listing 6.2.8 are used to import the necessary namespaces to work with SQL Server. Next, a connection to the SQL Server running on the local machine is opened.

In line 10, the SqlCommand class is initialized with two parameters: a SQL DELETE command and the SqlConnection class. Next, the command is executed, and the connection is closed.

If you need to work with a database other than Microsoft SQL Server, you would use similar code. However, you must use the classes from the System.Data.OleDb namespace rather than the classes from System.Data.SqlClient.

The page contained in Listing 6.2.9 illustrates how you would delete a record from a Microsoft Access database table named Authors (this page is included on the CD as OleDbDELETEcommand.aspx).

Listing 6.2.9 Deleting a Record from an Access Database

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.OleDb" %>
 3:
 4: <%
 5: Dim myConnection As OleDbConnection
 6: Dim myCommand As OleDbCommand
 7:
 8: myConnection = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA
 Source=c:\authors.mdb" )
 9: myConnection.Open()
10: myCommand = New OleDbCommand( "DELETE FROM Authors 
WHERE Author = 'Simpson'", myConnection )
11: myCommand.ExecuteNonQuery()
12: myConnection.Close()
13: %>
14: Record Deleted!
15:
16:

Notice that you must use DELETE FROM rather than just DELETE when working with a Microsoft Access database.

A SQL DELETE command is similar to a SQL UPDATE command in that it might affect an unknown number of records. A SQL DELETE command deletes all the records that match the condition specified by the command's WHERE clause.

If you need to determine the number of records affected by a DELETE command, you can grab the value returned by the ExecuteNonQuery() method. The page contained in Listing 6.2.10 illustrates how you would do this (this page is included on the CD as SQLDELETERecordsAffected.aspx).

Listing 6.2.10 Records Affected by DELETE

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SQLConnection
 6: Dim myCommand As SQLCommand
 7: Dim recordsAffected As Integer
 8:
 9: myConnection = New SqlConnection( "server=localhost;uid=sa;
pwd=secret;database=myDataPubs" ) 10: myConnection.Open() 11: myCommand = New SqlCommand( "DELETE Authors2 WHERE LastName='Smith'", myConnection ) 12: recordsAffected = myCommand.ExecuteNonQuery() 13: Response.Write( "The DELETE statement modified " & recordsAffected.toString() & " records!" ) 14: myConnection.Close 15: %> 16: 17:

Querying Data Using ADO.NET

The SQL command that you will use most often in your ASP.NET pages is the SELECT command. The SELECT command enables you to retrieve records from a database table that match a certain condition. Here's the syntax for a basic SELECT command:

SELECT column1, column2...
FROM tablename1, tablname2...
WHERE search condition

For example, if you wanted to retrieve the FirstName and LastName columns from the Authors table, where the LastName column has the value 'Smith', you would use the following SELECT command:

SELECT FirstName, LastName
FROM Authors
WHERE LastName = 'Smith'

If you simply want to retrieve all the columns and all the rows from the Authors table, use the following SELECT statement:

SELECT * FROM Authors

The asterisk (*) is a wildcard character that represents all the columns. Without a WHERE clause, all the rows from the Authors table are automatically returned.


Note

For performance reasons, you should avoid using the * wildcard character in the SELECT statement.


Four steps are involved in executing a SELECT command in an ASP.NET page:

  1. Create and open a database connection.

  2. Create a database command that represents the SQL SELECT statement to execute.

  3. Execute the command returning a DataReader.

  4. Loop through the DataReader, displaying the results of the query.

When you execute a query using ADO.NET, the results of the query are returned in a DataReader. More accurately, the results of a query are represented by either the SqlDataReader or the OleDbDataReader, depending on the database from which you are retrieving the records.

A DataReader represents a forward-only stream of database records. This means that the DataReader only represents a single record at a time. To fetch the next record in the stream, you must call the Read() method. To display all the records returned from a query, you must call the Read() method again and again until you read the end of the stream.


Note

If you have used earlier versions of ADO, it may be helpful to think of a DataReader as a Recordset opened with a forward-only cursor.


For example, the ASP.NET page in Listing 6.2.11 displays all the records from a SQL Server database table named Authors;. (This page is included on the CD as SQLDataReader.aspx.)

Listing 6.2.11 Using the SQLDataReader

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7: Dim myDataReader As SqlDataReader
 8:
 9: myConnection = New SqlConnection( "server=localhost;uid=sa;
pwd=secret;database=Pubs" ) 10: myConnection.Open() 11: myCommand = New SqlCommand( "Select * from Authors", myConnection ) 12: myDataReader = myCommand.ExecuteReader() 13: While myDataReader.Read() 14: Response.Write( myDataReader.Item( "au_lname" ) ) 15: End While 16: myDataReader.Close() 17: myConnection.Close() 18: %> 19:

The first two lines in Listing 6.2.11 are used to import the necessary namespaces to use the ADO.NET classes for SQL Server. Next, in lines 9 and 10, a connection is created and opened for the database located on the local server named Pubs.

Next, a SqlCommand object is initialized with a SQL string that contains a SQL SELECT command. The SQL SELECT command retrieves all the records from a database table named Authors.

Next, the command is executed by calling the ExecuteReader() method of the SqlCommand class. In this case, we want to call the ExecuteReader() method rather than the ExecuteNonQuery() method because we need to retrieve the results of a query. The ExecuteReader() method returns a SQLDataReader that represents the results of executing the SQL SELECT statement.

Once we have a SQLDataReader, we need to loop through its contents to display all the records returned by the query. In Listing 6.2.11, this is accomplished with a WHILE...END WHILE loop. All the records returned by the SELECT command are displayed with the following block of code:

 While myDataReader.Read
  Response.Write( myDataReader.Item( "au_lname" ) )
 End While

The Read() method of the SqlDataReader class does two things whenever it is called. First, the method returns the value TRUE if there is another record; otherwise, it returns FALSE. Second, the method advances the DataReader to the next record if a next record exists. By combining these functions, the Read() method makes it very easy to quickly loop through the contents of a DataReader.


Caution

Remember to call the Read() method at least once before displaying a record with the DataReader. When a DataReader is first returned, the first record is not retrieved until you call the Read() method.


The value of a returned column is displayed by using the Item property of the DataReader. For example, in the code in Listing 6.2.11, the value of the au_lname column is displayed.


Note

The Item property will automatically convert the value returned to the proper .NET data type. The value of a SQL Varchar column is returned as a String, the value of a SQL Int column is returned as an Int32 value, the value of a SQL Money column is returned as a Decimal, and so on.


The page in Listing 6.2.11 was written using Visual Basic.NET. The page in Listing 6.2.12 does the same thing using the C# language (this page is included on the CD as SqlDataReader.aspx in the C# folder).

Listing 6.2.12 Using the SqlDataReader with C#

 1: <%@ Page Language="C#" %>
 2: <%@ Import Namespace="System.Data" %>
 3: <%@ Import NameSpace="System.Data.SqlClient" %>
 4:
 5: <%
 6: SqlDataReader myDataReader;
 7: SqlConnection myConnection = new 
SqlConnection( "server=localhost;uid=sa;
pwd=secret;database=Pubs" ); 8: myConnection.Open(); 9: SqlCommand myCommand = new SqlCommand( "Select * from Authors", myConnection ); 10: myDataReader = myCommand.ExecuteReader(); 11: while ( myDataReader.Read() ) 12: { 13: Response.Write( myDataReader[ "au_lname" ].ToString() ); 14: } 15: myDataReader.Close(); 16: myConnection.Close(); 17: %> 18:

The ASP.NET pages in Listing 6.2.11 and 6.2.12 will only work with Microsoft SQL Server. To use other databases, you must use the System.Data.OleDb classes rather than the System.Data.SqlClient classes.

The page in Listing 6.2.13 illustrates how you would execute a query against a Microsoft Access database (this page is included on the CD as OleDbDataReader.aspx).

Listing 6.2.13 Using the OleDbDataReader

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.OleDb" %>
 3:
 4: <%
 5: Dim myConnection As OleDbConnection
 6: Dim myCommand As OleDbCommand
 7: Dim myDataReader As OleDbDataReader
 8:
 9: myConnection = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA
 Source=c:\authors.mdb" )
10: myConnection.Open()
11: myCommand = New OleDbCommand( "Select * from Authors", myConnection )
12: myDataReader = myCommand.ExecuteReader()
13: While myDataReader.Read
14:  Response.Write( myDataReader.Item( "author" ) )
15: End While
16: myDataReader.Close()
17: myConnection.Close
18: %>
19:

After you finish displaying the records from a DataReader, it is important that you explicitly close it (using the Close() method). If you neglect to close the DataReader, you might encounter problems when attempting to reuse the conncetion later in the page.

3. Using Parameters with SQL Commands

When building your ASP.NET pages, you'll need to use variables when working with SQL commands. Otherwise, you will insert the very same data every time a page is executed.

For example, if you retrieve data from a form, you'll need to use variables in your INSERT command to insert the values retrieved from the form. So far, we have not discussed how you can use variables with SQL commands.

The simplest way to use variables when working with SQL commands is to build up the command string itself with variables. An example of this can be found in Listing 6.3.1.

Listing 6.3.1 Inserting Data with Variables

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7: Dim sqlString As String
 8:
 9: Dim FirstName As String = "Robert"
10: Dim LastName As String = "Johnson"
11:
12: myConnection = New SQLConnection( "server=localhost;uid=sa;pwd=secret;database=myData" )
13: myConnection.Open()
14: sqlString = "Insert Authors ( FirstName, LastName ) Values 
('" & FirstName & "','" & LastName & "')"
15: myCommand = New SqlCommand( sqlString, myConnection )
16: myCommand.ExecuteNonQuery()
17: myConnection.Close()
18: %>
19: New Record Inserted!
20:

The page in Listing 6.3.1 contains two variables named FirstName and LastName. You could imagine that these variables represent information retrieved from an HTML form. The variables are used to build the contents of another variable called sqlString. The sqlString variable contains a standard SQL INSERT command.

Notice that the sqlString variable is used in line 15 when initializing the SqlCommand class. When the SqlCommand is executed, the SQL command contained in the sqlString variable is executed. When the page is executed, the new author, Robert Johnson, is added to the Authors table.

The page in Listing 6.3.1 will execute without generating an error. However, it has some limitations. You will encounter three types of problems when taking this approach of using variables with SQL commands.

First, building a SQL string with variables in this manner will produce errors when the variables contain apostrophes. For example, if the value of the LastName variable is "O'Leary," SQL Server will generate an error when you attempt to execute the SQL string. SQL Server generates this error because it interprets the apostrophe as marking the end of a SQL string.


Note

One way to get around this problem with the apostrophe character is to write a function that automatically doubles up every apostrophe in a string. SQL Server will automatically interpret two apostrophes in a row as a single apostrophe.


Second, imagine that you have a very large HTML form that has, for example, 50 form fields. Building a SQL string with this number of variables would be a very tedious and error-prone operation. You'll end up with a massive string as the value of the sqlString variable.

Finally, building SQL strings out of variables in this manner makes it difficult to convert your code to use stored procedures (stored procedures are discussed in the next section, "Using Stored Procedures with ADO.NET"). You cannot use this method of building SQL strings when working with stored procedures.

Fortunately, there is a better method for using variables with SQL commands: You can explicitly create parameters for a SQL command to represent variable information.

The ASP.NET page in Listing 6.2.15 illustrates how you can use parameters with the SqlCommand class (this page is included on the CD with the name SQLParameters.aspx).

Listing 6.3.2 Using SQL Parameters

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7: Dim FirstName As String = "Robert"
 8: Dim LastName As String = "Johnson"
 9:
10: myConnection = New SqlConnection( "server=localhost;uid=sa;pwd=secret;database=myData" )
11: myConnection.Open()
12: myCommand = New SQLCommand( "Insert Authors ( FirstName, LastName ) 
Values ( @FirstName, @LastName )", myConnection )
13:
14: myCommand.Parameters.Add( New SqlParameter( "@FirstName", 
SqlDbType.Varchar, 30 ))
15: myCommand.Parameters( "@FirstName" ).Value = FirstName
16:
17: myCommand.Parameters.Add( New SqlParameter( "@LastName", 
SqlDbType.Varchar, 30 ))
18: myCommand.Parameters( "@LastName" ).Value = LastName
19:
20: myCommand.ExecuteNonQuery()
21: myConnection.Close()
22: %>
23: Record Inserted!

In Listing 6.3.2, two parameters are created: @FirstName and @LastName. These parameters are used within the SQL INSERT command as placeholders for the values of the FirstName and LastName string variables. The SQL INSERT statement looks like this:

Insert Authors ( FirstName, LastName ) Values ( @FirstName, @LastName )

Next, the parameters are created and added to the Parameters collection of the SqlCommand class. For example, the @LastName parameter is created and added with the following statement:

myCommand.Parameters.Add( New 
SqlParameter( "@FirstName", SqlDbType.Varchar, 30 ))

The parameter is initialized with the name of the parameter (@FirstName), the data type of the parameter (SqlDbType.Varchar), and the maximum size of the value of the parameter (30 characters).

The name of the parameter must match the name that you used for the parameter placeholder in the SQL string. The name must also start with the @ character.

The data type should be a valid Microsoft SQL Server data type. When using variable-length data types, such as Varchar and VarBinary, you can also list the maximum size of the parameter. This size should match the size of the column in the underlying database table.

The value of the parameter is assigned with the following line of code:

myCommand.Parameters( "@FirstName" ).Value = FirstName

This statement assigns the value of the FirstName variable to the SqlCommand parameter named @FirstName.

The code in Listing 6.3.2 will only work with Microsoft SQL Server because it uses the classes from the System.Data.SqlClient namespace. The page in Listing 6.3.3 uses the classes from the System.Data.OleDb namespace to work with Microsoft Access (this page is included on the CD as OleDbParameters.aspx).

Listing 6.3.3 Using Parameters with Microsoft Access

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.OleDb" %>
 3:
 4: <%
 5: Dim myConnection As OleDbConnection
 6: Dim myCommand As OleDbCommand
 7: Dim FirstName As String = "Robert"
 8: Dim LastName As String = "Johnson"
 9:
10: myConnection = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;
DATA Source=c:\author2.mdb" )
11: myConnection.Open()
12: myCommand = New OleDbCommand( "Insert INTO Authors ( FirstName, LastName )
 Values ( @FirstName, @LastName )", myConnection )
13:
14: myCommand.Parameters.Add( New OleDbParameter( "@FirstName", 
OleDbType.Varchar, 30 ))
15: myCommand.Parameters( "@FirstName" ).Value = FirstName
16:
17: myCommand.Parameters.Add( New OleDbParameter( "@LastName", 
OleDbType.Varchar, 30 ))
18: myCommand.Parameters( "@LastName" ).Value = LastName
19:
20: myCommand.ExecuteNonQuery()
21: myConnection.Close()
22: %>
23: Record Inserted!
24:

Notice that the data types used for initializing the OleDbParameter class are different from those used for the SqlParameter class. When working with the OleDbParameter class, you must use values from the OleDbType enumeration. So, to represent an Access Text column, you must use the value OleDbType.Varchar.


Note

The SqlDbType enumeration can be found in the System.Data namespace, and the OleDbType enumeration can be found in the System.Data.OleDb namespace.


4. Using Stored Procedures with ADO.NET

There are two ways of executing a SQL command from within an ASP.NET page. You can execute the command directly from code in the page, or you can package the SQL command as a stored procedure and execute the stored procedure from the page.

Building stored procedures takes a little more work than executing commands directly on a page. However, you can significantly increase the performance of a database-driven Web site by using stored procedures. A SQL statement must be parsed, compiled, and optimized by SQL Server whenever it is executed from an ASP.NET page. A stored procedure, on the other hand, needs to be parsed, compiled, and optimized only once.

Another advantage of using stored procedures is that they enable you to reuse the same SQL statements in multiple ASP.NET pages. If, at some later date, you need to make changes to your code, you can change it in only one place rather than in every page.

In any case, after you have set up the parameters for a SQL command (as you did in the previous section), executing the command as a stored procedure requires a trivial amount of work. For example, Listing 6.4.1 illustrates how you can convert the code in Listing 6.3.1 to use a stored procedure (this page is included on the CD as SQLStoredProcedure.aspx).

Listing 6.4.1 Using Stored Procedures

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7: Dim FirstName As String = "Robert"
 8: Dim LastName As String = "Johnson"
 9:
10: myConnection = New SqlConnection(
"server=localhost;uid=sa;pwd=secret;database=myData" ) 11: myConnection.Open() 12: myCommand = New SqlCommand( "InsertAuthors", myConnection ) 13: myCommand.CommandType = CommandType.StoredProcedure 14: 15: myCommand.Parameters.Add( New SqlParameter( "@FirstName", SqlDbType.Varchar, 30 )) 16: myCommand.Parameters( "@FirstName" ).Value = FirstName 17: 18: myCommand.Parameters.Add( New SqlParameter( "@LastName", SqlDbType.Varchar, 30 )) 19: myCommand.Parameters( "@LastName" ).Value = LastName 20: 21: myCommand.ExecuteNonQuery() 22: myConnection.Close 23: %> 24: Record Inserted! 25: 26:

When you execute the page contained in Listing 6.4.1, a new record is added to the Authors table with the following stored procedure named InsertAuthors:

create procedure InsertAuthors
(
 @FirstName Varchar( 50 ),
 @LastName Varchar( 50 )
)
AS
Insert Authors ( FirstName, LastName )
 VALUES ( @FirstName, @LastName )

To create this stored procedure in Microsoft SQL Server, launch the SQL Query Analyzer by going to Start, Programs, Microsoft SQL Server, Query Analyzer. Enter the preceding stored procedure and click Execute (the green VCR play button).

This stored procedure accepts two input parameters: @FirstName and @LastName. The input parameters are used in the SQL INSERT command to add a new record to the Authors table.

Two modifications were made to the ASP.NET page in Listing 6.2.17 to enable the page to execute the InsertAuthors stored procedure. First, instead of a SQL statement being passed to the SqlCommand object, the name of the stored procedure (InsertAuthors) is passed instead:

myCommand = New SqlCommand( "InsertAuthors", myConnection )

An additional line of code was also added to Listing 6.2.17. The following statement was added to set the CommandType property of the SQLCommand class to the value StoredProcedure:

myCommand.CommandType = CommandType.StoredProcedure

Using Output Parameters

The code in Listing 6.4.1 demonstrates how you can pass input parameters to a stored procedure. However, stored procedures can also have output parameters and return values. You can use both output parameters and return values to return information from a stored procedure.

Output parameters and return values return different types of data. A return value can only return an integer value. Whenever you execute a stored procedure, the stored procedure has a return value. By default, the return value is 0.

Output parameters can return values of most data types. For example, you can use an output parameter to return a Varchar, Money, or Integer value. However, all versions of SQL Server, including the current version (SQL Server 2000), do not support returning TEXT values as output parameters.

For example, the following stored procedure has an input parameter, an output parameter, and Listing 6.4.1 demonstrates a return value:

create procedure getLastName
(
 @FirstName Varchar( 50 ),
 @LastName Varchar( 50 ) Output
)
As
Select @LastName = LastName
From Authors
WHERE FirstName = @FirstName

IF @LastName is Null
 Return( 0 )
ELSE
 Return( 1 )

The getLastName stored procedure accepts an input parameter named @FirstName and returns an output parameter named @LastName. If an author with the first name exists in the Authors table, a return value of 1 is retrieved. Otherwise, the stored procedure sends back a return value of 0.

The ASP.NET page in Listing 6.4.2 illustrates how you can use the getLastName stored procedure from within an ASP.NET page (this page is included on the CD as SQLInputOutput.aspx).

Listing 6.4.2 Retrieving Output Parameters and Return Values

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:Listing 6.4.1 demonstrates 
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myCommand As SqlCommand
 7: Dim myParam As SqlParameter
 8:
 9: myConnection = New SqlConnection(
"server=localhost;uid=sa; pwd=secret;database=myDataPubs" ) 10: myConnection.Open() 11: 12: myCommand = New SqlCommand( "getLastName", myConnection ) 13: myCommand.CommandType = CommandType.StoredProcedure 14: 15: myParam = myCommand.Parameters.Add( New SqlParameter( "RETURN VALUE", SqlDbType.INT )) 16: myParam.Direction = ParameterDirection.ReturnValue 17: 18: myParam = myCommand.Parameters.Add( New SqlParameter( "@FirstName", SqlDbType.Varchar, 50 )) 19: myParam.Direction = ParameterDirection.Input 20: myParam.Value = "Robert" 21: 22: myParam = myCommand.Parameters.Add( New SqlParameter( "@LastName", SqlDbType.Varchar, 50 )) 23: myParam.Direction = ParameterDirection.Output 24: 25: myCommand.ExecuteNonQuery() 26: If myCommand.Parameters( "RETURN VALUE" ).Value Then 27: Response.Write( "The last name is " & MyCommand.Parameters( "@LastName" ).Value ) 28: Else 29: Response.Write( "No author found!" ) 30: END If 31: myConnection.Close() 32: %> 33:

In Listing 6.4.2, a return value parameter, input parameter, and output parameter are added to the Parameters collection of the SqlCommand class. The direction of the parameter is set with the Direction property of the SqlParameter class. For example, the following two statements are used to create the output parameter that Listing 6.4.1 demonstrates:

myParam = myCommand.Parameters.Add( New 
SqlParameter( "@LastName", SqlDbType.Varchar, 50 ))
myParam.Direction = ParameterDirection.Output

After the command has been executed, the value of the parameters can be retrieved. The value of the output parameter is displayed in the following Response.Write() statement:

Response.Write( "The last name is " & Listing 6.4.1 demonstrates 
MyCommand.Parameters( "@LastName" ).Value )

5. Retrieving Data with DataSets

In previous sections, we examined how to use the ADO.NET classes to access and modify data from a database table. However, ADO.NET includes another group of classes that provides you with an alternative method of working with data. The classes in this group include DataSet, DataTable, and DataView. In this section, you learn how to use these classes in your ASP.NET pages.

Why are there two groups of classes? These two groups of classes represent database data in different ways, and they are appropriate for different types of applications. The first group of classes enables you to work with database data with an open database connection. The second group of classes enables you to work with a disconnected set of database data.

For example, we've looked at how to use the DataReader to represent the results of a database query. The DataReader enables you to quickly retrieve a set of records from a database and display the records in an ASP.NET page.

However, the DataReader has a limitation that's important to note: It can retrieve only a single database record into memory at a time. Because the DataReader fetches only a single record at a time, you cannot use it to work with the results of a query as a whole. For example, you cannot use a DataReader to retrieve a count of the number of records returned or to filter or sort the records. You also cannot cache a DataReader in your server's memory.


Note

It's worth emphasizing that retrieving data with the DataReader is typically much faster than with a DataSet. Under the hood, the Dataset class uses the DataReader class to retrieve data.


Because of these limitations, Microsoft has introduced an alternative group of classes for working with data. These classes enable you to build a memory-resident, disconnected representation of data. Because the data is held in memory, you have more options for manipulating the data in your ASP.NET applications. Because the data is disconnected from the underlying data source, you can easily cache the data.

There are six important classes in this group:

The DataSet class represents a memory-resident database. It is a container for the DataTable and DataRelation classes.

The DataTable class represents a memory-resident database table. You can create a DataTable from an existing database, or you can programmatically build a DataTable from scratch.

You can define relationships between DataTables by using the DataRelation class. Once you define a relationship between tables with the DataRelation class, you can navigate through the records contained in the two tables and return related values.

Finally, you can filter and sort the contents of a DataTable by using the DataView class. The DataView class also includes methods for searching a DataTable.

The SQLDataAdapter and OleDbDataAdapter classes are used for creating a DataTable from an existing database table. The SQLDataAdapter class enables you to build a DataTable from a Microsoft SQL Server database table. The OleDbDataAdapter class enables you to build a DataTable from other types of databases, such as Microsoft Access and Oracle databases.

The ASP.NET page in Listing 6.5.1 illustrates how you can use these classes to build a memory-resident database table and display all the records from the table (this page is included on the CD as SQLDataTable.aspx).

Listing 6.5.1 Using a DataTable

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myDataAdapter As SqlDataAdapter
 7: Dim myDataSet As DataSet
 8: Dim myDataTable As DataTable
 9: Dim myRow As DataRow
10:
11: myConnection = New SqlConnection( 
"server=localhost;uid=sa; pwd=secret;database=Pubs" ) 12: myDataAdapter = New SqlDataAdapter( "Select * From Authors", myConnection ) 13: myDataSet = New DataSet() 14: myDataAdapter.Fill( myDataSet, "Authors" ) 15: 16: For each myRow in myDataSet.Tables( "Authors" ).Rows 17: Response.Write( myRow( "au_lname" ) )DataTable 18: Next 19: 20: %> 21:

The first two lines in Listing 6.5.1 are used to import the necessary namespaces to work with the ADO.NET classes. Next, after some variables are declared, a connection to a Microsoft SQL Server database is created.

The database connection is used when initializing the SqlDataAdapter class. An instance of the SqlDataAdapter class is initialized by passing a SQL SELECT command and the database connection.

Next, the DataSet is initialized. The DataSet will be used to contain the DataTable.

The DataTable itself is created in the next line (line 14). The DataTable is created by calling the Fill() method of the SqlDataAdapter class. When the Fill() method is called, the records from the Authors table are copied from the Microsoft SQL Server database table into the DataSet.


Note

Notice that you never need to explicitly open a database connection when adding DataTables to a DataSet. When you call the Fill() method, a connection is opened automatically to the underlying database.


The final three lines of code are used to loop through the rows of the memory-resident DataTable. The Rows property represents all the rows in the DataTable. The FOR...EACH loop walks through all the rows displaying the value of the au_lname column.

Because the page in Listing 6.5.1 uses the classes from the System.Data.SqlClient namespace, it will not work with other databases such as Microsoft Access and Oracle. The ASP.NET page contained in Listing 6.5.2 demonstrates how you would rewrite the page to work with a Microsoft Access database (this page is included on the CD as OleDbDataTable.aspx).

Listing 6.5.2 Using a DataTable with Microsoft Access

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.OleDb" %>
 3:
 4: <%
 5: Dim myConnection As OleDbConnection
 6: Dim myDataAdapter As OleDbDataAdapter
 7: Dim myDataSet As DataSet
 8: Dim myDataTable As DataTable
 9: Dim myRow As DataRow
10:
11: myConnection = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA
 Source=c:\authors.mdb" )
12: myDataAdapter = New OleDbDataAdapter( "Select * From Authors", myConnection )
13: myDataSet = New DataSet()
14: myDataAdapter.Fill( myDataSet, "Authors" )
15:
16: For each myRow in myDataSet.Tables( "Authors" ).Rows
17:  Response.Write( myRow( "Author" ) )
18: Next
19:
20: %>
21:

6. Understanding DataTables

A DataTable represents a table of data in a DataSet. There are two ways to create a DataTable: by using the Fill() method of the DataAdapter class and by building the DataTable programmatically.

It is important to understand that all the data in a DataTable is copied into your server's memory after you call the Fill() method. This means that if you have a database with two million records, all these records will be placed in your server's memory. Therefore, be cautious about opening large DataTables.

A DataTable has both a Columns property, which represents a collection of columns, and a Rows property, which represents a collection of rows. You can display the value for a particular column in a particular row by using a statement like the following:

Response.Write( myDataSet.Tables( "Authors" ).Rows( 2 ).Item( "au_lname", 
DataRowVersion.Current ) )

This statement displays the value of the au_lname column for the indicated row in the DataTable. The statement displays the current value of the row. Instead of referring to the column by name, you can also use an integer index to indicate the column, like this:

Response.Write( myDataSet.Tables( "Titles" ).Rows( 2 ).Item( 1, 
DataRowVersion.Current ) )

This statement displays the value of the column with index 1 and the row with index 2 (both collections are zero based). In addition, the statement retrieves the current version of the row by passing the value DataRowVersion.Current to the Item property.

Because you can display any column in any row by supplying the proper indexes, you can write an ASP.NET page that automatically displays the contents of a table without knowing anything about the columns in the DataTable beforehand. The ASP.NET page in Listing 6.6.1 illustrates how you can automatically display a table named Authors from the Pubs database table (this page is included on the CD as SQLShowTable.aspx).

Listing 6.6.1 Automatically Displaying a Table

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myDataAdapter As SQLDataAdapter
 7: Dim myDataSet As DataSet
 8: Dim myDataTable As DataTable
 9:
10: Dim RowCount As Integer
11: Dim ColCount As Integer
12: Dim i, k As Integer
13:
14: myConnection = New SqlConnection( 
"server=localhost;uid=sa; pwd=secret;database=Pubs" ) 15: myDataAdapter = New SQLDataAdapter( "Select * From Authors", myConnection ) 16: myDataSet = New DataSet() 17: myDataAdapter.Fill( myDataSet, "Authors" ) 18: 19: RowCount = myDataSet.Tables( "Authors" ).Rows.Count 20: ColCount = myDataSet.Tables( "Authors" ).Columns.Count 21: 22: Response.Write( "<table border=1>" ) 23: For i = 0 To RowCount - 1 24: Response.Write( "<tr>" ) 25: For k = 0 To ColCount - 1 26: Response.WRite( "<td>" ) 27: Response.Write( myDataSet.Tables( "Authors" ).Rows( i ).Item( k, DataRowVersion.Current ).toString() ) 28: Response.Write( "</td>" ) 29: Next 30: Response.WRite( "</tr>" ) 31: Next 32: Response.Write( "</table>" ) 33: %> 34:

In Listing 6.6.1, the Authors database table is automatically displayed (see Figure 6.1). A count of the number of rows and columns in the Authors table is retrieved by using the Count property of the Rows and Columns collections. Next, a FOR...NEXT loop is used to iterate through each of the rows, and a second FOR...NEXT loop is used to display the value of each column in each row. The Authors table is displayed in an HTML table.

Figure 6.1
Automatically displaying a database table.

Building a DataTable Programmatically

Instead of creating a DataTable from existing data in a database, you can make one from thin air. Why would you want to do this? A DataTable can be a convenient container for information. For example, the code in Listing 6.6.2 demonstrates how you can create a DataTable that represents a shopping cart (this page is included on the CD as buildDataTable.aspx).

Listing 6.6.2 Building a DataTable

 1: <%@ Import Namespace="System.Data" %>
 2: <%
 3: Dim myDataTable as DataTable
 4: Dim myColumn as DataColumn
 5: Dim myRow As DataRow
 6: Dim i As Integer
 7: Dim myRand As System.Random
 8: Dim productID As Integer
 9:
10: ' Create a DataTable
11: myDataTable = new DataTable("ShoppingCart")
12: myDataTable.MinimumCapacity = 50
13: myDataTable.CaseSensitive = False
14:
15: ' Add an AutoIncrement (Identity) Column
16: myColumn = myDataTable.Columns.Add("ID", 
System.Type.GetType("System.Int32") )
17: myColumn.AutoIncrement = TRUE
18: myColumn.AllowDBNull = false
19:
20: ' Add an Integer Column
21: myColumn = myDataTable.Columns.Add("UserID", 
System.Type.GetType("System.Int32") )
22: myColumn.AllowDBNull = false
23:
24: ' Add an Integer Column
25: myColumn = myDataTable.Columns.Add("ProductID", 
System.Type.GetType("System.Int32") )
26: myColumn.AllowDBNull = false
27:
28: ' Add a String Column
29: myColumn = myDataTable.Columns.Add( "ProductName",
System.Type.GetType("System.String") )
30: myColumn.AllowDBNull = false
31:
32: ' Add a Decimal Column
33: myColumn = myDataTable.Columns.Add("ProductPrice",
System.Type.GetType("System.Decimal") )
34: myColumn.AllowDBNull = false
35:
36: ' Add Some Data
37: myRand = New Random
38: For i = 0 To 20
39:  productID = myRand.Next( 5 )
40:  myRow = myDataTable.NewRow()
41:  myRow( "UserID" ) = myRand.Next( 3 )
42:  myRow( "ProductID" ) = productID
43:  myRow( "ProductName" ) = "Product " & productID.toString()
44:  myRow( "ProductPrice" ) = 10.25
45:  myDataTable.Rows.Add( myRow )
46: Next
47:
48: ' Display All the Rows
49: For each myRow in myDataTable.Rows
50:  Response.Write( "<hr>" )
51:  For each myColumn in myDataTable.Columns
52:   Response.Write( myRow.Item( myColumn ).toString() & " / " )
53:  Next
54: Next
55: %>
56:
57:

In the first part of Listing 6.6.2, a new DataTable named ShoppingCart is created. The DataTable is created with a minimum capacity of 20 rows by setting the MinimumCapacity property. You're not required to set the minimum capacity, but you can optimize the performance of the DataTable by doing so. By default, the MinimumCapacity property has a value of 25 rows.

The DataTable is created in such a way that string comparisons are case insensitive. This is accomplished by setting the CaseSensitive property to False. Because this is the default value of the CaseSensitive property, we really did not need to set the property here.

Next, five columns are added to the DataTable. The first column added is an autoincrement column. Every time a new row is added to the DataTable, this column is automatically assigned a new integer value. This column is the same as an Identity column in SQL Server.

The other four columns represent information about the items in the shopping cart. The UserID column represents the user with which the item in the shopping cart is associated. The ProductID column represents the unique integer ID for each product in the shopping cart. The ProductName column represents the name of the product in the shopping cart. Finally, the ProductPrice column represents the price of the item.

Next, 21 rows of data are added to the shopping cart. A new row is created by calling the NewRow() method of the DataTable. Each of the columns of the new row are assigned a value. Finally, the new row of data is added back to the DataTable by using the Add() method of the DataTable.

The final section of Listing 6.6.2 simply displays all 21 rows from the DataTable. Each row is displayed by looping through the Rows collection of the DataTable, and the value of each column is displayed by looping through the Columns collection.

Filtering and Sorting Data in a DataTable

There are two ways that you can filter and sort the data in a DataTable. You can either use the Select() method of the DataTable class or use the RowFilter and Sort properties of a DataView.

First, the Select() method of the DataTable class enables you to filter and sort data by returning an array of DataRows. The Select() method returns an array of DataRows that represent the selected rows in a DataTable. For example, the ASP.NET page in Listing 6.6.3 uses the Select() method to return all the rows from the Titles table where the Type column has the value popular_comp (popular computing). The Select() method is also used to order the rows returned by title (in descending order). Finally, the CurrentRows value is passed to the Select() statement so that only the current rows are returned. (The file is included on the CD with the name SQLSelectFilter.aspx.)

Listing 6.6.3 Selecting DataRows

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myDataAdapter As SqlDataAdapter
 7: Dim myDataSet As DataSet
 8: Dim myDataTable As DataTable
 9: Dim myRow As DataRow
10: Dim selectRows() As DataRow
11:
12: myConnection = New SqlConnection( 
"server=localhost;uid=sa;pwd=secret;database=Pubs" ) 13: myDataAdapter = New SqlDataAdapter( "Select * From Titles", myConnection ) 14: myDataSet = New DataSet() 15: myDataAdapter.Fill( myDataSet, "Titles" ) 16: selectRows = myDataSet.Tables( "Titles" ).Select( "type='popular_comp'", "title DESC", DataViewRowState.CurrentRows ) 17: 18: For each myRow in selectRows 19: Response.Write( myRow.Item( "title" ) ) 20: Next 21: %>

When you use the Select() method, an array of DataRows is returned. Instead of filtering and sorting the rows from a DataTable with the Select() method, you can use the RowFilter and Sort properties of a DataView without returning an array. The page in Listing 6.6.4 illustrates how to use the RowFilter and Sort properties (this page is included on the CD as SQLDataViewFilter.aspx).

Listing 6.6.4 DataView Filter

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3: <%
 4: Dim myConnection As SqlConnection
 5: Dim myDataAdapter As SqlDataAdapter
 6: Dim myDataSet As DataSet
 7: Dim myDataTable As DataTable
 8: Dim myDataView As DataView
 9: Dim myRow As DataRowView
10:
11: myConnection = New SqlConnection( 
"server=localhost;uid=sa;pwd=secret;database=Pubs" ) 12: myDataAdapter = New SqlDataAdapter( "Select * From Titles", myConnection ) 13: myDataSet = New DataSet() 14: myDataAdapter.Fill( myDataSet, "Titles" ) 15: myDataView = myDataSet.Tables( "Titles" ).DefaultView 16: myDataView.RowFilter = "type='popular_comp'" 17: myDataView.Sort = "title DESC" 18: 19: For each myRow in myDataView 20: Response.Write( myRow( "title" ) ) 21: Next 22: %> 23:

In Listing 6.6.4, a DataView is returned from the DefaultView property of a DataTable. Next, the RowFilter and Sort properties of the DataView are set. Finally, all the sorted and filtered rows from the DataView are displayed.

Understanding DataRelations

A DataSet can contain multiple DataTables. You can relate the columns in separate DataTables by creating instances of the DataRelation class. All the DataRelations for a particular DataSet are contained in the DataSet's Relations collection.

For example, the most common type of relation between two tables is a master/detail relation. For each row in the master table, one or more rows exist in the detail table. To create a master/ detail relationship between two tables, the tables must share a common key.

The page in Listing 6.6.5 demonstrates how you can create an association between two tables in a DataSet with a DataRelation. The DataRelation is used to associate the Publishers table with the Titles table in a master/detail relationship. The common key shared by both tables is the pub_id column (this page is included on the CD as SQLDataRelation.aspx).

Listing 6.6.5 Creating a Master/Detail Relationship

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myDataAdapter As SqlDataAdapter
 7: Dim myDataSet As DataSet
 8: Dim myDataTable As DataTable
 9: Dim Publisher As DataRow
10: Dim Title As DataRow
11:
12: myConnection = New SqlConnection( "server=localhost;uid=sa;pwd=secret;database=Pubs" )
13: myDataSet = New DataSet()
14: myDataAdapter = New SQLDataAdapter( "Select * From Publishers", 
myConnection )
15: myDataAdapter.Fill( myDataSet, "Publishers" )
16: myDataAdapter.SelectCommand = New SqlCommand( "Select * From Titles", 
myConnection )
17: myDataAdapter.Fill( myDataSet, "Titles" )
18:
19: myDataSet.Relations.Add( "PubTitles", 
myDataSet.Tables( "Publishers" ).Columns( "pub_id" ), 
myDataSet.Tables( "Titles" ).Columns( "pub_id" ) )
20:
21: For Each Publisher in myDataSet.Tables( "Publishers" ).Rows
22:  Response.Write( "<p>" & Publisher( "pub_name" ) & ":" )
23:  For Each Title In Publisher.GetChildRows( "PubTitles" )
24:   Response.Write("<li>" & Title( "title" ) )
25:  Next
26: Next
27:
28: %>
29:

The DataRelation is both created and added to the Relations collection of the DataSet class with the following statement:

myDataSet.Relations.Add( "PubTitles", 
myDataSet.Tables( "Publishers" ).Columns( "pub_id" ), 
myDataSet.Tables( "Titles" ).Columns( "pub_id" ) )

This statement creates a new DataRelation named PubTitles that associates the pub_id column in the Publishers table with the column with the same name in the Titles table.

After the DataRelation is added to the DataSet, related rows from the Publishers and Titles DataTables can be retrieved and displayed. For each row in the Publishers DataTable, the corresponding rows in the Titles DataTable are returned by using the GetChildRows() method:

For Each Title In Publisher.GetChildRows( "PubTitles" )

The GetChildRows() method returns an array of DataRows. The method accepts one parameter: the name of the DataRelation to use when returning the child rows. You can, of course, create multiple DataRelations between one DataTable and another.

7. Understanding the DataAdapter

In previous sections, we've used the Fill() method of the DataAdapter to add new DataTables to a DataSet. However, we really haven't gotten into the details of how the DataAdapter works.

A DataAdapter contains a collection of four instances of the Command class: SelectCommand, InsertCommand, UpdateCommand, and DeleteCommand. When you use the SqlDataAdapter, these four commands are SqlCommand classes. When you use the OleDbDataAdapter, these four commands are OleDbCommand classes.

We've been using the SelectCommand class of the DataAdapter to indicate the rows that we want to retrieve when adding a DataTable. For example, we've been using code like this:

myDataSet = New DataSet()
myDataAdapter = New SqlDataAdapter( "Select * From Publishers", myConnection )
myDataAdapter.Fill( myDataSet, "Publishers" )

This code is actually equivalent to creating an instance of a SelectCommand class for the DataAdapter. For example, we could use the following code instead:

myDataSet = New DataSet()
myDataAdapter = New SqlDataAdapter
myDataAdapter.SelectCommand = New SqlCommand( "Select * From Publishers", 
myConnection )
myDataAdapter.Fill( myDataSet, "Publishers" )

The DataAdapter can be used for operations other than simply retrieving data from a database table. You can also use the DataAdapter to update a database table after the data in a DataTable has been modified.

For example, the code in Listing 6.7.1 demonstrates how you can update data in the Authors database table after the data has been modified in the Authors DataTable (this page is included on the CD as SQLDataAdapterUpdate.aspx).

Listing 6.7.1 Using the DataAdapter Update Method

 1: <%@ Import Namespace="System.Data" %>
 2: <%@ Import NameSpace="System.Data.SqlClient" %>
 3:
 4: <%
 5: Dim myConnection As SqlConnection
 6: Dim myDataAdapter As SqlDataAdapter
 7: Dim myBuilder As SqlCommandBuilder
 8: Dim myDataSet As DataSet
 9: Dim myDataTable As DataTable
10: Dim Author As DataRow
11:
12: ' Create the DataSet and DataAdapter
13: myConnection = New SqlConnection( "server=localhost;uid=sa;pwd=secret;database=Pubs" )
14: myDataSet = New DataSet()
15: myDataAdapter = New SqlDataAdapter( "Select * From Author", myConnection )
16: myDataAdapter.Fill( myDataSet, "Authors" )
17:
18: ' Change value of first row
19: myDataSet.Tables( "Authors" ).Rows( 0 ).Item( "au_fname" ) = "Jane"
20:
21: ' Update the Database Table
22: myBuilder = New SqlCommandBuilder( myDataAdapter )
23: myDataAdapter.Update( myDataSet, "Authors" )
24:
25: ' Display the Records
26: For Each Author in myDataSet.Tables( "Authors" ).Rows
27:  Response.Write( "<p>" & Author( "au_fname" ) & " " 
& Author( "au_lname" ) )
28: Next
29: %>
30:

In Listing 6.7.1, the au_fname column contained in the first row of the DataTable is assigned the value Jane. Next, an instance of the SqlCommandBuilder class is used to automatically generate UPDATE, DELETE, and INSERT commands for the DataAdapter. The SqlCommandBuilder automatically builds these commands when a DataAdapter is passed to it.

After the SqlCommandBuilder has done its work, the Update() method of the DataAdapter class is called. The Update method modifies the records in the underlying database table to reflect the changes made to the DataSet.

It's worth emphasizing that you do not need to explicitly create the UPDATE, DELETE, and INSERT commands when working with the DataAdapter. The SqlCommandBuilder will automatically generate these commands for you. As long as you specify a SelectCommand, and the underlying table has a primary key or unique column, the other three commands are generated automatically.


Note

The primary key or unique column requirement is important. The UpdateCommand, InsertCommand, and DeleteCommand classes require a primary key in order to be automatically generated. Furthermore, the commands can apply to only one table.


The page in Listing 6.7.2 does the same thing as the page in Listing 6.7.1, except it is written in C#.

Listing 6.7.2 Using the DataAdapter Update Method in C#

 1: <%@ Page Language="C#" %>
 2: <%@ Import Namespace="System.Data" %>
 3: <%@ Import NameSpace="System.Data.SqlClient" %>
 4:
 5: <%
 6: // Create the DataSet and DataAdapter
 7: SqlConnection myConnection = new 
SqlConnection( "server=localhost;uid=sa;pwd=secret;database=Pubs" );
 8: DataSet myDataSet = new DataSet();
 9: SqlDataAdapter myDataAdapter = new SqlDataAdapter( 
"Select * From Authors3", myConnection );
10: myDataAdapter.Fill( myDataSet, "Authors" );
11:
12: // Change value of first row
13: myDataSet.Tables[ "Authors" ].Rows[ 0 ][ "au_fname" ] = "Jane";
14:
15: // Update the Database Table
16: SqlCommandBuilder myBuilder = new SqlCommandBuilder( myDataAdapter );
17: myDataAdapter.Update( myDataSet, "Authors" );
18:
19: // Display the Records
20: foreach ( DataRow Author in myDataSet.Tables[ "Authors" ].Rows )
21: {
22:  Response.Write( "<p>" + Author[ "au_fname" ] + " " 
+ Author[ "au_lname" ] );
23: }
24: %>
25:

Summary

We've covered a lot of material in this chapter. You've been provided with an overview of all the major classes contained in ADO.NET.

In the first part of this chapter, you learned how to use the Connection, Command, and DataReader classes to access and modify data in a database table. You learned how to create and open a database connection to both a Microsoft SQL Server and a Microsoft Access database. You also learned how to use the Command class to represent parameterized queries and work with SQL stored procedures.

In the last part of this chapter, you learned how to represent disconnected data with the DataSet, DataTable, DataRelation, and DataView classes. You learned how to create DataTables from existing database tables and how to create DataTables programmatically. You also learned how to sort and filter a DataTable as well as how to create Master/Detail relationships between DataTables by using the DataRelation class. Finally, you learned how to synchronize a DataTable and an underlying database table with the DataAdapter class.

Other Resources

For more information on the material covered in this chapter: