Chapter 8. Data Management with ADO.NET

This chapter provides information on programming with the data management features that are part of ADO.NET, a key component of the .NET Framework and of your ASP.NET development. The discussion begins with the basics of ADO.NET and later dives into the ways you can use various features that make up ADO.NET to manage data contained in a relational database.

ADO.NET, first introduced in version 1.0 of the .NET Framework, provided an extensive array of features to handle live data in a connected mode or data that is disconnected from its underlying data store. ADO.NET 1.0 was primarily developed to address two specific problems in getting at data. The first had to do with the user's need to access data once and to iterate through a collection of data in a single instance. This need often arose in Web application development.

ADO.NET addresses a couple of the most common data-access strategies that are used for applications today. When classic ADO was developed, many applications could be connected to the data store almost indefinitely. Today, with the explosion of the Internet as the means of data communication, a new data technology is required to make data accessible and updateable in a disconnected architecture.

The first of these common data-access scenarios is one in which a user must locate a collection of data and iterate through this data a single time. This is a popular scenario for Web pages. When a request for data from a Web page that you have created is received, you can simply fill a table with data from a data store. In this case, you go to the data store, grab the data that you want, send the data across the wire, and then populate the table. In this scenario, the goal is to get the data in place as fast as possible.

The second way to work with data in this disconnected architecture is to grab a collection of data and use this data separately from the data store itself. This could be on the server or even on the client. Although the data is disconnected, you want the ability to keep the data (with all of its tables and relations in place) on the client side. Classic ADO data was represented by a single table that you could iterate through. ADO.NET, however, can be a reflection of the data store itself, with tables, columns, rows, and relations all in place. When you are done with the client-side copy of the data, you can persist the changes that you made in the local copy of data directly back into the data store. The technology that gives you this capability is the DataSet, which will be covered shortly.

Although classic ADO was geared for a two-tiered environment (client-server), ADO.NET addresses a multi-tiered environment. ADO.NET is easy to work with because it has a unified programming model. This unified programming model makes working with data on the server the same as working with data on the client. Because the models are the same, you find yourself more productive when working with ADO.NET.

Basic ADO.NET Features

This chapter begins with a quick look at the basics of ADO.NET and then provides an overview of basic ADO.NET capabilities, namespaces, and classes. It also reviews how to work with the Connection, Command, DataAdapter, DataSet, and DataReader objects.

Common ADO.NET Tasks

Before jumping into the depths of ADO.NET, step back and make sure that you understand some of the common tasks you might perform programmatically within ADO.NET. This next section looks at the process of selecting, inserting, updating, and deleting data.

