ptg 884 CHAPTER 19 Building Data Access Components with ADO.NET { DataLayer1.Movie newMovie = new DataLayer1.Movie(); newMovie.Title = (string)reader[“Title”]; newMovie.CategoryId = (int)reader[“CategoryID”]; movies.Add(newMovie); } } } static DataLayer1() { _connectionString = WebConfigurationManager.ConnectionStrings[“Movies”].ConnectionString; } } The SqlDataReader.NextResult() method is called to advance to the next resultset. This method returns either True or False depending on whether a next resultset exists. In Listing 19.22, it is assumed that there is both a movies category and movies resultset. The page in Listing 19.23 displays the contents of the two database tables in two GridView controls (see Figure 19.10). FIGURE 19.10 Displaying two resultsets. From the Library of Wow! eBook ptg 885 Connected Data Access 19 LISTING 19.23 ShowDataLayer1.aspx <%@ Page Language=”C#” %> <%@ Import Namespace=”System.Collections.Generic” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <script runat=”server”> void Page_Load() { // Get database data List<DataLayer1.MovieCategory> categories = ➥ new List<DataLayer1.MovieCategory>(); List<DataLayer1.Movie> movies = new List<DataLayer1.Movie>(); DataLayer1.GetMovieData(categories, movies); // Bind the data grdCategories.DataSource = categories; grdCategories.DataBind(); grdMovies.DataSource = movies; grdMovies.DataBind(); } </script> <html xmlns=”http://www.w3.org/1999/xhtml” > <head id=”Head1” runat=”server”> <title>Show DataLayer1</title> </head> <body> <form id=”form1” runat=”server”> <div> <h1>Movie Categories</h1> <asp:GridView id=”grdCategories” Runat=”server” /> <h1>Movies</h1> <asp:GridView id=”grdMovies” Runat=”server” /> </div> </form> </body> </html> From the Library of Wow! eBook ptg 886 CHAPTER 19 Building Data Access Components with ADO.NET Working with Multiple Active Resultsets ADO.NET 2.0 introduced a new feature named Multiple Active Results Sets (MARS). In the previous version of ADO.NET, a database connection could represent only a single resultset at a time. If you take advantage of MARS, you can represent multiple resultsets with a single database connection. Using MARS is valuable in scenarios in which you need to iterate through a resultset and perform an additional database operation for each record in the resultset. MARS is disabled by default. To enable MARS, you must include a MultipleActiveResultSets=True attribute in a connection string. For example, the page in Listing 19.24 programmatically builds the nodes in a TreeView control. The page displays a list of movie categories and, beneath each movie category, it displays a list of matching movies (see Figure 19.11). FIGURE 19.11 Fetching database records with MARS enabled. LISTING 19.24 ShowMARS.aspx <%@ Page Language=”C#” %> <%@ Import Namespace=”System.Data” %> <%@ Import Namespace=”System.Data.SqlClient” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <script runat=”server”> void Page_Load() { From the Library of Wow! eBook ptg 887 Connected Data Access 19 if (!Page.IsPostBack) BuildTree(); } void BuildTree() { // Create MARS connection string connectionString = @”MultipleActiveResultSets=True;” + @”Data Source=.\SQLExpress;Integrated Security=True;” + @”AttachDBFileName=|DataDirectory|MyDatabase.mdf;User Instance=True”; SqlConnection con = new SqlConnection(connectionString); // Create Movie Categories command string cmdCategoriesText = “SELECT Id,Name FROM MovieCategories”; SqlCommand cmdCategories = new SqlCommand(cmdCategoriesText, con); // Create Movie command string cmdMoviesText = “SELECT Title FROM Movies “ + “WHERE CategoryId=@CategoryID”; SqlCommand cmdMovies = new SqlCommand(cmdMoviesText, con); cmdMovies.Parameters.Add(“@CategoryId”, SqlDbType.Int); using (con) { con.Open(); // Iterate through categories SqlDataReader categories = cmdCategories.ExecuteReader(); while (categories.Read()) { // Add category node int id = categories.GetInt32(0); string name = categories.GetString(1); TreeNode catNode = new TreeNode(name); TreeView1.Nodes.Add(catNode); // Iterate through matching movies cmdMovies.Parameters[“@CategoryId”].Value = id; SqlDataReader movies = cmdMovies.ExecuteReader(); while (movies.Read()) { // Add movie node string title = movies.GetString(0); TreeNode movieNode = new TreeNode(title); catNode.ChildNodes.Add(movieNode); From the Library of Wow! eBook ptg 888 CHAPTER 19 Building Data Access Components with ADO.NET } movies.Close(); } } } </script> <html xmlns=”http://www.w3.org/1999/xhtml” > <head id=”Head1” runat=”server”> <title>Show MARS</title> </head> <body> <form id=”form1” runat=”server”> <div> <asp:TreeView id=”TreeView1” Runat=”server” /> </div> </form> </body> </html> The MultipleActiveResultSets attribute is included in the connection string used to open the database connection. If MARS were not enabled, you couldn’t loop through the interior SqlDataReader that represents the matching movies while the containing SqlDataReader that represents the movie categories is open. Disconnected Data Access The ADO.NET Framework supports two models of data access. In the first part of this chapter, you saw how you can use the SqlConnection, SqlCommand, and SqlDataReader objects to connect to a database and retrieve data. When you read data from a database by using a SqlDataReader object, an open connection must be maintained between your application and the database. In this section, we examine the second model of data access supported by ADO.NET: the disconnected model. When you use the objects discussed in this section, you do not need to keep a connection to the database open. This section discusses four new ADO.NET objects: . DataAdapter—Enables you to transfer data from the physical database to the in- memory database and back again. From the Library of Wow! eBook ptg 889 Disconnected Data Access 19 . DataTable—Represents an in-memory database table. . DataView—Represents an in-memory database view. . DataSet—Represents an in-memory database. The ADO.NET objects discussed in this section are built on top of the ADO.NET objects discussed in the previous section. For example, behind the scenes, the DataAdapter uses a DataReader to retrieve data from a database. The advantage of using the objects discussed in this section is that they provide you with more functionality. For example, you can filter and sort the rows represented by a DataView. Furthermore, you can use the DataTable object to track changes made to records and accept or reject the changes. The big disadvantage of using the objects discussed in this section is that they tend to be slower and more resource intensive. Retrieving 500 records with a DataReader is much faster than retrieving 500 records with a DataAdapter. NOTE For detailed performance comparisons between the DataReader and DataAdapter, see Priya Dhawan’s article at the Microsoft MSDN website (msdn.Microsoft.com), “Performance Comparison: Data Access Techniques.” Therefore, unless you need to use any of the specialized functionality supported by these objects, my recommendation is that you stick with the objects discussed in the first part of this chapter when accessing a database. In other words, DataReaders are good and DataAdapters are bad. Using the DataAdapter Object The DataAdapter acts as the bridge between an in-memory database table and a physical database table. You use the DataAdapter to retrieve data from a database and populate a DataTable. You also use a DataAdapter to push changes that you have made to a DataTable back to the physical database. The component in Listing 19.25 illustrates how you can use a SqlDataAdapter to populate a DataTable. From the Library of Wow! eBook ptg 890 CHAPTER 19 Building Data Access Components with ADO.NET LISTING 19.25 App_Code\Movie8.cs using System; using System.Data; using System.Data.SqlClient; using System.Web.Configuration; using System.Collections.Generic; public class Movie8 { private static readonly string _connectionString; public DataTable GetAll() { // Initialize the DataAdapter SqlDataAdapter dad = new SqlDataAdapter( “SELECT Title,Director FROM Movies”, _connectionString); // Create a DataTable DataTable dtblMovies = new DataTable(); // Populate the DataTable dad.Fill(dtblMovies); // Return results return dtblMovies; } static Movie8() { _connectionString = WebConfigurationManager.ConnectionStrings[“Movies”].ConnectionString; } } The page in Listing 19.26 contains a GridView that is bound to an ObjectDataSource that represents the component in Listing 19.25 (see Figure 19.12). From the Library of Wow! eBook ptg 891 Disconnected Data Access 19 LISTING 19.26 ShowMovie8.aspx <%@ Page Language=”C#” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <html xmlns=”http://www.w3.org/1999/xhtml” > <head id=”Head1” runat=”server”> <title>Show Movie8</title> </head> <body> <form id=”form1” runat=”server”> <div> <asp:GridView id=”grdMovies” DataSourceID=”srcMovies” Runat=”server” /> <asp:ObjectDataSource id=”srcMovies” TypeName=”Movie8” FIGURE 19.12 Displaying data with a DataAdapter. From the Library of Wow! eBook ptg 892 CHAPTER 19 Building Data Access Components with ADO.NET SelectMethod=”GetAll” Runat=”server” /> </div> </form> </body> </html> A SqlConnection is never explicitly created in the component in Listing 19.25. When you call the SqlDataAdapter object’s Fill() method, the SqlDataAdapter automatically creates and opens a connection. After the data is fetched from the database, the Fill() method automatically closes the connection. You don’t need to wrap the call to the Fill() method within a Using or Try Catch statement. Internally, the SqlDataAdapter uses a Try Catch statement to ensure that its connection gets closed. Opening and closing a database connection is a slow operation. If you know that you will need to perform another database operation after using the SqlDataAdapter, you should explicitly create a SqlConnection and open it like this: SqlConnection con = new SqlConnection( connection string ); SqlDataAdapter dad = new SqlDataAdapter(“SELECT Title,Director FROM Movies”, con); using (con) { con.Open(); dad.Fill(dtblMovies); Perform other database operations with connection } If a SqlConnection is already open when you call the Fill() method, it doesn’t close it. In other words, the Fill() method maintains the state of the connection. Performing Batch Updates You can think of a SqlDataAdapter as a collection of four SqlCommand objects: . SelectCommand—Represents a SqlCommand used for selecting data from a database. . UpdateCommand—Represents a SqlCommand used for updating data in a database. . InsertCommand—Represents a SqlCommand used for inserting data into a database. . DeleteCommand—Represents a SqlCommand used for deleting data from a database. You can use a DataAdapter not only when retrieving data from a database, but also when updating, inserting, and deleting data from a database. If you call a SqlDataAdapter object’s Update() method, and pass the method a DataTable, the SqlDataAdapter calls its UpdateCommand, InsertCommand, and DeleteCommand to make changes to the database. From the Library of Wow! eBook ptg 893 Disconnected Data Access 19 You can assign a SqlCommand object to each of the four properties of the SqlDataAdapter. Alternatively, you can use the SqlCommandBuilder object to create the UpdateCommand, InsertCommand, and DeleteCommand for you. The SqlCommandBuilder class takes a SqlDataAdapter that has a SELECT command and generates the other three commands automatically. For example, the page in Listing 19.27 displays all the records from the Movies database table in a spreadsheet created with a Repeater control (see Figure 19.13). If you make changes to the data and click the Update button, the Movies database table is updated with the changes. FIGURE 19.13 Batch updating database records. LISTING 19.27 ShowDataAdapterUpdate.aspx <%@ Page Language=”C#” %> <%@ Import Namespace=”System.Data” %> <%@ Import Namespace=”System.Data.SqlClient” %> <%@ Import Namespace=”System.Web.Configuration” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <script runat=”server”> private SqlDataAdapter dad; private DataTable dtblMovies; From the Library of Wow! eBook . enabled. LISTING 19. 24 ShowMARS.aspx <%@ Page Language=”C#” %> <%@ Import Namespace=”System.Data” %> <%@ Import Namespace=”System.Data.SqlClient” %> <!DOCTYPE html PUBLIC -/ /W3C//DTD. you can use a SqlDataAdapter to populate a DataTable. From the Library of Wow! eBook ptg 890 CHAPTER 19 Building Data Access Components with ADO .NET LISTING 19.25 App_CodeMovie8.cs using System;. ShowMovie8.aspx <%@ Page Language=”C#” %> <!DOCTYPE html PUBLIC -/ /W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <html xmlns=”http://www.w3.org/1999/xhtml”