ptg 844 CHAPTER 19 Building Data Access Components with ADO.NET You also learn how to build Microsoft SQL Server database objects, such as stored proce- dures and user-defined types, by using .NET Framework. For example, you learn how to write a Microsoft SQL Server stored procedure, using C# programming language. NOTE If you don’t want to get your hands dirty touching any actual SQL or ADO.NET code, skip this chapter and read Chapter 20, “Data Access with LINQ to SQL.” LINQ to SQL enables you to access the database without writing any ADO.NET or SQL code. Connected Data Access The ADO.NET Framework encompasses a huge number of classes. However, at its heart, it actually consists of the following three classes: . Connection—Enables you to represent a connection to a data source. . Command—Enables you to execute a command against a data source. . DataReader—Enables you to represent data retrieved from a data source. Most of the other classes in ADO.NET Framework are built from these three classes. These three classes provide you with the fundamental methods of working with database data. They enable you to connect to a database, execute commands against a database, and represent the data returned from a database. Now that you understand the importance of these three classes, it’s safe to tell you that they don’t actually exist. ADO.NET uses the Provider model. You use different sets of ADO.NET classes for communicating with different data sources. For example, there is no such thing as the Connection class. Instead, there are the SqlConnection, OracleConnection, OleDbConnection, and ODBCConnection classes. You use different Connection classes to connect to different data sources. The different implementations of the Connection, Command, and DataReader classes are grouped into the following namespaces: . System.Data.SqlClient—Contains ADO.NET classes for connecting to Microsoft SQL Server version 7.0 or higher. . System.Data.OleDb—Contains ADO.NET classes for connecting to a data source with an OLEDB provider. . System.Data.Odbc—Contains ADO.NET classes for connecting to a data source with an ODBC driver. . System.Data.OracleClient—Contains ADO.NET classes for connecting to an Oracle database (requires Oracle 8i Release 3/8.1.7 Client or later). . System.Data.SqlServerCe—Contains ADO.NET classes for connecting to SQL Server Mobile. From the Library of Wow! eBook ptg 845 Connected Data Access If you connect to Microsoft SQL Server 7.0 or higher, you should always use the classes from the SqlClient namespace. These classes provide the best performance because they connect directly to SQL Server at the level of the Tabular Data Stream (the low-level proto- col that Microsoft SQL Server uses to communicate with applications). Of course, there are other databases in the world than Microsoft SQL Server. If you are communicating with an Oracle database, you should use the classes from the OracleClient namespace. If you are communicating with another type of database, you need to use the classes from either the OleDb or Odbc namespaces. Just about every data- base created by man has either an OLEDB provider or an ODBC driver. Because ADO.NET follows the Provider model, all implementations of the Connection, Command, and DataReader classes inherit from a set of base classes. Here is a list of these base classes: . DbConnection—The base class for all Connection classes. . DbCommand—The base class for all Command classes. . DbDataReader—The base class for all DataReader classes. These base classes are contained in the System.Data.Common namespace. All the sample code in this chapter assumes that you work with Microsoft SQL Server. Therefore, all the sample code uses the classes from the SqlClient namespace. However, because ADO.NET uses the Provider model, the methods that you would use to work with another database are similar to the methods described in this chapter. NOTE Before you can use the classes from the SqlClient namespaces in your components and pages, you need to import the System.Data.SqlClient namespace. Before we examine the Connection, Command, and DataReader classes in detail, let’s look at how you can build a simple data access component with these classes. The component in Listing 19.1, named Movie1, includes a method named GetAll() that returns every record from the Movies database table. LISTING 19.1 App_Code\Movie1.cs using System; using System.Data; using System.Data.SqlClient; using System.Web.Configuration; using System.Collections.Generic; public class Movie1 { private static readonly string _connectionString; 19 From the Library of Wow! eBook ptg 846 CHAPTER 19 Building Data Access Components with ADO.NET private string _title; private string _director; public string Title { get { return _title; } set { _title = value; } } public string Director { get { return _director; } set { _director = value; } } public List<Movie1> GetAll() { List<Movie1> results = new List<Movie1>(); SqlConnection con = new SqlConnection(_connectionString); SqlCommand cmd = new SqlCommand(“SELECT Title,Director FROM Movies”, con); using (con) { con.Open(); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { Movie1 newMovie = new Movie1(); newMovie.Title = (string)reader[“Title”]; newMovie.Director = (string)reader[“Director”]; results.Add(newMovie); } } return results; } static Movie1() { _connectionString = WebConfigurationManager.ConnectionStrings[“Movies”].ConnectionString; } } In Listing 19.1, a SqlConnection object represents a connection to a Microsoft SQL Server database. A SqlCommand object represents a SQL SELECT command. The results of executing the command are represented with a SqlDataReader. From the Library of Wow! eBook ptg 847 Connected Data Access Each row returned by the SELECT command is retrieved by a call to the SqlDataReader.Read() method from within a While loop. When the last row is retrieved from the SELECT command, the SqlDataReader.Read() method returns False and the While loop ends. Each row retrieved from the database is added to a List collection. An instance of the Movie1 class represents each record. The page in Listing 19.2 uses a GridView and ObjectDataSource control to display the records returned by the Movie1 data access component (see Figure 19.1). 19 FIGURE 19.1 Displaying movie records. LISTING 19.2 ShowMovie1.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 Movie1</title> From the Library of Wow! eBook ptg 848 CHAPTER 19 Building Data Access Components with ADO.NET </head> <body> <form id=”form1” runat=”server”> <div> <asp:GridView id=”grdMovies” DataSourceID=”srcMovies” Runat=”server” /> <asp:ObjectDataSource id=”srcMovies” TypeName=”Movie1” SelectMethod=”GetAll” Runat=”server” /> </div> </form> </body> </html> Using the Connection Object The Connection object represents a connection to a data source. When you instantiate a Connection, you pass a connection string to the constructor, which contains information about the location and security credentials required for connecting to the data source. For example, the following statement creates a SqlConnection that represents a connec- tion to a Microsoft SQL Server database named Pubs that is located on the local machine: SqlConnection con = new SqlConnection(“Data Source=localhost;Integrated ➥ Security=True;Initial Catalog=Pubs”); For legacy reasons, there are a number of ways to write a connection string that does exactly the same thing. For example, the keywords Data Source, Server, Address, Addr, and Network Address are all synonyms. You can use any of these keywords to specify the location of the database server. NOTE You c an use the SqlConnectionStringBuilder class to convert any connection string into canonical syntax. For example, this class replaces the keyword Server with the keyword Data Source in a connection string. From the Library of Wow! eBook ptg 849 Connected Data Access Before you execute any commands against the data source, you first must open the connection. After you finish executing commands, you should close the connection as quickly as possible. A database connection is a valuable resource. Strive to open database connections as late as possible, and close database connections as early as possible. Furthermore, always include error handling code to make sure that a database connection gets closed even when there is an exception. For example, you can take advantage of the Using statement to force a connection to close even when an exception is raised, like this: SqlConnection con = new SqlConnection(“Data Source=localhost; Integrated Security=True;Initial Catalog=Pubs”); SqlCommand cmd = new SqlCommand(“INSERT Titles (Title) VALUES (‘Some Title’)”, con); using (con) { con.Open(); cmd.ExecuteNonQuery(); } The using statement forces the connection to close, regardless of whether an error occurs when a command is executed against the database. The using statement also disposes of the Connection object. (If you need to reuse the Connection, you need to reinitialize it.) Alternatively, you can use a try catch statement to force a connection to close like this: SqlConnection con = new SqlConnection(“Data Source=localhost;Integrated Security=True;Initial Catalog=Pubs”); SqlCommand cmd = new SqlCommand(“INSERT Titles (Title) VALUES (‘Some Title’)”, con); try { con.Open(); cmd.ExecuteNonQuery(); } finally { con.Close(); } The finally clause in this try catch statement forces the database connection to close both when there are no errors and when there are errors. Retrieving Provider Statistics When you use the SqlConnection object, you can retrieve statistics about the database commands executed with the connection. For example, you can retrieve statistics on total execution time. 19 From the Library of Wow! eBook ptg 850 CHAPTER 19 Building Data Access Components with ADO.NET The GetAll() method exposed by the component in Listing 19.3 includes a parameter named executionTime. After the database command executes, the value of executionTime is retrieved from the Connection statistics. LISTING 19.3 App_Code\Movie2.cs using System; using System.Data; using System.Data.SqlClient; using System.Web.Configuration; using System.Collections; using System.Collections.Generic; public class Movie2 { private static readonly string _connectionString; private string _title; private string _director; public string Title { get { return _title; } set { _title = value; } } public string Director { get { return _director; } set { _director = value; } } public List<Movie2> GetAll(out long executionTime) { List<Movie2> results = new List<Movie2>(); SqlConnection con = new SqlConnection(_connectionString); SqlCommand cmd = new SqlCommand(“WAITFOR DELAY ‘0:0:03’;SELECT Title,Director FROM Movies”, con); con.StatisticsEnabled = true; using (con) { con.Open(); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { From the Library of Wow! eBook ptg 851 Connected Data Access Movie2 newMovie = new Movie2(); newMovie.Title = (string)reader[“Title”]; newMovie.Director = (string)reader[“Director”]; results.Add(newMovie); } } IDictionary stats = con.RetrieveStatistics(); executionTime = (long)stats[“ExecutionTime”]; return results; } static Movie2() { _connectionString = WebConfigurationManager.ConnectionStrings[“Movies”].ConnectionString; } } In Listing 19.3, the SqlConnection.StatisticsEnabled property is set to the value True. You must enable statistics before you can gather statistics. After the command executes, a dictionary of statistics is retrieved with the SqlConnection.RetrieveStatistics() method. Finally, you retrieve the executionTime by looking up the ExecutionTime key in the dictionary. NOTE In Listing 19.3, the SQL WAITFOR statement is used to pause the execution of the SELECT command for three seconds so that a more interesting execution time is retrieved from the ExecutionTime statistic. Because the SELECT command is such a simple command, if you don’t add a delay, you often receive an execution time of 0 milliseconds. The page in Listing 19.4 illustrates how you can use this component to display both the results of a database query and the database query execution time (see Figure 19.2). 19 From the Library of Wow! eBook ptg 852 CHAPTER 19 Building Data Access Components with ADO.NET LISTING 19.4 ShowMovie2.aspx <%@ Page Language=”C#” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <script runat=”server”> protected void srcMovies_Selected(object sender, ➥ ObjectDataSourceStatusEventArgs e) { lblExecutionTime.Text = e.OutputParameters[“executionTime”].ToString(); } </script> <html xmlns=”http://www.w3.org/1999/xhtml” > <head id=”Head1” runat=”server”> <title>Show Movie2</title> </head> <body> FIGURE 19.2 Displaying execution time statistics. From the Library of Wow! eBook ptg 853 Connected Data Access <form id=”form1” runat=”server”> <div> <asp:GridView id=”grdMovies” DataSourceID=”srcMovies” Runat=”server” /> <asp:ObjectDataSource id=”srcMovies” TypeName=”Movie2” SelectMethod=”GetAll” Runat=”server” OnSelected=”srcMovies_Selected”> <SelectParameters> <asp:Parameter Name=”executionTime” Type=”Int64” Direction=”Output” /> </SelectParameters> </asp:ObjectDataSource> <br /> Execution time was <asp:Label id=”lblExecutionTime” Runat=”server” /> milliseconds </div> </form> </body> </html> The SqlConnection object supports the following properties and methods related to gath- ering statistics: . StatisticsEnabled—Enables you to turn on statistics gathering. . RetrieveStatistics()—Enables you to retrieve statistics represented with an IDictionary collection. . ResetStatistics()—Resets all statistics to 0. You can call the RetrieveStatistics() method multiple times on the same SqlConnection. Each time you call the method, you get another snapshot of the Connection statistics. 19 From the Library of Wow! eBook . Library of Wow! eBook ptg 852 CHAPTER 19 Building Data Access Components with ADO .NET LISTING 19 .4 ShowMovie2.aspx <%@ Page Language=”C#” %> <!DOCTYPE html PUBLIC -/ /W3C//DTD XHTML 1.0. ptg 844 CHAPTER 19 Building Data Access Components with ADO .NET You also learn how to build Microsoft SQL Server database objects, such as stored proce- dures and user-defined types, by. eBook ptg 846 CHAPTER 19 Building Data Access Components with ADO .NET private string _title; private string _director; public string Title { get { return _title; } set { _title = value; } } public