The following example makes use of the Northwind.mdf SQL Server Express Database file. To get this database, please search for "Northwind and pubs Sample Databases for SQL Server 2000". You can find this link at www.microsoft.com/downloads/details.aspx?familyid=06616212-0356-46a0-8da2-eebc53a68034&displaylang=en. Once installed, you will find the Northwind.mdf file in the C: SQL Server 2000 Sample Databases directory. To add this database to your ASP.NET application, create an App_Data folder within your project (if it isn't already there) and right-click on the folder and select Add Existing Item. From the provided dialog box, you are then able to browse to the location of the Northwind.mdf file that you just installed. If you are having trouble getting permissions to work with the database, make a data connection to the file from the Visual Studio Server Explorer by right-clicking on the Data Connections node and selecting Add New Connection from the provided menu. You will be asked to be made the appropriate user of the database. ThenVS will make the appropriate changes on your behalf for this to occur.

Selecting Data

After the connection to the data source is open and ready to use, you probably want to read the data from the data source. If you do not want to manipulate the data, but simply to read it or transfer it from one spot to another, you use the DataReader class.

In the following example (Listing 8-1), you use the GetCompanyNameData() function to provide a list of company names from the SQL Northwind database.

Example 8.1. Reading the data from a SQL database using the DataReader class

VB
Imports Microsoft.VisualBasic
Imports System.Collections.Generic
Imports System.Data
Imports System.Data.SqlClient

Public Class SelectingData
    Public Function GetCompanyNameData() As List(Of String)
        Dim conn As SqlConnection
        Dim cmd As SqlCommand
        Dim cmdString As String = "Select CompanyName from Customers"
        conn = New SqlConnection("Data Source=.SQLEXPRESS;AttachDbFilename=
           |DataDirectory|NORTHWND.MDF;Integrated Security=True;
           User Instance=True")' Put this string on one line in your code
        cmd = New SqlCommand(cmdString, conn)
        conn.Open()

        Dim myReader As SqlDataReader
        Dim returnData As List(Of String) = New List(Of String)
        myReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)

        While myReader.Read()
            returnData.Add(myReader("CompanyName").ToString())
        End While

        Return returnData
    End Function
End Class

C#
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;

public class SelectingData
{
    public List<string> GetCompanyNameData()
    {
        SqlConnection conn;
        SqlCommand cmd;
        string cmdString = "Select CompanyName from Customers";
        conn = new
            SqlConnection(@"Data Source=.SQLEXPRESS;AttachDbFilename=
                |DataDirectory|NORTHWND.MDF;Integrated Security=True;
                User Instance=True");  // Put this string on one line in your code
        cmd = new SqlCommand(cmdString, conn);
        conn.Open();

        SqlDataReader myReader;
        List<string> returnData = new List<string>();

        myReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);

        while (myReader.Read())
        {
            returnData.Add(myReader["CompanyName"].ToString());
}

        return returnData;
    }
}

In this example, you create an instance of both the SqlConnection and the SqlCommand classes. Then, before you open the connection, you simply pass the SqlCommand class a SQL command selecting specific data from the Northwind database. After your connection is opened (based upon the commands passed in), you create a DataReader. To read the data from the database, you iterate through the data with the DataReader by using the myReader.Read() method. After the List(Of String) object is built, the connection is closed, and the object is returned from the function.

Inserting Data

When working with data, you often insert the data into the data source. Listing 8-2 shows you how to do this. This data may have been passed to you by the end user through the XML Web Service, or it may be data that you generated within the logic of your class.

Example 8.2. Inserting data into SQL Server

VB
Public Sub InsertData()
    Dim conn As SqlConnection
    Dim cmd As SqlCommand
    Dim cmdString As String = "Insert Customers (CustomerID, _
       CompanyName, ContactName) Values ('BILLE', 'XYZ Company', 'Bill Evjen')"
    conn = New SqlConnection("Data Source=.SQLEXPRESS;AttachDbFilename=
           |DataDirectory|NORTHWND.MDF;Integrated Security=True;
           User Instance=True")' Put this string on one line in your code
    cmd = New SqlCommand(cmdString, conn)
    conn.Open()

    cmd.ExecuteNonQuery()
    conn.Close()
End Sub

C#
public void InsertData()
{
    SqlConnection conn;
    SqlCommand cmd;
    string cmdString = "Insert Customers (CustomerID, CompanyName,
       ContactName) Values ('BILLE', 'XYZ Company', 'Bill Evjen')";
    conn = new
           SqlConnection(@"Data Source=.SQLEXPRESS;AttachDbFilename=
               |DataDirectory|NORTHWND.MDF;Integrated Security=True;
               User Instance=True");  // Put this string on one line in your code
    cmd = new SqlCommand(cmdString, conn);
    conn.Open();

    cmd.ExecuteNonQuery();
    conn.Close();
}

Inserting data into SQL is pretty straightforward and simple. Using the SQL command string, you insert specific values for specific columns. The actual insertion is initiated using the cmd.ExecuteNonQuery() command. This executes a command on the data when you don't want anything in return.

Updating Data

In addition to inserting new records into a database, you frequently update existing rows of data in a table. Imagine a table in which you can update multiple records at once. In the example in Listing 8-3, you want to update an employee table by putting a particular value in the emp_bonus column if the employee has been at the company for five years or longer.

Example 8.3. Updating data in SQL Server

VB
Public Function UpdateEmployeeBonus() As Integer
    Dim conn As SqlConnection
    Dim cmd As SqlCommand
    Dim RecordsAffected as Integer
    Dim cmdString As String = "UPDATE Employees SET emp_bonus=1000 WHERE " & _
       "yrs_duty>=5"
    conn = New SqlConnection("Data Source=.SQLEXPRESS;AttachDbFilename=
           |DataDirectory|NORTHWND.MDF;Integrated Security=True;
           User Instance=True")' Put this string on one line in your code
    cmd = New SqlCommand(cmdString, conn)
    conn.Open()

    RecordsAffected = cmd.ExecuteNonQuery()
    conn.Close()

    Return RecordsAffected
End Function

C#
public int UpdateEmployeeBonus()
{
    SqlConnection conn;
    SqlCommand cmd;
    int RecordsAffected;
    string cmdString = "UPDATE Employees SET emp_bonus=1000 WHERE yrs_duty>=5";
    conn = new
        SqlConnection(@"Data Source=.SQLEXPRESS;AttachDbFilename=
            |DataDirectory|NORTHWND.MDF;Integrated Security=True;
            User Instance=True");  // Put this string on one line in your code

    cmd = new SqlCommand(cmdString, conn);
    conn.Open();

    RecordsAffected = cmd.ExecuteNonQuery();
    conn.Close();

    return RecordsAffected;
}

This update function iterates through all the employees in the table and changes the value of the emp_bonus field to 1000 if an employee has been with the company for more than five years. This is done with the SQL command string. The great thing about these update capabilities is that you can capture the number of records that were updated by assigning the ExecuteNonQuery() command to the RecordsAffected variable. The total number of affected records is then returned by the function.

Deleting Data

Along with reading, inserting, and updating data, you sometimes need to delete data from the data source. Deleting data is a simple process of using the SQL command string and then the ExecuteNonQuery() command as you did in the update example. See Listing 8-4 for an illustration of this.

Example 8.4. Deleting data from SQL Server

VB
Public Function DeleteEmployee() As Integer
    Dim conn As SqlConnection
    Dim cmd As SqlCommand
    Dim RecordsAffected as Integer
    Dim cmdString As String = "DELETE Employees WHERE LastName='Evjen'"
    conn = New SqlConnection("Data Source=.SQLEXPRESS;AttachDbFilename=
           |DataDirectory|NORTHWND.MDF;Integrated Security=True;
           User Instance=True")' Put this string on one line in your code
    cmd = New SqlCommand(cmdString, conn)
    conn.Open()

    RecordsAffected = cmd.ExecuteNonQuery()
    conn.Close()

    Return RecordsAffected
End Function

C#
public int DeleteEmployee()
{
    SqlConnection conn;
    SqlCommand cmd;
    int RecordsAffected;
    string cmdString = "DELETE Employees WHERE LastName='Evjen'";
    conn = new
        SqlConnection(@"Data Source=.SQLEXPRESS;AttachDbFilename=
            |DataDirectory|NORTHWND.MDF;Integrated Security=True;
            User Instance=True");  // Put this string on one line in your code
    cmd = new SqlCommand(cmdString, conn);
    conn.Open();

    RecordsAffected = cmd.ExecuteNonQuery();
    conn.Close();

    return RecordsAffected;
}

You can assign the ExecuteNonQuery() command to an Integer variable (just as you did for the update function) to return the number of records deleted.

Basic ADO.NET Namespaces and Classes

The six core ADO.NET namespaces are shown in the following table. In addition to these namespaces, each new data provider can have its own namespace. As an example, the Oracle .NET data provider adds a namespace of System.Data.OracleClient (for the Microsoft-built Oracle data provider).

Namespace

Description

System.Data

This namespace is the core of ADO.NET. It contains classes used by all data providers. It contains classes to represent tables, columns, rows, and the DataSet class. It also contains several useful interfaces, such as IDbCommand, IDbConnection, and IDbDataAdapter. These interfaces are used by all managed providers, enabling them to plug into the core of ADO.NET.

System.Data.Common

This namespace defines common classes that are used as base classes for data providers. All data providers share these classes. A few examples are DbConnection and DbDataAdapter.

System.Data.OleDb

This namespace defines classes that work with OLE-DB data sources using the .NET OleDb data provider. It contains classes such as OleDbConnection and OleDbCommand.

System.Data.Odbc

This namespace defines classes that work with the ODBC data sources using the .NET ODBC data provider. It contains classes such as OdbcConnection and OdbcCommand.

System.Data.SqlClient

This namespace defines a data provider for the SQL Server 7.0 or higher database. It contains classes such as SqlConnection and SqlCommand.

System.Data.SqlTypes

This namespace defines a few classes that represent specific data types for the SQL Server database.

ADO.NET has three distinct types of classes commonly referred to as Disconnected, Shared, and Data Providers. The Disconnected classes provide the basic structure for the ADO.NET framework. A good example of this type of class is the DataTable class. The objects of this class are capable of storing data without any dependency on a specific data provider. The Shared classes form the base classes for data providers and are shared among all data providers. The Data Provider classes are meant to work with different kinds of data sources. They are used to perform all data-management operations on specific databases. The SqlClient data provider, for example, works only with the SQL Server database.

A data provider contains Connection, Command, DataAdapter, and DataReader objects. Typically, in programming ADO.NET, you first create the Connection object and provide it with the necessary information, such as the connection string. You then create a Command object and provide it with the details of the SQL command that is to be executed. This command can be an inline SQL text command, a stored procedure, or direct table access. You can also provide parameters to these commands if needed. After you create the Connection and the Command objects, you must decide whether the command returns a result set. If the command doesn't return a result set, you can simply execute the command by calling one of its several Execute methods. On the other hand, if the command returns a result set, you must make a decision about whether you want to retain the result set for future uses without maintaining the connection to the database. If you want to retain the result set, you must create a DataAdapter object and use it to fill a DataSet or a DataTable object. These objects are capable of maintaining their information in a disconnected mode. However, if you don't want to retain the result set, but rather to simply process the command in a swift fashion, you can use the Command object to create a DataReader object. The DataReader object needs a live connection to the database, and it works as a forward-only, read-only cursor.

Using the Connection Object

The Connection object creates a link (or connection) to a specified data source. This object must contain the necessary information to discover the specified data source and to log in to it properly using a defined username and password combination. This information is provided via a single string called a connection string. You can also store this connection string in the web.config file of your application.

Every type of data provider has a connection object of some kind. The data provider for working with a SQL data store includes a SqlConnection class that performs this type of operation. The SqlConnection object is a class that is specific to the SqlClient provider. As discussed earlier in this chapter, the SqlClient provider is built for working with the SQL Server 7.0 and higher databases. The properties for the SqlConnection class are shown in the following table.

Property

Description

ConnectionString

This property allows you to read or provide the connection string that should be used by the SqlConnection object.

Database

This read-only property returns the name of the database to use after the connection is opened.

Datasource

This read-only property returns the name of the instance of the SQL Server database used by the SqlConnectionobject.

State

This read-only property returns the current state of the connection. The possible values are Broken, Closed, Connecting, Executing, Fetching, and Open.

Connecting to a data source is probably the most common task when you are working with data. This example and the ones that follow assume that you have a SQL Server database. In order to connect to your SQL Server database, you use the SqlConnection class. This is shown in Listing 8-5.

Example 8.5. Connecting to a SQL database

VB
Dim conn as SqlConnection
conn = New SqlConnection("Data Source=.SQLEXPRESS;AttachDbFilename=
           |DataDirectory|NORTHWND.MDF;Integrated Security=True;
           User Instance=True")' Put this string on one line in your code
conn.Open()

C#
SqlConnection conn;
conn = new
         SqlConnection(@"Data Source=.SQLEXPRESS;AttachDbFilename=
|DataDirectory|NORTHWND.MDF;Integrated Security=True;
            User Instance=True");  // Put this string on one line in your code

conn.Open();

To make this connection work, be sure that the proper namespaces are imported before you start using any of the classes that work with SQL. The first step in making a connection is to create an instance of the SqlConnection class and assign it to the conn instance. This SqlConnection class is initialized after you pass in the connection string as a parameter to the class. In this case, you are connecting to the Northwind database that resides on your local machine using the system administrator's login credentials.

Another means of making a connection is to put the connection string within the application's web.config file and then to make a reference to the web.config file. With ASP.NET 3.5, you will find that there is an easy way to manage the storage of your connection strings through the use of the web.config file. This is actually a better way to store your connection strings rather than hard-coding them within the code of the application itself. In addition to having a single point in the application where the credentials for database access can be managed, storing credentials in the web.config also gives you the ability to encrypt the credentials.

To define your connection string within the web.config file, you are going to make use of the <connectionStrings> section. From this section, you can place an <add> element within it to define your connection. An example of this is illustrated in Listing 8-6.

Example 8.6. Providing your connection string within the web.config file

<connectionStrings>
  <add name="DSN_Northwind" connectionString="Data
   Source=.SQLEXPRESS;AttachDbFilename=|DataDirectory|NORTHWND.MDF;Integrated
   Security=True;User Instance=True"
   providerName="System.Data.SqlClient" />
 </connectionStrings>

In many places of this chapter, you will see that the actual connection string is broken up on multiple lines. This connection string will need to be on a single line within your code or broken up with string concatenation.

Now that you have a connection string within the web.config file, you can then make use of that connection string directly in your code by using the ConnectionManager object as illustrated here in Listing 8-7.

Example 8.7. Using the connection string found in the web.config file

VB
conn = New _
   SqlConnection( _
   ConfigurationManager.ConnectionStrings("DSN_Northwind").ConnectionString)

C#
conn = new
   SqlConnection(
   ConfigurationManager.ConnectionStrings["DSN_Northwind"].ConnectionString);

For this line of code to work, you are going to have to make a reference to the System.Configuration namespace.

When you complete your connection to the data source, be sure that you close the connection by using conn.Close(). The .NET Framework does not implicitly release the connections when they fall out of scope.

Using the Command Object

The Command object uses the Connection object to execute SQL queries. These queries can be in the form of inline text, stored procedures, or direct table access. If the SQL query uses a SELECT clause, the result set it returns is usually stored in either a DataSet or a DataReader object. The Command object provides a number of Execute methods that can be used to perform various types of SQL queries.

Next, take a look at some of the more useful properties of the SqlCommand class, as shown in the following table.

Property

Description

CommandText

This read/write property allows you to set or retrieve either the T-SQL statement or the name of the stored procedure.

CommandTimeout

This read/write property gets or sets the number of seconds to wait while attempting to execute a particular command. The command is aborted after it times out and an exception is thrown. The default time allotted for this operation is 30 seconds.

CommandType

This read/write property indicates the way the CommandTextproperty should be interpreted. The possible values are StoredProcedure, TableDirect, and Text. The value of Text means that your SQL statement is inline or contained within the code itself.

Connection

This read/write property gets or sets the SqlConnection object that should be used by this Command object.

Next, take a look at the various Execute methods that can be called from a Command object.

Property

Description

ExecuteNonQuery

This method executes the command specified and returns the number of rows affected.

ExecuteReader

This method executes the command specified and returns an instance of the SqlDataReader class. The DataReader object is a read-only and forward-only cursor.

ExecuteRow

This method executes the command and returns an instance of the SqlRecord class. This object contains only a single returned row.

ExecuteScalar

This method executes the command specified and returns the first column of the first row in the form of a generic object. The remaining rows and columns are ignored.

ExecuteXmlReader

This method executes the command specified and returns an instance of the XmlReader class. This method enables you to use a command that returns the results set in the form of an XML document.

Using the DataReader Object

The DataReader object is a simple forward-only and read-only cursor. It requires a live connection with the data source and provides a very efficient way of looping and consuming all or part of the result set. This object cannot be directly instantiated. Instead, you must call the ExecuteReader method of the Command object to obtain a valid DataReader object.

When using a DataReader object, be sure to close the connection when you are done using the data reader. If not, then the connection stays alive. The connection utilized stays alive until it is explicitly closed using the Close() method or until you have enabled your Command object to close the connection. You can close the connection after using the data reader in one of two ways. One way is to provide the CommandBehavior.CloseConnection enumeration while calling the ExecuteMethod of the Command object. This approach works only if you loop through the data reader until you reach the end of the result set, at which point the reader object automatically closes the connection for you. However, if you don't want to keep reading the data reader until the end of the result set, you can call the Close() method of the Connection object yourself.

Listing 8-8 shows the Connection, Command, and DataReader objects in action. It shows how to connect to the Northwind database (an example database found in the Microsoft's SQL Server 7.0, 2000, 2005, or 2008 database servers), read the Customers table within this database, and display the results in a GridView server control.

Example 8.8. The SqlConnection, SqlCommand, and SqlDataReader objects in action

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs)

        If Not Page.IsPostBack Then
            Dim MyReader As SqlDataReader

        Dim MyConnection As SqlConnection = New SqlConnection()
MyConnection.ConnectionString = _
        ConfigurationManager.ConnectionStrings("DSN_Northwind").ConnectionString

            Dim MyCommand As SqlCommand = New SqlCommand()
            MyCommand.CommandText = "SELECT TOP 3 * FROM CUSTOMERS"
            MyCommand.CommandType = CommandType.Text
            MyCommand.Connection = MyConnection

            MyCommand.Connection.Open()
            MyReader = MyCommand.ExecuteReader(CommandBehavior.CloseConnection)

            gvCustomers.DataSource = MyReader
            gvCustomers.DataBind()

            MyCommand.Dispose()
            MyConnection.Dispose()
        End If
    End Sub
</script>

<html>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:GridView ID="gvCustomers" runat="server">
        </asp:GridView>
    </div>
    </form>
</body>
</html>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            SqlDataReader MyReader;
            SqlConnection MyConnection = new SqlConnection();
            MyConnection.ConnectionString =
        ConfigurationManager.ConnectionStrings["DSN_Northwind"].ConnectionString;

            SqlCommand MyCommand = new SqlCommand();
            MyCommand.CommandText = "SELECT TOP 3 * FROM CUSTOMERS";
            MyCommand.CommandType = CommandType.Text;
            MyCommand.Connection = MyConnection;

            MyCommand.Connection.Open();
MyReader = MyCommand.ExecuteReader(CommandBehavior.CloseConnection);

            gvCustomers.DataSource = MyReader;
            gvCustomers.DataBind();

            MyCommand.Dispose();
            MyConnection.Dispose();
        }
    }
</script>

The code shown in Listing 8-8 uses the SqlConnection class to create a connection with the Northwind database using the connection string stored in the web.config file. This connection string is then retrieved using the ConfigurationManager class. It is always best to store your connection strings inside the web.config and to reference them in this manner. If you have a single place to work with your connection strings, any task is a lot more manageable than if you place all your connection strings in the actual code of your application.

After working with the connection string, this bit of code from Listing 8-8 creates a Command object using the SqlCommand class because you are interested in working with a SQL database. Next, the code provides the command text, command type, and connection properties. After the command and the connection are created, the code opens the connection and executes the command by calling the ExecuteReader method of the MyCommand object. After receiving the data reader from the Command object, you simply bind the retrieved results to an instance of the GridView control. The results are shown in Figure 8-1.

Figure 8-1

Figure 8.1. Figure 8-1

Using Data Adapter

The SqlDataAdapter is a special class whose purpose is to bridge the gap between the disconnected DataTable objects and the physical data source. The SqlDataAdapter provides a two-way data transfer mechanism. It is capable of executing a SELECT statement on a data source and transferring the result set into a DataTable object. It is also capable of executing the standard INSERT, UPDATE, and DELETE statements and extracting the input data from a DataTable object.

The commonly used properties offered by the SqlDataAdapter class are shown in the following table.

Property

Description

SelectCommand

This read/write property sets or gets an object of type SqlCommand. This command is automatically executed to fill a DataTable with the result set.

InsertCommand

This read/write property sets or gets an object of type SqlCommand. This command is automatically executed to insert a new record to the SQL Server database.

UpdateCommand

This read/write property sets or gets an object of type SqlCommand. This command is automatically executed to update an existing record on the SQL Server database.

DeleteCommand

This read/write property sets or gets an object of type SqlCommand. This command is automatically executed to delete an existing record on the SQL Server database.

The SqlDataAdapter class also provides a method called Fill(). Calling the Fill() method automatically executes the command provided by the SelectCommand property, receives the result set, and copies it to a DataTable object.

The code example in Listing 8-9 illustrates how to use an object of SqlDataAdapter class to fill a DataTable object.

Example 8.9. Using an object of SqlDataAdapter to fill a DataTable

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs)

        If Not Page.IsPostBack Then
           Dim MyTable As DataTable = New DataTable()

           Dim MyConnection As SqlConnection = New SqlConnection()
           MyConnection.ConnectionString = _
           ConfigurationManager.ConnectionStrings("DSN_Northwind").ConnectionString

           Dim MyCommand As SqlCommand = New SqlCommand()
           MyCommand.CommandText = "SELECT TOP 5 * FROM CUSTOMERS"
           MyCommand.CommandType = CommandType.Text
           MyCommand.Connection = MyConnection

           Dim MyAdapter As SqlDataAdapter = New SqlDataAdapter()
           MyAdapter.SelectCommand = MyCommand
MyAdapter.Fill(MyTable)

            gvCustomers.DataSource = MyTable.DefaultView
            gvCustomers.DataBind()

            MyAdapter.Dispose()
            MyCommand.Dispose()
            MyConnection.Dispose()
        End If

    End Sub
</script>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            DataTable MyTable = new DataTable();

            SqlConnection MyConnection = new SqlConnection();
            MyConnection.ConnectionString =
                ConfigurationManager.
                ConnectionStrings["DSN_Northwind"].ConnectionString;

            SqlCommand MyCommand = new SqlCommand();
            MyCommand.CommandText = "SELECT TOP 5 * FROM CUSTOMERS";
            MyCommand.CommandType = CommandType.Text;
            MyCommand.Connection = MyConnection;

            SqlDataAdapter MyAdapter = new SqlDataAdapter();
            MyAdapter.SelectCommand = MyCommand;
            MyAdapter.Fill(MyTable);

            gvCustomers.DataSource = MyTable.DefaultView;
            gvCustomers.DataBind();

            MyAdapter.Dispose();
            MyCommand.Dispose();
            MyConnection.Dispose();        }
    }
</script>

The code shown in Listing 8-9 creates a Connection and Command object and then proceeds to create an instance of the SqlDataAdapter class. It then sets the SelectCommand property of the DataAdapter object to the Command object it had previously created. After the DataAdapter object is ready for executing, the code executes the Fill() method, passing it an instance of the DataTable class. The Fill() method populates the DataTable object. Figure 8-2 shows the result of executing this code.

Figure 8-2

Figure 8.2. Figure 8-2

Using Parameters

Most serious database programming, regardless of how simple it might be, requires you to configure SQL statements using parameters. Using parameters helps guard against possible SQL injection attacks. Obviously, a discussion on the basics of ADO.NET programming is not complete without covering the use of parameterized SQL statements.

Creating a parameter is as simple as declaring an instance of the SqlParameter class and providing it the necessary information, such as parameter name, value, type, size, direction, and so on. The following table shows the properties of the SqlParameter class.

Property

Description

ParameterName

This read/write property gets or sets the name of the parameter.

SqlDbType

This read/write property gets or sets the SQL Server database type of the parameter value.

Size

This read/write property sets or gets the size of the parameter value.

Direction

This read/write property sets or gets the direction of the parameter, such as Input, Output, or InputOutput.

SourceColumn

This read/write property maps a column from a DataTable to the parameter. It enables you to execute multiple commands using the SqlDataAdapter object and pick the correct parameter value from a DataTable column during the command execution.

Value

This read/write property sets or gets the value provided to the parameter object. This value is passed to the parameter defined in the command during runtime.

Listing 8-10 modifies the code shown in Listing 8-5 to use two parameters while retrieving the list of customers from the database.

Example 8.10. The use of a parameterized SQL statement

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs)
        If Not Page.IsPostBack Then
            Dim MyReader As SqlDataReader
            Dim CityParam As SqlParameter
            Dim ContactParam As SqlParameter

            Dim MyConnection As SqlConnection = New SqlConnection()
            MyConnection.ConnectionString = _
         ConfigurationManager.ConnectionStrings("DSN_Northwind").ConnectionString

            Dim MyCommand As SqlCommand = New SqlCommand()
            MyCommand.CommandText = _
         "SELECT * FROM CUSTOMERS WHERE CITY = @CITY AND CONTACTNAME = @CONTACT"
            MyCommand.CommandType = CommandType.Text
            MyCommand.Connection = MyConnection

            CityParam = New SqlParameter()
            CityParam.ParameterName = "@CITY"
            CityParam.SqlDbType = SqlDbType.VarChar
            CityParam.Size = 15
            CityParam.Direction = ParameterDirection.Input
            CityParam.Value = "Berlin"

            ContactParam = New SqlParameter()
            ContactParam.ParameterName = "@CONTACT"
            ContactParam.SqlDbType = SqlDbType.VarChar
            ContactParam.Size = 15
            ContactParam.Direction = ParameterDirection.Input
            ContactParam.Value = "Maria Anders"

            MyCommand.Parameters.Add(CityParam)
            MyCommand.Parameters.Add(ContactParam)

            MyCommand.Connection.Open()
            MyReader = MyCommand.ExecuteReader(CommandBehavior.CloseConnection)

            gvCustomers.DataSource = MyReader
            gvCustomers.DataBind()

            MyCommand.Dispose()
            MyConnection.Dispose()
        End If
End Sub
</script>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
       if (!Page.IsPostBack)
       {
            SqlDataReader MyReader;
            SqlParameter CityParam;
            SqlParameter ContactParam;

            SqlConnection MyConnection = new SqlConnection();
            MyConnection.ConnectionString =
        ConfigurationManager.ConnectionStrings["DSN_Northwind"].ConnectionString;

            SqlCommand MyCommand = new SqlCommand();
            MyCommand.CommandText =
        "SELECT * FROM CUSTOMERS WHERE CITY = @CITY AND CONTACTNAME = @CONTACT";
            MyCommand.CommandType = CommandType.Text;
            MyCommand.Connection = MyConnection;

            CityParam = new SqlParameter();
            CityParam.ParameterName = "@CITY";
            CityParam.SqlDbType = SqlDbType.VarChar;
            CityParam.Size = 15;
            CityParam.Direction = ParameterDirection.Input;
            CityParam.Value = "Berlin";

            ContactParam = new SqlParameter();
            ContactParam.ParameterName = "@CONTACT";
            ContactParam.SqlDbType = SqlDbType.VarChar;
            ContactParam.Size = 15;
            ContactParam.Direction = ParameterDirection.Input;
            ContactParam.Value = "Maria Anders";

            MyCommand.Parameters.Add(CityParam);
            MyCommand.Parameters.Add(ContactParam);

            MyCommand.Connection.Open();
            MyReader = MyCommand.ExecuteReader(CommandBehavior.CloseConnection);

            gvCustomers.DataSource = MyReader;
            gvCustomers.DataBind();

            MyCommand.Dispose();
MyConnection.Dispose();
       }
    }
</script>

The code shown in Listing 8-8 uses a parameterized SQL statement that receives the name of the city and the contact person to narrow the result set. These parameters are provided by instantiating a couple of instances of the SqlParameter class and filling in the appropriate name, type, size, direction, and value properties for each object of SqlParameter class. From there, you add the populated parameters to the Command object by invoking the Add() method of the Parameters collection. The result of executing this code is shown in Figure 8-3.

Figure 8-3

Figure 8.3. Figure 8-3

Understanding DataSet and DataTable

Most programmers agree that the DataSet class is the most commonly used part of ADO.NET in real- world, database-driven applications. This class provides mechanisms for managing data when it is disconnected from the data source. This capability to handle data in a disconnected state was first introduced in .NET during the 1.0 version of ADO.NET. The current 3.5 version of ADO.NET retains all the features of its predecessors and provides a few newer, much needed features.

An object created from the DataSet class works as a container for other objects that are created from the DataTable class. The DataTable object represents a logical table in memory. It contains rows, columns, primary keys, constraints, and relations with other DataTable objects. Therefore, you could have a DataSet that is made up of two distinct tables such as a Customers and an Orders table. Then you could use the DataSet, just as you would any other relational data source, to make a relation between the two tables in order to show all the orders for a particular customer.

Most of the disconnected data-driven programming is actually done using one or more DataTable objects within the DataSet. However, the previous versions of ADO.NET didn't allow you to work directly with the DataTable object for some very important tasks, such as reading and writing data to and from an XML file. It didn't even allow you to serialize the DataTable object independently of the larger and encompassing DataSet object. This limitation required you to always use the DataSet object to perform any operation on a DataTable. The current version of ADO.NET removes this limitation and enables you to work directly with the DataTable for all your needs. In fact, we recommend that you don't use the DataSet object unless you need to work with multiple DataTable objects and need a container object to manage them. If you end up working with only a single table of information, then it is best to work with an instance of the DataTable object rather than a DataSet that contains only a single DataTable.

The current version of ADO.NET provides the capability to load a DataTable in memory by consuming a data source using a DataReader. In the past, you were sometimes restricted to creating multiple overloads of the same method just to work with both the DataReader and the DataTable objects. Now you have the flexibility to write the data access code one time and reuse the DataReader — either directly or to fill a DataTable, as shown in Listing 8-11.

Example 8.11. How to load a DataTable from a DataReader

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs)

        If Not Page.IsPostBack Then
            Dim MyDataTable As DataTable
            Dim MyReader As SqlDataReader
            Dim CityParam As SqlParameter

            Dim MyConnection As SqlConnection = New SqlConnection()
            MyConnection.ConnectionString = _
        ConfigurationManager.ConnectionStrings("DSN_Northwind").ConnectionString

            Dim MyCommand As SqlCommand = New SqlCommand()
            MyCommand.CommandText = _
                    "SELECT * FROM CUSTOMERS WHERE CITY = @CITY"
            MyCommand.CommandType = CommandType.Text
            MyCommand.Connection = MyConnection

            CityParam = New SqlParameter()
            CityParam.ParameterName = "@CITY"
            CityParam.SqlDbType = SqlDbType.VarChar
            CityParam.Size = 15
            CityParam.Direction = ParameterDirection.Input
            CityParam.Value = "London"

            MyCommand.Parameters.Add(CityParam)

            MyCommand.Connection.Open()
            MyReader = MyCommand.ExecuteReader(CommandBehavior.CloseConnection)
MyDataTable = New DataTable()

           ' Loading DataTable using a DataReader
            MyDataTable.Load(MyReader)

            gvCustomers.DataSource = MyDataTable
            gvCustomers.DataBind()

            MyDataTable.Dispose()
            MyCommand.Dispose()
            MyConnection.Dispose()
        End If

    End Sub

</script>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack )
        {
            DataTable MyDataTable;
            SqlDataReader MyReader;
            SqlParameter CityParam;

            SqlConnection MyConnection = new SqlConnection();
            MyConnection.ConnectionString =
        ConfigurationManager.ConnectionStrings["DSN_Northwind"].ConnectionString;

            SqlCommand MyCommand = new SqlCommand();
            MyCommand.CommandText =
               "SELECT * FROM CUSTOMERS WHERE CITY = @CITY";
            MyCommand.CommandType = CommandType.Text;
            MyCommand.Connection = MyConnection;

            CityParam = new SqlParameter();
            CityParam.ParameterName = "@CITY";
            CityParam.SqlDbType = SqlDbType.VarChar;
            CityParam.Size = 15;
            CityParam.Direction = ParameterDirection.Input;
            CityParam.Value = "London";

            MyCommand.Parameters.Add(CityParam);
            MyCommand.Connection.Open();
            MyReader = MyCommand.ExecuteReader(CommandBehavior.CloseConnection);
MyDataTable = new DataTable();

            // Loading DataTable using a DataReader
            MyDataTable.Load(MyReader);

            gvCustomers.DataSource = MyDataTable;
            gvCustomers.DataBind();

            MyDataTable.Dispose();
            MyCommand.Dispose();
            MyConnection.Dispose();
        }
    }
</script>

Not only can you load a DataTable object from a DataReader object, you can also retrieve a DataTableReader from an existing DataTable object. This is accomplished by calling the CreateDataReader method of the DataTable class. This method returns an instance of the DataTableReader object that can be passed to any method that expects to receive a DataReader.

Deciding When to Use a DataSet

As revolutionary as a DataSet might be, it is not the best choice in every situation. Often, it may not be appropriate to use a DataSet; instead it might be better to use a DataReader.

With ADO 2.6, it was possible to perform a command upon a data store and get back a single collection of data made up of any number of rows. You could then iterate through this collection of data and use it in some fashion. Now ADO.NET can use the DataSet to return a collection of data that actually keeps its structure when removed from the data store. In some situations, you benefit greatly from keeping this copy in its original format. By doing so, you can keep the data disconnected in an in-memory cache in its separate tables and work with the tables individually or apply relationships between the tables. You can work with the tables in much the same manner as you do with other relational data sources — using a parent/child relationship. If it is to your advantage to work with certain data with all its relationships in place (in order to enforce a parent/child relationship upon the data); in this case, of course, it is better to use a DataSet as opposed to a DataReader.

Because the DataSet is a disconnected copy of the data, you can work with the same records repeatedly without having to go back to the data store. This capability can greatly increase performance and lessen the load upon the server. Having a copy of the data separate from the data store also enables you to continuously handle and shape the data locally. For instance, you might need to repeatedly filter or sort through a collection of data. In this case, it would be of great advantage to work with a DataSet rather than going back and forth to the data store itself.

Probably one of the greatest uses of the DataSet is to work with multiple data stores and come away with a single collection of data. So for instance, if you have your Customers table within SQL and the orders information for those particular customers within an Oracle database, you can very easily query each data store and create a single DataSet with a Customers and an Orders table in place that you can use in any fashion you choose. The DataSet is just a means of storage for data and doesn't concern itself with where the data came from. So, if you are working with data that is coming from multiple data stores, it is to your benefit to use the DataSet.

Because the DataSet is based upon XML and XML Schemas, it is quite easy to move the DataSet around — whether you are transporting it across tiers, processes or between disparate systems or applications. If the application or system to which you are transferring the DataSet doesn't understand DataSets, the DataSet represents itself as an XML file. So basically, any system or application that can interpret and understand XML can work with the DataSet. This makes it a very popular transport vehicle, and you see an example of it when you transport the DataSet from an XML Web service.

Last but not least, the DataSet enables you to program data with ease. It is much simpler than anything that has been provided before the .NET Framework came to the scene. Putting the data within a class object allows you to programmatically access the DataSet. The code example in Listing 8-12 shows you just how easy it can be.

Example 8.12. An example of working with the DataSet object

VB
Dim conn As SqlConnection = New SqlConnection _
   (ConfigurationManager.ConnectionStrings("DSN_Northwind").ConnectionString)
conn.Open()
Dim da As SqlDataAdapter = New SqlDataAdapter("Select * from Customers", conn)
Dim ds As DataSet = New DataSet()
da.Fill(ds, "CustomersTable")

C#
SqlConnection conn = new SqlConnection
   (ConfigurationManager.ConnectionStrings["DSN_Northwind"].ConnectionString);
conn.Open();
SqlDataAdapter da = new SqlDataAdapter("Select * from Customers", conn);
DataSet ds = new DataSet();
da.Fill(ds, "CustomersTable");

Basically, when you work with data, you have to weigh when to use the DataSet. In some cases, you get extreme benefits from using this piece of technology that is provided with ADO.NET. Sometimes, however, you may find it is not in your best interests to use the DataSet. Instead, it is best to use the DataReader.

The DataSet can be used whenever you choose, but sometimes you would rather use the DataReader and work directly against the data store. By using the command objects, such as the SqlCommand and the OleDbCommand objects, you have a little more direct control over what is executed and what you get back as a result set. In situations where this is vital, it is to your advantage not to use the DataSet.

When you don't use the DataSet, you don't incur the cost of extra overhead because you are reading and writing directly to the data source. Performing operations in this manner means you don't have to instantiate any additional objects — avoiding unnecessary steps.

This is especially true in a situation when you work with Web Forms in ASP.NET. If you are dealing with Web Forms, the Web pages are re-created each and every time. When this happens, not only is the page re-created by the call to the data source, the DataSet is also re-created unless you are caching the DataSet in some fashion. This can be an expensive process; so, in situations such as this, you might find it to your benefit to work directly off the data source using the DataReader. In most situations when you are working with Web Forms, you want to work with the DataReader instead of creating a DataSet.

The Typed DataSet

As powerful as the DataSet is, it still has some limitations. The DataSet is created at runtime. It accesses particular pieces of data by making certain assumptions. Take a look at how you normally access a specific field in a DataSet that is not strongly typed (Listing 8-13).

Example 8.13. Accessing a field in a DataSet

VB
ds.Tables("Customers").Rows(0).Columns("CompanyName") = "XYZ Company"

C#
ds.Tables["Customers"].Rows[0].Columns["CompanyName"] = "XYZ Company";

The preceding code looks at the Customers table, the first row (remember, everything is zero-based) in the column CompanyName, and assigns the value of XYZ Company to the field. This is pretty simple and straightforward, but it is based upon certain assumptions and is generated at runtime. The "Customers" and "CompanyName" words are string literals in this line of code. If they are spelled wrong or if these items aren't in the table, an error occurs at runtime.

Listing 8-14 shows you how to assign the same value to the same field by using a typed DataSet.

Example 8.14. Accessing a field in a typed DataSet

VB
ds.Customers(0).CompanyName = "XYZ Company"

C#
ds.Customers[0].CompanyName = "XYZ Company";

Now the table name and the field to be accessed are not treated as string literals but, instead, are encased in an XML Schema and a class that is generated from the DataSet class. When you create a typed DataSet, you are creating a class that implements the tables and fields based upon the schema used to generate the class. Basically, the schema is coded into the class.

As you compare the two examples, you see that a typed DataSet is easier to read and understand. It is less error-prone, and errors are realized at compile time as opposed to runtime.

In the end, typed DataSets are optional, and you are free to use either style as you code.

Using Oracle as Your Database with ASP.NET 3.5

If you work in the enterprise space, in many cases you must work with an Oracle backend database. ADO.NET 2.0 has a built-in capability to work with Oracle using the System.Data.OracleClient namespace.

First, in order to connect ASP.NET to your Oracle database, you install the Oracle 10g Client on your Web server. You can get this piece of software from the Oracle Web site found at oracle.com. If you are able to connect to your Oracle database from your Web server using SQL*Plus (an Oracle IDE for working with an Oracle database), can use the Microsoft-built Oracle data provider, System.Data.OracleClient.

If you are still having trouble connecting to your Oracle database, you also may try to make sure that the database connection is properly defined in your server's .ora file found at C:Oracleproduct10.1.0Client_1NETWORKADMIN. Note that the version number might be different.

After you know you can connect to Oracle, you can make use of the Microsoft-built Oracle data provider. To utilize the built-in capabilities to connect to Oracle, your ASP.NET application must reference this DLL. To do this, right-click your project in the Visual Studio Solution Explorer and select Add Reference from the list of options presented. This gives you a long list of available .NET components. Select the System.Data.OracleClient component. Notice the two versions of this component (as illustrated in Figure 8-4). You select the one that is built for the .NET Framework 2.0.

Figure 8-4

Figure 8.4. Figure 8-4

After this is added, you find the reference to this component in the web.config file of your ASP.NET application (as presented in Listing 8-15).

Example 8.15. The reference to the System.Data.OracleClient DLL in the web.config

<configuration>
   <system.web>
      <compilation debug="true">
         <assemblies>
            <add assembly="System.Data.OracleClient,
                Version=2.0.0.0, Culture=neutral,
                PublicKeyToken=B77A5C561934E089"/>
</assemblies>
      </compilation>
   </system.web>
</configuration>

With this reference in place, you also reference this available DLL in your page along with System.Data. This is demonstrated in Listing 8-16.

Example 8.16. Referencing the System.Data.OracleClient DLL

VB
Imports System.Data
Imports System.Data.OracleClient

C#
using System.Data;
using System.Data.OracleClient;

With all the references in place, you are able to work with an Oracle backend in pretty much the same manner as you work with a SQL Server backend. Listing 8-17 shows you just how similar it is.

Example 8.17. Using the OracleClient object to connect to an Oracle database

VB
Dim conn As OracleConnection
Dim cmd As OracleCommand

Dim cmdString As String = "Select CompanyName from Customers"
conn = New _
   OracleConnection("User Id=bevjen;Password=bevjen01;Data Source=myOracleDB")
cmd = New OracleCommand(cmdString, conn)
cmd.CommandType = CommandType.Text

conn.Open()

C#
OracleConnection conn;
OracleCommand cmd;

string cmdString = "Select CompanyName from Customers";
conn = new
   OracleConnection("User Id=bevjen;Password=bevjen01;Data Source=myOracleDB");
cmd = new OracleCommand(cmdString, conn);
cmd.CommandType = CommandType.Text;

conn.Open();

After you are connected and performing the PL-SQL commands you want, you can use the OracleDataReader object just as you would use the SqlDataReader object.

Notice that, in this section, I have made reference to the Microsoft-built Oracle data provider. Another option, and many developers consider this the better option, is to use the Oracle-built ODP.NET data provider instead. This data provider can be freely downloaded from the Oracle download page at oracle.com. You can then reference this new DLL in your project. It is now simply a matter of importing and working with System.DataAccess.OracleClient in your applications. The Oracle-built data provider contains the capability to work with the more advanced feature provided from the Oracle 10g database.

The DataList Server Control

The DataList control has been around since the beginning of ASP.NET. It is part of a series of controls that enable you to display your data (especially repeated types of data) using templates. Templates enable you to create more sophisticated layouts for your data and perform functionss that controls such as the GridView server control cannot.

Template-based controls like the DataList control require more work on your part. For instance, you have to build common tasks for yourself. You cannot rely on other data controls, which you might be used to, such as paging.

Looking at the Available Templates

The idea, when using template-based controls such as the DataList control, is that you put together specific templates to create your desired detailed layout. The DataList control has a number of templates that you can use to build your display. The available templates are defined here in the following table:

Template

Description

AlternatingItemTemplate

Works in conjunction with the ItemTemplate to provide a layout for all the odd rows within the layout. This is commonly used if you want to have a grid or layout where each row is distinguished in some way (such as having a different background color).

EditItemTemplate

Allows for a row or item to be defined on how it looks and behaves when editing.

FooterTemplate

Allows the last item in the template to be defined. If this is not defined, then no footer will be used.

HeaderTemplate

Allows the first item in the template to be defined. If this is not defined, then no header will be used.

ItemTemplate

The core template that is used to define a row or layout for each item in the display.

SelectedItemTemplate

Allows for a row or item to be defined on how it looks and behaves when selected.

SeparatorTemplate

The layout of any separator that is used between the items in the display.

Working with ItemTemplate

Although you have seven templates available to you for use with the DataList control, at a minimum, you are going to need the ItemTemplate. The following example, shown here in Listing 8-18, shows the company names from the Northwind database.

Example 8.18. Showing the company names from the Northwind database using DataList

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="DataListControl.aspx.vb"
    Inherits="DataListControl" %>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>DataList Control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:DataList ID="DataList1" runat="server" DataSourceID="SqlDataSource1">
            <ItemTemplate>
                Company Name:
                <asp:Label ID="CompanyNameLabel" runat="server"
                    Text='<%# Eval("CompanyName") %>' />
                <br />
                <br />
            </ItemTemplate>
        </asp:DataList>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
            ConnectionString="<%$ ConnectionStrings:DSN_Northwind %>"
            SelectCommand="SELECT [CompanyName] FROM [Customers]">
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

As stated, the DataList control requires, at a minimum, an ItemTemplate element where you define the page layout for each item that is encountered from the data source. In this case, all the data is pulled from the Northwind database sample using the SqlDataSource control. The SqlDataSource control pulls only the CompanyName column from the Customers table. From there, the ItemTemplate section of the DataList control defines two items within it. The first item is a static item, "Company Name:" followed by a single ASP.NET server control, the Label server control. Second, the item is then followed by a couple of standard HTML elements. The Text property of the Label control uses inline data binding (as shown in the previous chapter of this book) to bind the values that are coming out of the SqlDataSource control. If there were more than one data point coming out of the SqlDataSource control, you can still specifically grab the data point that you are interested in using by specifying the item in the Eval statement.

<asp:Label ID="CompanyNameLabel" runat="server"
 Text='<%# Eval("CompanyName") %>' />

Using the code from Listing 8-18 gives you the following results as illustrated in Figure 8-5.

Figure 8-5

Figure 8.5. Figure 8-5

If you then look at the source of the page, you can see that the DataList control uses tables by default to lay out the elements.

<table id="DataList1" cellspacing="0" border="0" style="border-collapse:collapse;">
   <tr>
      <td>
         CompanyName:
         <span id="DataList1_ctl00_CompanyNameLabel">Alfreds Futterkiste</span>
         <br />
         <br />
      </td>
   </tr><tr>
      <td>
         CompanyName:
         <span id="DataList1_ctl01_CompanyNameLabel">
          Ana Trujillo Emparedados y helados</span>
         <br />
         <br />
      </td>
   </tr>

   <!-- Code removed for clarity -->

</table>

Although this table layout is the default, you can change this so that the DataList control outputs <span> tags instead. This is done through the use of the RepeatLayout property of the DataList control. You will need to rework your DataList, as is shown in Listing 8-19.

Example 8.19. Changing the output style using RepeatLayout

<asp:DataList ID="DataList1" runat="server" DataSourceID="SqlDataSource1"
 RepeatLayout="Flow">
   <ItemTemplate>
    Company Name:
    <asp:Label ID="CompanyNameLabel" runat="server"
     Text='<%# Eval("CompanyName") %>' />
    <br />
    <br />
   </ItemTemplate>
</asp:DataList>

The possible options for the RepeatLayout property are either Table or Flow. Table is the default setting. The output you will get when looking at the source of the page when using the Flow setting is presented here:

<span id="DataList1">
   <span>
      CompanyName:
      <span id="DataList1_ctl00_CompanyNameLabel">Alfreds Futterkiste</span>
         <br />
         <br />
      </span><br />
   <span>
       CompanyName:
       <span id="DataList1_ctl01_CompanyNameLabel">
        Ana Trujillo Emparedados y helados</span>
       <br />
       <br />
   </span>

   <!-- Code removed for clarity -->

</span>

Working with Other Layout Templates

You will find that the other templates are just as easy to work with as the ItemTemplate. Listing 8-20 shows you how to add the AlternatingItemTemplate and the SeparatorTemplate to the company name display.

Example 8.20. Using both the AlternatingItemTemplate and the SeparatorTemplate

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="DataListControl.aspx.vb"
    Inherits="DataListControl" %>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>DataList Control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:DataList ID="DataList1" runat="server" DataSourceID="SqlDataSource1">
            <ItemTemplate>
                Company Name:
                <asp:Label ID="CompanyNameLabel" runat="server"
                    Text='<%# Eval("CompanyName") %>' />
                <br />
                <br />
            </ItemTemplate>
            <AlternatingItemTemplate>
                CompanyName:
                <asp:Label ID="CompanyNameLabel" runat="server"
                 BackColor="LightGray"
                 Text='<%# Eval("CompanyName") %>' />
                <br />
                <br />
            </AlternatingItemTemplate>
            <SeparatorTemplate>
                <hr />
            </SeparatorTemplate>
        </asp:DataList>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
            ConnectionString="<%$ ConnectionStrings:DSN_Northwind %>"
            SelectCommand="SELECT [CompanyName] FROM [Customers]">
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

In this case, the AlternatingItemTemplate is a repeat of the ItemTemplate, but the addition of the BackColor property to the Label control is contained within the item. The SeparatorTemplate is used between each item, whether it is from the ItemTemplate or the AlternatingItemTemplate. In this case, a simple <hr /> element is used to draw a line between each item. The output of this is shown here in Figure 8-6.

This process allows you to change how items are defined within the alternating rows and to put a separator between the elements. If you wanted just alternating row colors or an alternating style, it might not always be the best approach to use the <AlternatingItemTemplate> element, but you will find that it is better to use the <AlternatingItemStyle> element instead. This approach is presented here in Listing 8-21.

Example 8.21. Using template styles

<asp:DataList ID="DataList1" runat="server" DataSourceID="SqlDataSource1"
 BackColor="White" BorderColor="#999999" BorderStyle="Solid" BorderWidth="1px"
 CellPadding="3" ForeColor="Black" GridLines="Vertical">
   <FooterStyle BackColor="#CCCCCC" />
<AlternatingItemStyle BackColor="#CCCCCC" />
   <SelectedItemStyle BackColor="#000099" Font-Bold="True" ForeColor="White" />
   <HeaderStyle BackColor="Black" Font-Bold="True" ForeColor="White" />
   <ItemTemplate>
      CompanyName:
      <asp:Label ID="CompanyNameLabel" runat="server"
       Text='<%# Eval("CompanyName") %>' />
      <br />
      <br />
   </ItemTemplate>
</asp:DataList>
Figure 8-6

Figure 8.6. Figure 8-6

You will notice that each of the available templates also have an associated style element. Figure 8-7 shows the use of these styles.

Figure 8-7

Figure 8.7. Figure 8-7

Working with Multiple Columns

Template-based controls are better at displaying items in multiple columns than other controls, such as the GridView control. The RepeatColumns property takes care of this. The code to make use of this property is shown in Listing 8-22.

Example 8.22. Creating multiple columns using the RepeatColumns property

<asp:DataList ID="DataList1" runat="server" DataSourceID="SqlDataSource1"
 CellPadding="2" RepeatColumns="3" RepeatDirection="Horizontal">
   <ItemTemplate>
    Company Name:
    <asp:Label ID="CompanyNameLabel" runat="server"
     Text='<%# Eval("CompanyName") %>' />
    <br />
    <br />
   </ItemTemplate>
</asp:DataList>

Running this bit of code in your page produces the results shown in Figure 8-8.

Figure 8-8

Figure 8.8. Figure 8-8

The RepeatDirection property instructs the DataList control about how to lay out the items bound to the control on the Web page. Possible values include Vertical and Horizontal. The default value is Vertical. Setting it to Vertical with a RepeatColumn setting of 3 gives the following results:

Item1    Item5   Item9
Item2    Item6   Item10
Item3    Item7   Item11
Item4    Item8   Item12

When the RepeatDirection property is set to Horizontal, you get the items laid out in a horizontal fashion:

Item1    Item2   Item3
Item4    Item5   Item6
Item7    Item8   Item9
Item10   Item11  Item12

The ListView Server Control

One of the newest template-based controls is the ListView control. This is a control that is only available in the 3.5 version of the .NET Framework. This control is considered a better alternative to the DataList control. You will find that this control gives you more control over the layout and works quite nicely in Visual Studio because it provides a set of wizards to easily set up your layout with the most common options.

Looking at the Available Templates

As with the DataList control, the ListView control has a series of available templates at your disposal. Each one of these templates controls a specific aspect of the layout. The following table defines the layout options available to this control.

Template

Description

LayoutTemplate

The core template that allows you to define the structure of the entire layout. Using this layout, you can use tables, spans, or anything else you want to layout your data elements.

ItemTemplate

Defines the layout for each individual item in the data collection.

ItemSeparatorTemplate

Defines the layout of any separator that is used between items.

GroupTemplate

A group container element that can contain any number of data items.

GroupSeparatorTemplate

Defines the layout of any separator that is used between groups.

EmptyItemTemplate

Defines the layout of the empty items that might be contained within a group. For instance, if you group by ten items and the last page contains only seven items, then the last three items will use this template.

EmptyDataTemplate

Defines the layout for items that do not contain data.

SelectedItemTemplate

Allows for a row or item to be defined on how it looks and behaves when selected.

AlternatingItemTemplate

Works in conjunction with the ItemTemplate to provide a layout for all the odd rows within the layout. This is commonly used if you want to have a grid or layout where each row is distinguished in some way (such as having a different background color).

EditItemTemplate

Allows for a row or item to be defined on how it looks and behaves when editing.

InsertItemTemplate

Allows for a row or item to be defined on how it looks and behaves when performing an insert.

Next, the following sections look at using some of these in your ASP.NET page.

Using the Templates

In creating a page that makes use of the ListView control, the first step will be to create a basic page with a ListView control on it, as illustrated here in Listing 8-23.

Example 8.23. Creating the base page

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="ListViewControl.aspx.vb"
    Inherits="ListViewControl" %>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ListView Control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

        <asp:ListView ID="ListView1" runat="server" DataKeyNames="CustomerID"
            DataSourceID="SqlDataSource1">
        </asp:ListView>

        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
            ConnectionString="<%$ ConnectionStrings:DSN_Northwind %>"
            SelectCommand="SELECT * FROM [Customers] ORDER BY [CompanyName]"
            InsertCommand="INSERT INTO [Customers] ([CustomerID], [CompanyName],
               [ContactName], [ContactTitle], [Address], [City], [Region],
               [PostalCode], [Country], [Phone], [Fax]) VALUES (@CustomerID,
               @CompanyName, @ContactName, @ContactTitle, @Address, @City, @Region,
               @PostalCode, @Country, @Phone, @Fax)"
            UpdateCommand="UPDATE [Customers] SET [CompanyName] = @CompanyName,
               [ContactName] = @ContactName, [ContactTitle] = @ContactTitle,
               [Address] = @Address, [City] = @City, [Region] = @Region,
               [PostalCode] = @PostalCode, [Country] = @Country, [Phone] = @Phone,
               [Fax] = @Fax WHERE [CustomerID] = @CustomerID">
            <UpdateParameters>
                <asp:Parameter Name="CompanyName" Type="String" />
                <asp:Parameter Name="ContactName" Type="String" />
                <asp:Parameter Name="ContactTitle" Type="String" />
                <asp:Parameter Name="Address" Type="String" />
                <asp:Parameter Name="City" Type="String" />
                <asp:Parameter Name="Region" Type="String" />
                <asp:Parameter Name="PostalCode" Type="String" />
                <asp:Parameter Name="Country" Type="String" />
                <asp:Parameter Name="Phone" Type="String" />
                <asp:Parameter Name="Fax" Type="String" />
                <asp:Parameter Name="CustomerID" Type="String" />
            </UpdateParameters>
            <InsertParameters>
                <asp:Parameter Name="CustomerID" Type="String" />
                <asp:Parameter Name="CompanyName" Type="String" />
                <asp:Parameter Name="ContactName" Type="String" />
                <asp:Parameter Name="ContactTitle" Type="String" />
                <asp:Parameter Name="Address" Type="String" />
                <asp:Parameter Name="City" Type="String" />
                <asp:Parameter Name="Region" Type="String" />
                <asp:Parameter Name="PostalCode" Type="String" />
                <asp:Parameter Name="Country" Type="String" />
<asp:Parameter Name="Phone" Type="String" />
                <asp:Parameter Name="Fax" Type="String" />
            </InsertParameters>
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

In this case, you have a base ListView control and a SqlDataSource control that has been wired up to the Northwind sample database and provided Select, Update, and Insert methods. The ListView control itself is then bound to the SqlDataSource control. It provides the primary key of the table for working with the various queries through the use of the DataKeyNames property.

Creating the Layout Template

The next step is to create the layout of the overall control using the LayoutTemplate. The use of this template is illustrated in Listing 8-24.

Example 8.24. Using the LayoutTemplate element

<LayoutTemplate>
   <table runat="server">
      <tr runat="server">
         <td runat="server">
            <table ID="itemPlaceholderContainer" runat="server" border="1"
             style="background-color: #FFFFFF;border-collapse: collapse;
                    border-color: #999999;border-style:none;border-width:1px;
                    font-family: Verdana, Arial, Helvetica, sans-serif;">
              <tr runat="server" style="background-color:#DCDCDC;color: #000000;">
                 <th runat="server"></th>
                 <th runat="server">Customer ID</th>
                 <th runat="server">Company Name</th>
                 <th runat="server">Contact Name</th>
              </tr>
              <tr ID="itemPlaceholder" runat="server"></tr>
            </table>
         </td>
      </tr>
      <tr runat="server">
         <td runat="server" style="text-align: center;background-color: #CCCCCC;
          font-family: Verdana, Arial, Helvetica, sans-serif;color: #000000;">
            <asp:DataPager ID="DataPager1" runat="server">
               <Fields>
                  <asp:NextPreviousPagerField ButtonType="Button"
                   ShowFirstPageButton="True" ShowNextPageButton="False"
                   ShowPreviousPageButton="False" />
                  <asp:NumericPagerField />
                  <asp:NextPreviousPagerField ButtonType="Button"
                   ShowLastPageButton="True" ShowNextPageButton="False"
                   ShowPreviousPageButton="False" />
               </Fields>
</asp:DataPager>
         </td>
      </tr>
   </table>
</LayoutTemplate>

This layout template constructs the layout as a grid using tables to layout the items. A styled table is defined with a header in place. The most important part of laying out the template is that the container itself is defined using a control with the ID value of itemPlaceholderContainer. The element will also need to be made a server control by adding the runat property.

<table ID="itemPlaceholderContainer" runat="server" border="1"
 style="background-color: #FFFFFF;border-collapse: collapse;
 border-color: #999999;border-style:none;border-width:1px;
 font-family: Verdana, Arial, Helvetica, sans-serif;">

</table>

The placeholder for each data item needs to take the same form, but the ID of the server control you make needs to have a value of itemPlaceholder.

<table ID="itemPlaceholderContainer" runat="server" border="1"
 style="background-color: #FFFFFF;border-collapse: collapse;
 border-color: #999999;border-style:none;border-width:1px;
 font-family: Verdana, Arial, Helvetica, sans-serif;">
   <tr runat="server" style="background-color:#DCDCDC;color: #000000;">
      <th runat="server"></th>
      <th runat="server">Customer ID</th>
      <th runat="server">Company Name</th>
      <th runat="server">Contact Name</th>
   </tr>
   <tr ID="itemPlaceholder" runat="server"></tr>
</table>

It is important to keep the itemPlaceholder element within the itemPlaceholderContainer control, within the layout template. It cannot sit outside of the container.

The final part of this layout is the new DataPager server control. This new server control is part of ASP.NET 3.5.

<asp:DataPager ID="DataPager1" runat="server">
   <Fields>
      <asp:NextPreviousPagerField ButtonType="Button"
       ShowFirstPageButton="True" ShowNextPageButton="False"
       ShowPreviousPageButton="False" />
      <asp:NumericPagerField />
      <asp:NextPreviousPagerField ButtonType="Button"
       ShowLastPageButton="True" ShowNextPageButton="False"
       ShowPreviousPageButton="False" />
   </Fields>
</asp:DataPager>

The DataPager works with template-based data in allowing you to control how end users move across the pages of the data collection.

Now that the LayoutTemplate is in place, the next step is to create the ItemTemplate.

Creating the ItemTemplate

The ItemTemplate that you create is quite similar to the ItemTemplate that is part of the DataList control that was discussed earlier. In this case, however, the ItemTemplate is placed in the specific spot within the layout of the page where you defined the itemPlaceholder control to be. Listing 8-25 shows the ItemTemplate for this example.

Example 8.25. Building the ItemTemplate

<ItemTemplate>
   <tr style="background-color:#DCDCDC;color: #000000;">
      <td>
         <asp:Button ID="EditButton" runat="server"
          CommandName="Edit" Text="Edit" />
      </td>
      <td>
         <asp:Label ID="CustomerIDLabel" runat="server"
          Text='<%# Eval("CustomerID") %>' />
      </td>
      <td>
         <asp:Label ID="CompanyNameLabel" runat="server"
          Text='<%# Eval("CompanyName") %>' />
      </td>
      <td>
         <asp:Label ID="ContactNameLabel" runat="server"
          Text='<%# Eval("ContactName") %>' />
      </td>
   </tr>
</ItemTemplate>

Creating the EditItemTemplate

The EditItemTemplate is the area that shows up when you decide to edit the data item (in this case, a row of data). Listing 8-26 shows the EditItemTemplate in use.

Example 8.26. Building the EditItemTemplate

<EditItemTemplate>
   <tr style="background-color:#008A8C;color: #FFFFFF;">
      <td>
         <asp:Button ID="UpdateButton" runat="server"
          CommandName="Update" Text="Update" />
         <asp:Button ID="CancelButton" runat="server"
          CommandName="Cancel" Text="Cancel" />
      </td>
      <td>
         <asp:Label ID="CustomerIDLabel1" runat="server"
          Text='<%# Eval("CustomerID") %>' />
      </td>
<td>
         <asp:TextBox ID="CompanyNameTextBox" runat="server"
          Text='<%# Bind("CompanyName") %>' />
      </td>
      <td>
         <asp:TextBox ID="ContactNameTextBox" runat="server"
          Text='<%# Bind("ContactName") %>' />
      </td>
   </tr>
</EditItemTemplate>

In this case, the EditItemTemplate, when shown, displays an Update and Cancel button to manipulate the editing options. When editing, the values are placed within text boxes and the values are then updated into the database through the Updatecommand.

Creating the EmptyItemTemplate

If there are no values in the database, then you should prepare to gracefully show something in your layout. The EmptyItemTemplate is used in Listing 8-27 to perform that operation.

Example 8.27. Building the EmptyItemTemplate

<EmptyDataTemplate>
   <table runat="server"
    style="background-color: #FFFFFF;border-collapse: collapse;
        border-color: #999999;border-style:none;border-width:1px;">
      <tr>
         <td>No data was returned.</td>
      </tr>
   </table>
</EmptyDataTemplate>

Creating the InsertItemTemplate

The last section looked at here is the InsertItemTemplate. This section allows you to define how a form should be laid out for inserting data, similar to that used in the ItemTemplate, into the data store.

Listing 8-28 shows an example of the InsertItemTemplate.

Example 8.28. Building the InsertItemTemplate

<InsertItemTemplate>
   <tr style="">
      <td>
         <asp:Button ID="InsertButton" runat="server" CommandName="Insert"
          Text="Insert" />
         <asp:Button ID="CancelButton" runat="server" CommandName="Cancel"
          Text="Clear" />
      </td>
      <td>
         <asp:TextBox ID="CustomerIDTextBox" runat="server"
          Text='<%# Bind("CustomerID") %>' />
      </td>
<td>
         <asp:TextBox ID="CompanyNameTextBox" runat="server"
          Text='<%# Bind("CompanyName") %>' />
      </td>
      <td>
         <asp:TextBox ID="ContactNameTextBox" runat="server"
          Text='<%# Bind("ContactName") %>' />
      </td>
   </tr>
</InsertItemTemplate>

The Results

After you have created an additional AlternatingItemTemplate that is the same as the ItemTemplate (but styled differently), you can then run the page. Then you will be presented with your own custom grid. An example is presented in Figure 8-9.

Figure 8-9

Figure 8.9. Figure 8-9

From this figure, you can see that all your defined elements are in place. The header is defined through the use of the LayoutTemplate. The items in the grid are defined through the use of the ItemTemplate. The AlternatingItemTemplate, the insert form, is defined through the use of the InsertTemplate. The page navigation is defined by the new DataPager server control. Again, the DataPager control is defined within the LayoutTemplate itself.

Editing items in this template is as simple as clicking on the Edit button. This will change the view to the EditTemplate for the selected item, as illustrated in Figure 8-10.

Once you enter the edit mode here, you can change any of the values within the text boxes and then click the Update button to update the data to the new values. You can also cancel out of the operation by clicking the Cancel button.

Inserting data is as simple as filling out the form and clicking on the Insert button, as illustrated in Figure 8-11.

Figure 8-10

Figure 8.10. Figure 8-10

Figure 8-11

Figure 8.11. Figure 8-11

Although this example shows a grid as the output of the new ListView control, you can also structure it so that your data items are presented in any fashion you want (such as bulleted lists).

Using Visual Studio for ADO.NET Tasks

Earlier, this chapter covered how to construct a DataSet and how to fill it with data using the DataAdapter. Although you can always build this construction yourself, you also have the option of building data access into your ASP.NET applications using some of the wizards available from Visual Studio 2008.

The following example, which is a little bit of a lengthy one, shows you how to build an ASP.NET page that displays the results from a DataSet that gets its data from two separate tables. You will discover several different wizards in Visual Studio that you can work with when using ADO.NET.

Creating a Connection to the Data Source

As in code, one of the first things you do when working with data is make a connection to the data source. Visual Studio provides a visual way to make connections to your data stores. In this case, you will want to make a connection to the Northwind database in SQL Server.

When you open the Server Explorer, you will notice a section for data connections (see Figure 8-12).

Figure 8-12

Figure 8.12. Figure 8-12

The steps to create a data connection to the Northwind database in SQL Server are straightforward. Right-click on Data Connections and choose Add Connection. You are presented with the Data Link Properties dialog box. This dialog box, by default, asks for a connection to SQL Server. If you are going to connect to a different source, such as Microsoft Access, simply click on the Provider tab and change the provider.

Figure 8-13 shows the Add Connection dialog box and the settings that you need in order to connect to your local SQL Server Express Edition.

If you are connecting to a SQL Server that resides on your local host, you want to put a period (.) in the box that asks you to select or enter a server name. If you are working from a local SQL Server Express Edition file in your project (such as what is shown here in Figure 8-13), then you are going to want to use your server name with SQLEXPRESS. Put in your login credentials for SQL Server and then select the database that you wish to make the connection to by using the drop-down list. The other option, if you are using a SQL Server Express Edition file, is to select the physical database file by using the Attach a Database File option.

Figure 8-13

Figure 8.13. Figure 8-13

From this dialog box, you can also test the connection to ensure that everything works properly. If everything is in place, you get a confirmation stating such. Clicking OK will then cause a connection to appear in the Solution Explorer.

Expanding this connection, you find a way to access the data source just as you would by using the SQL Server Enterprise Manager (see Figure 8-14).

From here, you can work with the database and view information about all the tables and fields that are contained within the database. More specifically, you can view and work with Database Diagrams, Tables, Views, Stored Procedures, and Functions.

After you have run through this wizard, you have a connection to the Northwind database that can be used by any components that you place on any component designer that you might be working with in your application.

Figure 8-14

Figure 8.14. Figure 8-14

Working with a Dataset Designer

The next step is to create a typed DataSet object in your project that pulls its data from the Northwind database. First you need to make sure that your application has an App_Code folder within the solution. Right-clicking on the folder will allow you to add a new item to the folder. From the provided dialog box, add a DataSet called CustomerOrders.xsd. You will then be presented with the message shown in Figure 8-15.

This page is referred to as the Dataset Designer. This is the design surface for any nonvisual components that you incorporate within your DataSet object. Just as you can drag and drop controls onto a design surface for any Windows Forms or Web Forms application, the Dataset Designer enables you to drag and drop components onto this surface.

A component does not appear visually in your applications, but a visual representation of the component sits on the design surface. Highlighting the component allows you to modify its settings and properties in the Properties window.

Figure 8-15

Figure 8.15. Figure 8-15

What can you drag and drop onto this surface? In the following examples, you see how to work with TableAdapter and DataTable objects on this design surface. If you open up the Toolbox window, and click the DataSet tab, you see some additional components that can be used on this design surface.

The goal of this example is to return a DataSet to the end user through an XML Web service. To accomplish this, you have to incorporate a DataAdapter to extract the data from the data source and to populate the DataSet before passing it on.

This example uses the Northwind database, and the first step you need to take is to drag and drop a TableAdapter onto the Dataset design surface. Dragging and dropping a TableAdapter onto your design surface causes a wizard to appear, as shown in Figure 8-16.

Because you want this DataSet to contain two DataTables — one for the Customers table and another for the Orders table — you have to go through this process twice.

It is important to note that the job of the TableAdapter object is to make the connection to the specified table as well as to perform all the select, update, insert, and delete commands that are required. For this example, you simply want the TableAdapter to make the select call and then later to update any changes that are made back to the SQL Server.

Figure 8-16

Figure 8.16. Figure 8-16

As you work through the wizard, you come to a screen that asks how you want to query the database (see Figure 8-17). You have three options: using SQL statements, using stored procedures that have already been created, or building brand-new stored procedures directly from this wizard.

Figure 8-17

Figure 8.17. Figure 8-17

For this example, choose Use SQL statements. Selecting this option brings you to a text box where you can write your own SQL statement if you wish.

The great thing about this process is that, after you create a SQL select command, the TableAdapter wizard also creates the associated insert, update, and delete commands for you. You also have the option of building your queries using the Query Builder. This enables you to graphically design the query yourself. If this option is selected, you can choose from a list of tables in the Northwind database. For the first TableAdapter, choose Customers. For the second TableAdapter choose Orders. You make your selection by clicking the Add button and then closing the dialog box (see Figure 8-18).

Figure 8-18

Figure 8.18. Figure 8-18

After you close the Add Table dialog box, you see a visual representation of the table that you selected in the Query Builder dialog box (see Figure 8-19). You can then select some or all the fields to be returned from the query. For this example, you want everything returned from both the Customers and the Orders table, so select the first check box with the asterisk (*). Notice that the query listed in this dialog box now says SELECT * FROM Customers. After the word "Customers," add text to the query so that it looks like the following:

SELECT Customers.* FROM Customers WHERE (CustomerID LIKE @Customer)

With this query, you specify that you want to return the customer information when the CustomerID fits the parameter that you pass into the query from your code (using @Customer).

After your query is in place, simply click OK and then click the Next button to have not only the select query, but also the insert, update, and delete queries generated for you.

Figure 8-20 shows you the final page after all the queries have been generated.

After you reach this point, you can either click the Previous button to return to one of the prior steps in order to change a setting or the query itself, or you can click the Finish button to apply everything to your TableAdapter. After you are finished using the wizard, notice there is a visual representation of the CustomersTableAdapter that you just created (see Figure 8-21). Along with that is a DataTable object for the Customers table. The TableAdapter and the DataTable objects that are shown on the design surface are also labeled with their IDs. Therefore, in your code, you can address this TableAdapter that you just built by referring to it as CustomerOrdersTableAdapters.CustomersTableAdapter. The second TableAdapter that queries the Orders table is then shown and referred to as CustomerOrdersTableAdapters.OrdersTableAdapter.

Figure 8-19

Figure 8.19. Figure 8-19

Figure 8-20

Figure 8.20. Figure 8-20

After you have the two DataAdapters in place, you will also notice that there is an automatic relation put into place for you. This is represented by the line between the two items on the page. Right-clicking on the relation, you can edit the relation with the Relation dialog box (see Figure 8-22).

Figure 8-21

Figure 8.21. Figure 8-21

Figure 8-22

Figure 8.22. Figure 8-22

In the end, Visual Studio has taken care of a lot for you. Again, this is not the only way to complete all these tasks.

Using the CustomerOrders DataSet

Now comes the fun part — building the ASP.NET that will use all the items that were just created! The goal is to allow the end user to send in a request that contains just the CustomerID. In return, he will get back a complete DataSet containing not only the customer information, but also all the relevant order information. Listing 8-29 shows you the code to build all this functionality. You need only a single method in addition to the Page_Load: the GetCustomerOrders() method. The page should be laid out as is shown here in Figure 8-23.

Figure 8-23

Figure 8.23. Figure 8-23

The page that you create should contain a single TextBox control, a Button control, and two GridView controls (GridView1 and GridView2). The code for the page is shown in Listing 8-29.

Example 8.29. The .aspx page

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb"
    Inherits="_Default" %>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>CustomerOrders</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
Enter Customer ID:
        <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
        <br />

        <asp:Button ID="Button1" runat="server" Text="Select" />
        <br />
        <br />
        <asp:GridView ID="GridView1" runat="server" BackColor="White"
            BorderColor="#999999" BorderStyle="Solid" BorderWidth="1px"
            CellPadding="3" ForeColor="Black" GridLines="Vertical">
            <FooterStyle BackColor="#CCCCCC" />
            <PagerStyle BackColor="#999999" ForeColor="Black"
             HorizontalAlign="Center" />
            <SelectedRowStyle BackColor="#000099" Font-Bold="True"
             ForeColor="White" />
            <HeaderStyle BackColor="Black" Font-Bold="True" ForeColor="White" />
            <AlternatingRowStyle BackColor="#CCCCCC" />
        </asp:GridView>
        <br />
        <asp:GridView ID="GridView2" runat="server" BackColor="White"
            BorderColor="#999999" BorderStyle="Solid" BorderWidth="1px"
            CellPadding="3" ForeColor="Black" GridLines="Vertical">
            <FooterStyle BackColor="#CCCCCC" />
            <PagerStyle BackColor="#999999" ForeColor="Black"
             HorizontalAlign="Center" />
            <SelectedRowStyle BackColor="#000099" Font-Bold="True"
             ForeColor="White" />
            <HeaderStyle BackColor="Black" Font-Bold="True" ForeColor="White" />
            <AlternatingRowStyle BackColor="#CCCCCC" />
        </asp:GridView>
    </div>
    </form>
</body>
</html>

The code-behind for the page is presented in Listing 8-30.

Example 8.30. The code-behind for the CustomerOrders page

VB
Imports System

Partial Class _Default
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
     Handles Me.Load
If Page.IsPostBack Then
            GetCustomerOrders(TextBox1.Text)
        End If
    End Sub

    Protected Sub GetCustomerOrders(ByVal custId As String)
        Dim myDataSet As New CustomerOrders
        Dim custDA As New CustomerOrdersTableAdapters.CustomersTableAdapter
        Dim ordersDA As New CustomerOrdersTableAdapters.OrdersTableAdapter

        custDA.Fill(myDataSet.Customers, custId)
        ordersDA.Fill(myDataSet.Orders, custId)

        myDataSet.Customers(0).Phone = "NOT AVAILABLE"
        myDataSet.Customers(0).Fax = "NOT AVAILABLE"

        GridView1.DataSource = myDataSet.Tables("Customers")
        GridView1.DataBind()

        GridView2.DataSource = myDataSet.Tables("Orders")
        GridView2.DataBind()
    End Sub
End Class

C#
using System;
using CustomerOrdersTableAdapters;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Page.IsPostBack)
        {
            GetCustomerOrders(TextBox1.Text);
        }
    }

    protected void GetCustomerOrders(string custId)
    {
        CustomerOrders myDataSet = new CustomerOrders();
        CustomersTableAdapter custDA = new CustomersTableAdapter();
        OrdersTableAdapter ordersDA = new OrdersTableAdapter();

        custDA.Fill(myDataSet.Customers, custId);
        ordersDA.Fill(myDataSet.Orders, custId);

        myDataSet.Customers[0].Phone = "NOT AVAILABLE";
        myDataSet.Customers[0].Fax = "NOT AVAILABLE";

        GridView1.DataSource = myDataSet.Tables["Customers"];
GridView1.DataBind();

        GridView2.DataSource = myDataSet.Tables["Orders"];
        GridView2.DataBind();
    }
}

Now there is not much code here. One of the first things done in the method is to create an instance of the typed DataSet. In the next two lines of code, the custDA and the ordersDA objects are used. In this case, the only accepted parameter, custId, is being set for both the DataAdapters. After this parameter is passed to the TableAdapter, this TableAdapter queries the database based upon the select query that you programmed into it earlier using the TableAdapter wizard.

After the query, the TableAdapter is instructed to fill the instance of the DataSet. Before the DataSet is returned to the consumer, you can change how the result is output to the client. If you are passing customer information, you may want to exclude some of the information. Because the DataSet is a typed DataSet, you have programmatic access to the tables. In this example, the code specifies that in the DataSet, in the Customers table, in the first row (remember it is zero-based), make the value of the Phone and Fax fields equal to NOT AVAILABLE.

By compiling and running the ASP.NET page, you are able to test it from the test page using the CustomerID of ALFKI (the first record of the Customers table in the Northwind database). The results are returned to you in the browser (see Figure 8-24).

Figure 8-24

Figure 8.24. Figure 8-24

Asynchronous Command Execution

When you process data using ADO or previous versions of ADO.NET, each command is executed sequentially. The code waits for each command to complete before the next one is processed. When you use a single database, the sequential processing enables you to reuse the same connection object for all commands. However, with the introduction of MARS, you can now use a single connection for multiple, concurrent database access. Since the introduction of ADO.NET 2.0, ADO.NET has enabled users to process database commands asynchronously. This enables you to not only use the same connection, but also to use it in a parallel manner. The real advantage of asynchronous processing becomes apparent when you are accessing multiple data sources — especially when the data access queries across these databases aren't dependent on each other. You can now open a connection to the database in an asynchronous manner. When you are working with multiple databases, you can now open connections to them in a parallel fashion as well.

To make this work, be sure to add Asynchronous Processing = true; to your connection string.

Asynchronous Methods of the SqlCommand Class

The SqlCommand class provides a few additional methods that facilitate executing commands asynchronously. These new methods are summarized in the following table.

Method

Description

BeginExecuteNonQuery()

This method expects a query that doesn't return any results and starts it asynchronously. The return value is a reference to an object of the SqlAsyncResult class that implements the IAsyncResult interface. The returned object can be used to monitor the process as it runs and when it is completed.

BeginExecuteNonQuery (callback, stateObject )

This overloaded method also starts the process asynchronously, and it expects to receive an object of the AsynchCallback instance. The callback method is called after the process is finished running so that you can proceed with other tasks. The second parameter receives any custom-defined object. This object is passed to the callback automatically. It provides an excellent mechanism for passing parameters to the callback method. The callback method can retrieve the custom-defined state object by using the AsyncState property of the IAsyncResult interface.

EndExecuteNonQuery (asyncResult)

This method is used to access the results from the BeginExecuteNonQuery method. When calling this method, you are required to pass the same SqlAsyncResult object that you received when you called the BeginExecuteNonQuery method. This method returns an integer value containing the number of rows affected.

BeginExecuteReader

This method expects a query that returns a result set and starts it asynchronously. The return value is a reference to an object of SqlAsyncResult class that implements IAsyncResult interface. The returned object can be used to monitor the process as it runs and as it is completed.

BeginExecuteReader (commandBehavior)

This overloaded method works the same way as the one described previously. It also takes a parameter containing a command behavior enumeration just like the synchronous ExecuteReader method.

BeginExecuteReader (callback, stateObject)

This overloaded method starts the asynchronous process and it expects to receive an object of AsyncCallback instance. The callback method is called after the process finishes running so that you can proceed with other tasks. The second parameter receives any custom-defined object. This object is passed to the callback automatically. It provides an excellent mechanism for passing parameters to the callback method. The callback method can retrieve the custom-defined state object by using the AsyncState property of the IAsyncResult interface.

BeginExecuteReader (callback, stateObject, commandBehavior)

This overloaded method takes an instance of the AsyncCallback class and uses it to fire a callback method when the process has finished running. The second parameter receives a custom object to be passed to the callback method, and the third parameter uses the command behavior enumeration in the same way as the synchronous ExecuteReader method.

EndExecuteReader

This method is used to access the results from the BeginExecuteReader method. When calling this method, you are required to pass the same SqlAsyncResult object that you receive when you called the BeginExecuteReader method. This method returns a SqlDataReader object containing the result of the SQL query.

BeginExecuteXmlReader

This method expects a query that returns the result set as XML. The return value is a reference to an object of SqlAsyncResult class that implements IAsyncResult interface. The returned object can be used to monitor the process as it runs and as it is completed.

BeginExecuteXmlReader (callback, stateObject)

This overloaded method starts the asynchronous process, and it expects to receive an object of AsyncCallback instance. The callback method is called after the process has finished running so that you can proceed with other tasks. The second parameter receives any custom-defined object. This object is passed to the callback automatically. It provides an excellent mechanism for passing parameters to the callback method. The callback method can retrieve the custom-defined state object by using the AsyncState property of the IAsyncResult interface.

EndExecuteXmlReader

This method is used to access the results from the BeginExecuteXmlReader method. When calling this method, you are required to pass the same SqlAsyncResult object that you received when you called the BeginExecuteXmlReader method. This method returns an XML Reader object containing the result of the SQL query.

IAsyncResult Interface

All the asynchronous methods for the SqlCommand class return a reference to an object that exposes the IAsyncResult interface. The properties of this interface are shown in the following table.

Property

Description

AsyncState

This read-only property returns an object that describes the state of the process.

AsyncWaitHandle

This read-only property returns an instance of WaitHandle that can be used to set the time out, test whether the process has completed, and force the code to wait for completion.

CompletedSynchronously

This read-only property returns a Boolean value that indicates whether the process was executed synchronously.

IsCompleted

This read-only property returns a Boolean value indicating whether the process has completed.

AsyncCallback

Some of the asynchronous methods of the SqlCommand class receive an instance of the AsyncCallback class. This class is not specific to ADO.NET and is used by many objects in the .NET Framework. It is used to specify those methods that you want to execute after the asynchronous process has finished running. This class uses its constructor to receive the address of the method that you want to use for callback purposes.

WaitHandle Class

This class is an abstract class used for multiple purposes such as causing the execution to wait for any or all asynchronous processes to finish. To process more than one database command asynchronously, you can simply create an array containing wait handles for each asynchronous process. Using the static methods of the WaitHandle class, you can cause the execution to wait for either any or all wait handles in the array to finish processing.

The WaitHandle class exposes a few methods, as shown in the following table.

Method

Description

WaitOne

This method waits for a single asynchronous process to complete or time out. It returns a Boolean value containing True if the process completed successfully and False if it timed out.

WaitOne (milliseconds, exitContext)

This overloaded method receives an integer value as the first parameter. This value represents the time out in milliseconds. The second parameter receives a Boolean value specifying whether the method requires asynchronous context and should be set to False for asynchronous processing.

WaitOne (timeSpan, exitContext)

This overloaded method receives a TimeSpan object to represent the time-out value. The second parameter receives a Boolean value specifying whether the method requires asynchronous context and should be set to False for Asynchronous processing.

WaitAny (waitHandles)

This is a static method used if you are managing more than one WaitHandle in the form of an array. Using this method causes the execution to wait for any of the asynchronous processes that have been started and whose wait handles are in the array being passed to it. The WaitAny method must be called repeatedly — once for each WaitHandle you want to process.

WaitAny (waitHandles, milliseconds, exitContext)

This overloaded method receives the time-out value in the form of milliseconds and a Boolean value specifying whether the method requires asynchronous context. It should be set to False for asynchronous processing.

WaitAny (waitHandles, timeSpan, exitContext

This overloaded method receives the time-out value in the form of a TimeSpan object. The second parameter receives a Boolean value specifying whether the method requires asynchronous context. It should be set to False for asynchronous processing.

WaitAll (waitHandles)

This is a static method and is used to wait for all asynchronous processes to finish running.

WaitAll (waitHandles, milliseconds, exitContext)

This overloaded method receives the time-out value in the form of milliseconds and a Boolean value specifying whether the method requires asynchronous context. It should be set to False for asynchronous processing.

WaitAll (waitHandles, timeSpan, exitContext)

This overloaded method receives the time-out value in the form of TimeSpan object. The second parameter receives a Boolean value specifying whether the method requires asynchronous context. It should be set to False for asynchronous processing.

Close ( )

This method releases all wait handles and reclaims their resources.

Now that you understand asynchronous methods added to the SqlCommand and how to properly interact with them, you can write some code to see the asynchronous processing in action.

Approaches of Asynchronous Processing in ADO.NET

You can process asynchronous commands in three distinct ways. One approach is to start the asynchronous process and start polling the IAsyncResult object to see when the process has finished. The second approach is to provide a callback method while starting the asynchronous process. This approach enables you to perform other tasks in parallel. When the asynchronous process finishes, it fires the callback method that cleans up after the process and notifies other parts of the program that the asynchronous process has finished. The third and most elegant method is to associate a wait handle with the asynchronous process. Using this approach, you can start all the asynchronous processing you want and then wait for all or any of them to finish so that you can process them accordingly.

The Poll Approach

The code shown in Listing 8-31 creates an inline SQL statement to retrieve the top five records from the Orders table from the Northwind database. It starts the asynchronous process by calling the BeginExecuteReader. After the asynchronous process has started, it uses a while loop to wait for the process to finish. While waiting, the main thread sleeps for 10 milliseconds after checking the status of the asynchronous process. After the process has finished, it retrieves the result using the EndExecuteReader method.

Example 8.31. The Poll approach to working with asynchronous commands

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim DBCon As SqlConnection
        Dim Command As SqlCommand = New SqlCommand()
        Dim OrdersReader As SqlDataReader
        Dim ASyncResult As IAsyncResult

        DBCon = New SqlConnection()
        DBCon.ConnectionString = _
           ConfigurationManager.ConnectionStrings("DSN_NorthWind").ConnectionString

        Command.CommandText = _
                "SELECT TOP 5 Customers.CompanyName, Customers.ContactName, " & _
                "Orders.OrderID, Orders.OrderDate, " & _
                "Orders.RequiredDate, Orders.ShippedDate " & _
                "FROM Orders, Customers " & _
                "WHERE Orders.CustomerID = Customers.CustomerID " & _
                "ORDER BY Customers.CompanyName, Customers.ContactName"
        Command.CommandType = CommandType.Text
        Command.Connection = DBCon

        DBCon.Open()

       ' Starting the asynchronous processing
        ASyncResult = Command.BeginExecuteReader()

       ' This loop with keep the main thread waiting until the
       ' asynchronous process is finished
       While Not ASyncResult.IsCompleted
           ' Sleeping current thread for 10 milliseconds
            System.Threading.Thread.Sleep(10)
       End While

       ' Retrieving result from the asynchronous process
       OrdersReader = Command.EndExecuteReader(ASyncResult)

       ' Displaying result on the screen
       gvOrders.DataSource = OrdersReader
       gvOrders.DataBind()
' Closing connection
        DBCon.Close()
    End Sub
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>The Poll Approach</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:GridView ID="gvOrders" runat="server"
     AutoGenerateColumns="False" Width="100%">
       <Columns>
          <asp:BoundField HeaderText="Company Name"
           DataField="CompanyName"></asp:BoundField>
          <asp:BoundField HeaderText="Contact Name"
           DataField="ContactName"></asp:BoundField>
          <asp:BoundField HeaderText="Order Date"
           DataField="orderdate" DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Required Date" DataField="requireddate"
           DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Shipped Date" DataField="shippeddate"
           DataFormatString="{0:d}"></asp:BoundField>
       </Columns>
    </asp:GridView>
    </div>
    </form>
</body>
</html>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        SqlConnection DBCon;
        SqlCommand Command = new SqlCommand();
        SqlDataReader OrdersReader;
        IAsyncResult ASyncResult;

        DBCon = new SqlConnection();
        DBCon.ConnectionString =
           ConfigurationManager.ConnectionStrings["DSN_NorthWind"].ConnectionString;

        Command.CommandText =
                "SELECT TOP 5 Customers.CompanyName, Customers.ContactName, " +
                "Orders.OrderID, Orders.OrderDate, " +
                "Orders.RequiredDate, Orders.ShippedDate " +
"FROM Orders, Customers " +
                "WHERE Orders.CustomerID = Customers.CustomerID " +
                "ORDER BY Customers.CompanyName, Customers.ContactName";

        Command.CommandType = CommandType.Text;
        Command.Connection = DBCon;

        DBCon.Open();

        // Starting the asynchronous processing
        ASyncResult = Command.BeginExecuteReader();

        // This loop with keep the main thread waiting until the
        // asynchronous process is finished
        while (!ASyncResult.IsCompleted)
        {
            // Sleeping current thread for 10 milliseconds
            System.Threading.Thread.Sleep(10);
        }

        // Retrieving result from the asynchronous process
        OrdersReader = Command.EndExecuteReader(ASyncResult);

        // Displaying result on the screen
        gvOrders.DataSource = OrdersReader;
        gvOrders.DataBind();

        // Closing connection
        DBCon.Close();
    }
</script>

If you set a break point at the while loop, you will be able to see that the code execution continues after calling the BeginExecuteReader method. The code then continues to loop until the asynchronous execution has finished.

The Wait Approach

The most elegant of the three approaches is neither the poll approach nor the callback approach. The approach that provides the highest level of flexibility, efficiency, and (admittedly) a bit more complexity is the wait approach. Using this approach, you can write code that starts multiple asynchronous processes and waits for any or all the processes to finish running. This approach allows you to wait for only those processes that are dependent on each other and to proceed with the ones that don't. This approach, by its design, requires you to think about asynchronous processes in great detail. You must pick a good candidate for running in parallel and, most importantly, determine how different processes depend on each other. The complexity of this approach requires you to understand its details and design the code accordingly. The end result is, typically, a very elegant code design that makes the best use of synchronous and asynchronous processing models.

The code shown in Listing 8-32 uses the WaitOne method of the WaitHandle class. This method causes the program execution to wait until the asynchronous process has finished running.

Example 8.32. The wait approach to handling a single asynchronous process

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim DBCon As SqlConnection
        Dim Command As SqlCommand = New SqlCommand()
        Dim OrdersReader As SqlDataReader
        Dim ASyncResult As IAsyncResult
        Dim WHandle As Threading.WaitHandle

        DBCon = New SqlConnection()
        DBCon.ConnectionString = _
           ConfigurationManager.ConnectionStrings("DSN_NorthWind").ConnectionString


        Command.CommandText = _
                "SELECT TOP 5 Customers.CompanyName, Customers.ContactName, " & _
                "Orders.OrderID, Orders.OrderDate, " & _
                "Orders.RequiredDate, Orders.ShippedDate " & _
                "FROM Orders, Customers " & _
                "WHERE Orders.CustomerID = Customers.CustomerID " & _
                "ORDER BY Customers.CompanyName, Customers.ContactName"

        Command.CommandType = CommandType.Text
        Command.Connection = DBCon

        DBCon.Open()

       ' Starting the asynchronous processing
       ASyncResult = Command.BeginExecuteReader()

       WHandle = ASyncResult.AsyncWaitHandle

       If WHandle.WaitOne = True Then
           ' Retrieving result from the asynchronous process
           OrdersReader = Command.EndExecuteReader(ASyncResult)

           ' Displaying result on the screen
           gvOrders.DataSource = OrdersReader
           gvOrders.DataBind()

           ' Closing connection
           DBCon.Close()
       Else
           ' Asynchronous process has timed out. Handle this
           ' situation here.
       End If
End Sub
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>The Wait Approach</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:GridView ID="gvOrders" runat="server"
     AutoGenerateColumns="False" Width="100%">
       <Columns>
          <asp:BoundField HeaderText="Company Name"
           DataField="CompanyName"></asp:BoundField>
          <asp:BoundField HeaderText="Contact Name"
           DataField="ContactName"></asp:BoundField>
          <asp:BoundField HeaderText="Order Date"
           DataField="orderdate" DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Required Date" DataField="requireddate"
           DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Shipped Date" DataField="shippeddate"
           DataFormatString="{0:d}"></asp:BoundField>
       </Columns>
    </asp:GridView>
    </div>
    </form>
</body>
</html>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        SqlConnection DBCon;
        SqlCommand Command = new SqlCommand();
        SqlDataReader OrdersReader;
        IAsyncResult ASyncResult;
        System.Threading.WaitHandle WHandle;

        DBCon = new SqlConnection();
        DBCon.ConnectionString =
           ConfigurationManager.ConnectionStrings["DSN_NorthWind"].ConnectionString;

        Command.CommandText =
                "SELECT TOP 5 Customers.CompanyName, Customers.ContactName, " +
                "Orders.OrderID, Orders.OrderDate, " +
                "Orders.RequiredDate, Orders.ShippedDate " +
                "FROM Orders, Customers " +
"WHERE Orders.CustomerID = Customers.CustomerID " +
                "ORDER BY Customers.CompanyName, Customers.ContactName";

        Command.CommandType = CommandType.Text;
        Command.Connection = DBCon;

        DBCon.Open();

        // Starting the asynchronous processing
        ASyncResult = Command.BeginExecuteReader();

        WHandle = ASyncResult.AsyncWaitHandle;

        if (WHandle.WaitOne() == true)
        {
            // Retrieving result from the asynchronous process
            OrdersReader = Command.EndExecuteReader(ASyncResult);

            // Displaying result on the screen
            gvOrders.DataSource = OrdersReader;
            gvOrders.DataBind();

            // Closing connection
            DBCon.Close();
        }
        else
        {
            // Asynchronous process has timed out. Handle this
            // situation here.
        }
    }
</script>

If you set a break point and step through this code, you will notice that the program execution stops at the WHandle.WaitOne method call. The program automatically resumes when the asynchronous commands finishes its execution.

Using Multiple Wait Handles

The real power of the wait approach doesn't become apparent until you start multiple asynchronous processes. The code shown in Listing 8-33 starts two asynchronous processes. One process queries a database to get information about a specific customer and runs another query to retrieve all orders submitted by that the same customer. The code example shown in this listing creates two separate Command objects, Data Reader objects, and wait handles. However, it uses the same connection object for both queries to demonstrate how well Multiple Active Result Set (MARS) supports work in conjunction with the asynchronous processing. For this to work, you are going to have to add MultipleActiveResultSets=True to your connection string.

Example 8.33. Use of multiple wait handles in conjunction with MARS

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim DBCon As SqlConnection
        Dim OrdersCommand As SqlCommand = New SqlCommand()
        Dim CustCommand As SqlCommand = New SqlCommand()
        Dim OrdersReader As SqlDataReader
        Dim CustReader As SqlDataReader
        Dim OrdersASyncResult As IAsyncResult
        Dim CustAsyncResult As IAsyncResult

        Dim WHandles(1) As System.Threading.WaitHandle
        Dim OrdersWHandle As System.Threading.WaitHandle
        Dim CustWHandle As System.Threading.WaitHandle

        DBCon = New SqlConnection()
        DBCon.ConnectionString = _
           ConfigurationManager.ConnectionStrings("DSN_NorthWind").ConnectionString

        CustCommand.CommandText = _
           "SELECT * FROM Customers WHERE CompanyName = 'Alfreds Futterkiste'"

        CustCommand.CommandType = CommandType.Text
        CustCommand.Connection = DBCon

        ' Selecting all orders for a specific customer
        OrdersCommand.CommandText = _
                "SELECT Customers.CompanyName, Customers.ContactName, " & _
                "Orders.OrderID, Orders.OrderDate, " & _
                "Orders.RequiredDate, Orders.ShippedDate " & _
                "FROM Orders, Customers " & _
                "WHERE Orders.CustomerID = Customers.CustomerID " & _
                "AND Customers.CompanyName = 'Alfreds Futterkiste' " & _
                "ORDER BY Customers.CompanyName, Customers.ContactName"

        OrdersCommand.CommandType = CommandType.Text
        OrdersCommand.Connection = DBCon

        DBCon.Open()

        ' Retrieving customer information asynchronously
        CustAsyncResult = CustCommand.BeginExecuteReader()

        ' Retrieving orders list asynchronously
        OrdersASyncResult = OrdersCommand.BeginExecuteReader()

        CustWHandle = CustAsyncResult.AsyncWaitHandle
        OrdersWHandle = OrdersASyncResult.AsyncWaitHandle

        ' Filling Wait Handles array with the two wait handles we
        ' are going to use in this code
        WHandles(0) = CustWHandle
WHandles(1) = OrdersWHandle

        System.Threading.WaitHandle.WaitAll(WHandles)

        CustReader = CustCommand.EndExecuteReader(CustAsyncResult)

        OrdersReader = OrdersCommand.EndExecuteReader(OrdersASyncResult)

        gvCustomers.DataSource = CustReader
        gvCustomers.DataBind()

        gvOrders.DataSource = OrdersReader
        gvOrders.DataBind()

        DBCon.Close()
    End Sub
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Wait All Approach</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:GridView ID="gvCustomers" Width="100%" runat="server"></asp:GridView>
    <br /><br />
    <asp:GridView ID="gvOrders" Width="100%" AutoGenerateColumns="False"
     runat="server">
       <Columns>
          <asp:BoundField HeaderText="Company Name"
           DataField="CompanyName"></asp:BoundField>
          <asp:BoundField HeaderText="Contact Name"
           DataField="ContactName"></asp:BoundField>
          <asp:BoundField HeaderText="Order Date" DataField="orderdate"
           DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Required Date" DataField="requireddate"
           DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Shipped Date" DataField="shippeddate"
           DataFormatString="{0:d}"></asp:BoundField>
       </Columns>
    </asp:GridView>
    </div>
    </form>
</body>
</html>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
protected void Page_Load(object sender, EventArgs e)
    {
        SqlConnection DBCon;
        SqlCommand OrdersCommand = new SqlCommand();
        SqlCommand CustCommand = new SqlCommand();
        SqlDataReader OrdersReader;
        SqlDataReader CustReader;
        IAsyncResult OrdersASyncResult;
        IAsyncResult CustAsyncResult;

        System.Threading.WaitHandle[] WHandles = new
           System.Threading.WaitHandle[2];
        System.Threading.WaitHandle OrdersWHandle;
        System.Threading.WaitHandle CustWHandle;

        DBCon = new SqlConnection();
        DBCon.ConnectionString =
           ConfigurationManager.ConnectionStrings["DSN_NorthWind"].ConnectionString;

        CustCommand.CommandText =
           "SELECT * FROM Customers WHERE CompanyName = 'Alfreds Futterkiste'";

        CustCommand.CommandType = CommandType.Text;
        CustCommand.Connection = DBCon;

        // Selecting all orders for a specific customer
        OrdersCommand.CommandText =
                "SELECT Customers.CompanyName, Customers.ContactName, " +
                "Orders.OrderID, Orders.OrderDate, " +
                "Orders.RequiredDate, Orders.ShippedDate " +
                "FROM Orders, Customers " +
                "WHERE Orders.CustomerID = Customers.CustomerID " +
                "AND Customers.CompanyName = 'Alfreds Futterkiste' " +
                "ORDER BY Customers.CompanyName, Customers.ContactName";

        OrdersCommand.CommandType = CommandType.Text;
        OrdersCommand.Connection = DBCon;

        DBCon.Open();

        // Retrieving customer information asynchronously
        CustAsyncResult = CustCommand.BeginExecuteReader();

        // Retrieving orders list asynchronously
        OrdersASyncResult = OrdersCommand.BeginExecuteReader();

        CustWHandle = CustAsyncResult.AsyncWaitHandle;
        OrdersWHandle = OrdersASyncResult.AsyncWaitHandle;

        // Filling Wait Handles array with the two wait handles we
        // are going to use in this code
        WHandles[0] = CustWHandle;
        WHandles[1] = OrdersWHandle;
System.Threading.WaitHandle.WaitAll(WHandles);

        CustReader = CustCommand.EndExecuteReader(CustAsyncResult);

        OrdersReader = OrdersCommand.EndExecuteReader(OrdersASyncResult);

        gvCustomers.DataSource = CustReader;
        gvCustomers.DataBind();

        gvOrders.DataSource = OrdersReader;
        gvOrders.DataBind();

        DBCon.Close();
    }
</script>

When you compile and execute the code shown in Listing 8-33, you see the result on the screen, as shown in Figure 8-25. This figure clearly shows two GridView controls that were used in the code example. The GridView control on the top shows the result of executing a query that retrieved all information related to a specific customer. The GridView control on the bottom shows the results of executing the second query that retrieved a list of all orders submitted by a specific customer.

Figure 8-25

Figure 8.25. Figure 8-25

The code shown in Listing 8-33 reveals some of the elegance of using the wait approach. However, it is still not the most efficient code you can write with ADO.NET 2.0. The code should allow for a wait until both asynchronous processes finish running before the data binds the result sets to the respective GridView controls.

You can change the code shown in Listing 8-33 just a little to gain even more efficiency. Replace the WaitAll method with the WaitAny method. The WaitAny method enables you to handle the results of each of the asynchronous processes as soon as each is completed without waiting for other processing to finish. To use the WaitAny method and still manage the execution of all asynchronous processes, you can also add a loop that enables you to make sure that all asynchronous processes are handled after they are completed.

The WaitAny method returns an Integer value that indicates an array index of the wait handle that has finished running. Using this return value, you can easily find the correct wait handle and process the result set retrieved from the query that was executed in that particular process, as shown in Listing 8-34.

Example 8.34. Use of the WaitAny method to process multiple asynchronous processes

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim DBCon As SqlConnection
        Dim OrdersCommand As SqlCommand = New SqlCommand()
        Dim CustCommand As SqlCommand = New SqlCommand()
        Dim OrdersReader As SqlDataReader
        Dim CustReader As SqlDataReader
        Dim OrdersASyncResult As IAsyncResult
        Dim CustAsyncResult As IAsyncResult

        Dim WHIndex As Integer
        Dim WHandles(1) As Threading.WaitHandle
        Dim OrdersWHandle As Threading.WaitHandle
        Dim CustWHandle As Threading.WaitHandle

        DBCon = New SqlConnection()
        DBCon.ConnectionString = _
           ConfigurationManager.ConnectionStrings("DSN_NorthWind").ConnectionString

        CustCommand.CommandText = _
           "SELECT * FROM Customers WHERE CompanyName = 'Alfreds Futterkiste'"

        CustCommand.CommandType = CommandType.Text
        CustCommand.Connection = DBCon

        OrdersCommand.CommandText = _
                "SELECT Customers.CompanyName, Customers.ContactName, " & _
                "Orders.OrderID, Orders.OrderDate, " & _
                "Orders.RequiredDate, Orders.ShippedDate " & _
                "FROM Orders, Customers " & _
                "WHERE Orders.CustomerID = Customers.CustomerID " & _
                "AND Customers.CompanyName = 'Alfreds Futterkiste' " & _
                "ORDER BY Customers.CompanyName, Customers.ContactName"
OrdersCommand.CommandType = CommandType.Text
        OrdersCommand.Connection = DBCon

        ' Opening the database connection
        DBCon.Open ()

        ' Retrieving customer information asynchronously
        CustAsyncResult = CustCommand.BeginExecuteReader()

        ' Retrieving orders list asynchronously
        OrdersASyncResult = OrdersCommand.BeginExecuteReader()

        CustWHandle = CustAsyncResult.AsyncWaitHandle
        OrdersWHandle = OrdersASyncResult.AsyncWaitHandle

        ' Filling Wait Handles array with the two wait handles we
        ' are going to use in this code
        WHandles(0) = CustWHandle
        WHandles(1) = OrdersWHandle

        ' Looping 2 times because there are 2 wait handles
        ' in the array
        For Index As Integer = 0 To 1
            ' We are only waiting for any of the two
            ' asynchronous process to finish running
            WHIndex = Threading.WaitHandle.WaitAny(WHandles)

            ' The return value from the WaitAny method is
            ' the array index of the Wait Handle that just
            ' finsihed running
            Select Case WHIndex
                Case 0
                    CustReader = CustCommand.EndExecuteReader(CustAsyncResult)

                    gvCustomers.DataSource = CustReader
                    gvCustomers.DataBind()
                Case 1
                    OrdersReader = _
                       OrdersCommand.EndExecuteReader(OrdersASyncResult)

                    gvOrders.DataSource = OrdersReader
                    gvOrders.DataBind()

            End Select
        Next

        ' Closing connection
        DBCon.Close()
    End Sub
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>The Wait Any Approach</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:GridView ID="gvCustomers" Width="100%" runat="server"></asp:GridView>
    <br /><br />
    <asp:GridView ID="gvOrders" Width="100%" AutoGenerateColumns="False"
     runat="server">
       <Columns>
          <asp:BoundField HeaderText="Company Name"
           DataField="CompanyName"></asp:BoundField>
          <asp:BoundField HeaderText="Contact Name"
           DataField="ContactName"></asp:BoundField>
          <asp:BoundField HeaderText="Order Date" DataField="orderdate"
           DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Required Date" DataField="requireddate"
           DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Shipped Date" DataField="shippeddate"
           DataFormatString="{0:d}"></asp:BoundField>
       </Columns>
    </asp:GridView>
    </div>
    </form>
</body>
</html>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        SqlConnection DBCon;
        SqlCommand OrdersCommand = new SqlCommand();
        SqlCommand CustCommand = new SqlCommand();
        SqlDataReader OrdersReader;
        SqlDataReader CustReader;
        IAsyncResult OrdersASyncResult;
        IAsyncResult CustAsyncResult;

        int WHIndex;
        System.Threading.WaitHandle[] WHandles =
           new System.Threading.WaitHandle[1];
        System.Threading.WaitHandle OrdersWHandle;
        System.Threading.WaitHandle CustWHandle;

        DBCon = new SqlConnection();
        DBCon.ConnectionString =
           ConfigurationManager.ConnectionStrings["DSN_NorthWind"].ConnectionString;
CustCommand.CommandText =
           "SELECT * FROM Customers WHERE CompanyName = 'Alfreds Futterkiste'";

        CustCommand.CommandType = CommandType.Text;
        CustCommand.Connection = DBCon;

        OrdersCommand.CommandText =
                "SELECT Customers.CompanyName, Customers.ContactName, " +
                "Orders.OrderID, Orders.OrderDate, " +
                "Orders.RequiredDate, Orders.ShippedDate " +
                "FROM Orders, Customers " +
                "WHERE Orders.CustomerID = Customers.CustomerID " +
                "AND Customers.CompanyName = 'Alfreds Futterkiste' " +
                "ORDER BY Customers.CompanyName, Customers.ContactName";

        OrdersCommand.CommandType = CommandType.Text;
        OrdersCommand.Connection = DBCon;

        // Opening the database connection
        DBCon.Open();

        // Retrieving customer information asynchronously
        CustAsyncResult = CustCommand.BeginExecuteReader();

        // Retrieving orders list asynchronously
        OrdersASyncResult = OrdersCommand.BeginExecuteReader();

        CustWHandle = CustAsyncResult.AsyncWaitHandle;
        OrdersWHandle = OrdersASyncResult.AsyncWaitHandle;

        // Filling Wait Handles array with the two wait handles we
        // are going to use in this code
        WHandles[0] = CustWHandle;
        WHandles[1] = OrdersWHandle;

        // Looping 2 times because there are 2 wait handles
        // in the array
        for (int Index = 0; Index < 2; Index++ )
        {
            // We are only waiting for any of the two
            // asynchronous process to finish running
            WHIndex = System.Threading.WaitHandle.WaitAny(WHandles);

            // The return value from the WaitAny method is
            // the array index of the Wait Handle that just
            // finsihed running
            switch (WHIndex)
            {
                case 0:
                    CustReader = CustCommand.EndExecuteReader(CustAsyncResult);

                    gvCustomers.DataSource = CustReader;
                    gvCustomers.DataBind();
break;
                case 1:
                    OrdersReader =
                       OrdersCommand.EndExecuteReader(OrdersASyncResult);

                    gvOrders.DataSource = OrdersReader;
                    gvOrders.DataBind();
                    break;
            }
        }
        // Closing connection
        DBCon.Close();
    }
</script>

Next, look at the callback approach. Using this approach, you assign a callback method to the asynchronous process and use it to display the result returned by executing the SQL query.

The Callback Approach

Listing 8-35 creates an inline SQL statement that retrieves the top five records from the database. It starts the asynchronous process by calling the BeginExecuteReader method and passing it the callback delegate. No further processing is needed, and the method ends after the asynchronous process has started. After the callback method is fired, it retrieves the result and displays it on the screen.

Example 8.35. Asynchronous command processing using the callback approach

VB
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<script runat="server">
    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim DBCon As SqlConnection
        Dim Command As SqlCommand = New SqlCommand()
        Dim ASyncResult As SqlAsyncResult

        DBCon = New SqlConnection()
        Command = New SqlCommand()
        DBCon.ConnectionString = _
          ConfigurationManager.ConnectionStrings("DSN_NorthWind").ConnectionString

        ' Selecting top 5 records from the Orders table
        Command.CommandText = _
                "SELECT TOP 5 Customers.CompanyName, Customers.ContactName, " & _
                "Orders.OrderID, Orders.OrderDate, " & _
                "Orders.RequiredDate, Orders.ShippedDate " & _
                "FROM Orders, Customers " & _
                "WHERE Orders.CustomerID = Customers.CustomerID " & _
                "ORDER BY Customers.CompanyName, Customers.ContactName"
Command.CommandType = CommandType.Text
        Command.Connection = DBCon

        DBCon.Open()

        ' Starting the asynchronous processing
        AsyncResult = Command.BeginExecuteReader(New _
           AsyncCallback(AddressOf CBMethod), CommandBehavior.CloseConnection)
    End Sub

    Public Sub CBMethod(ByVal ar As SQLAsyncResult)
        Dim OrdersReader As SqlDataReader

        ' Retrieving result from the asynchronous process
        OrdersReader = ar.EndExecuteReader(ar)

        ' Displaying result on the screen
        gvOrders.DataSource = OrdersReader
        gvOrders.DataBind()
    End Sub
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>The Call Back Approach</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:GridView ID="gvOrders" Width="100%" AutoGenerateColumns="False"
     runat="server">
       <Columns>
          <asp:BoundField HeaderText="Company Name"
           DataField="CompanyName"></asp:BoundField>
          <asp:BoundField HeaderText="Contact Name"
           DataField="ContactName"></asp:BoundField>
          <asp:BoundField HeaderText="Order Date" DataField="orderdate"
           DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Required Date" DataField="requireddate"
           DataFormatString="{0:d}"></asp:BoundField>
          <asp:BoundField HeaderText="Shipped Date" DataField="shippeddate"
           DataFormatString="{0:d}"></asp:BoundField>
       </Columns>
    </asp:GridView>
    </div>
    </form>
</body>
</html>

C#
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        SqlConnection DBCon;
        SqlCommand Command = new SqlCommand();
        SqlAsyncResult ASyncResult;
        DBCon = new SqlConnection();
        Command = new SqlCommand();
        DBCon.ConnectionString =
           ConfigurationManager.ConnectionStrings["DSN_NorthWind"].ConnectionString;

        // Selecting top 5 records from the Orders table
        Command.CommandText =
                "SELECT TOP 5 Customers.CompanyName, Customers.ContactName, " +
                "Orders.OrderID, Orders.OrderDate, " +
                "Orders.RequiredDate, Orders.ShippedDate " +
                "FROM Orders, Customers " +
                "WHERE Orders.CustomerID = Customers.CustomerID " +
                "ORDER BY Customers.CompanyName, Customers.ContactName";

        Command.CommandType = CommandType.Text;
        Command.Connection = DBCon;

        DBCon.Open();

        // Starting the asynchronous processing
        AsyncResult = Command.BeginExecuteReader(new AsyncCallback(CBMethod),
           CommandBehavior.CloseConnection);
    }

    public void CBMethod(SQLAsyncResult ar)
    {
        SqlDataReader OrdersReader;

        // Retrieving result from the asynchronous process
        OrdersReader = ar.EndExecuteReader(ar);

        // Displaying result on the screen
        gvOrders.DataSource = OrdersReader;
        gvOrders.DataBind();
    }
</script>

The callback approach enables you to handle the result of a command execution at a different part of your code. This feature is useful in cases where the command execution takes longer than usual and you want to respond to the user without waiting for the command execution to finish.

Canceling Asynchronous Processing

The asynchronous process often takes longer than expected. To alleviate this problem, you can provide an option to the user to cancel the process without waiting for the result. Canceling an asynchronous process is as easy as calling the Cancel method on the appropriate Command object. This method doesn't return any value. To roll back the work that was already completed by the Command object, you must provide a custom transaction to the Command object before executing the query. You can also handle the rollback or the commit process yourself.

Asynchronous Connections

Now that you understand how to execute multiple database queries asynchronously using the Command object, take a quick look at how you can open database connections asynchronously, as well. The principles of working with asynchronous connections are the same as when you work with asynchronous commands. You can still use any of the three approaches you learned previously.

In ADO.NET, the SqlConnection class exposes a couple of new properties needed when working asynchronously. These properties are shown in the following table.

Property

Description

Asynchronous

This read-only property returns a Boolean value indicating whether the connection has been opened asynchronously.

State

This property returns a value from System.Data.ConnectionState enumeration indicating the state of the connection. The possible values are as follows:

  • Broken

  • Closed

  • Connecting

  • Executing

  • Fetching

  • Open

Summary

In summary, ADO.NET is a powerful tool to incorporate within your ASP.NET applications. ADO.NET has a number of new technologies that provide you with data solutions that you could only dream of in the past.

Visual Studio also makes ADO.NET programming quick and easy when you use the wizards that are available. In this chapter, you saw a number of the wizards. You do not have to use these wizards in order to work with ADO.NET. On the contrary, you can use some of the wizards and create the rest of the code yourself, or you can use none of the wizards. In any case, you have complete and full access to everything that ADO.NET provides.

This chapter covered a range of advanced features of ADO.NET as well. These features are designed to give you the flexibility to handle database processing in a manner never before possible with either of the previous versions of ADO.NET or ADO.

This chapter also covered the features of Multiple Active Result Sets (MARS), which enables you to reuse a single open connection for multiple accesses to the database, even if the connection is currently processing a result set. This feature becomes even more powerful when it is used in conjunction with the asynchronous command processing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.125.7