169 CHAPTER 6 Developing Database Applications with ADO.NET IN THIS CHAPTER The ADO.NET Architecture ADO.NET Namespaces .NET Data Providers Core Classes in the ADO.NET System.Data Namespace Using the .NET Framework Data Provider for SQL Server Using the SqlConnection Object Using the SqlCommand Object Using the SqlDependency Object Using the SqlDataReader Object Using the SqlDataAdapter Object Copyright © 2006 by The McGraw-Hill Companies. Click here for terms of use. 170 Microsoft SQL Server 2005 Developer’s Guide I n this chapter, you will see how to develop SQL Server database applications using Visual Basic and ADO.NET. The first part of the chapter provides you with an overview of the ADO.NET data access technology. The second part of this chapter introduces you to the different ADO.NET namespaces and gives you an overall understanding of the functions of the different classes that compose the ADO.NET architecture. Finally, the last section of this chapter covers the classes that are used by the ADO.NET DataSet object. In this part of the chapter, you’ll get an understanding of DataTable, DataColumn, DataRow, and other classes used by the new ADO.NET DataSets. The ADO.NET Architecture At its essence, ADO.NET is data access middleware that enables the development of database applications. ADO.NET builds on the platform provided by the .NET Framework. ADO.NET is built using managed code from the Microsoft .NET Framework, which means that it enjoys the benefits of the robust .NET execution time environment. Designed primarily to address the issues of Web and distributed applications, ADO.NET consists of a set of classes or namespaces within the .NET Framework that provide data access and management capabilities to .NET applications. As a data access framework, ADO.NET has been primarily designed to allow it to work in the disconnected data access model that is required by n-tiered Web-based applications. ADO, the direct predecessor of ADO.NET, was primarily designed to accommodate a two-tiered client/server style of applications, which typically open a database connection when the application first starts and then hold that connection open until the application ends. This technique works fine for most intranet-style applications where the total number of client connections is a known quantity, and where the state of the application is typically controlled by the application and therefore is also a known quantity. Although this approach worked well for single- tier desktop applications and two-tiered client/server-style applications, it ran into serious limitations for n-tiered Web-style applications. Because the Web is a public environment, the total number of open connections required by Web applications isn’t a known quantity. It could vary greatly and quickly: At one minute, an application may need only a handful of connections, but the need can jump to thousands of connections just a few minutes later. Keeping open connections in this type of environment hurts scalability because each connection must go through the overhead of initializing the connection with the back-end database, plus each open connection requires system resources to be held open—reducing the resources available for Chapter 6: Developing Database Applications with ADO.NET 171 other database operations. As ADO evolved, Microsoft added mechanisms such as disconnected recordsets to help deal with Web-style applications, but these were never part of ADO’s original design. Microsoft designed ADO.NET to be able to handle the disconnected computing scenario required by Web-based applications. This disconnected design enables ADO.NET to be readily scalable for enterprise applications because an open connection isn’t maintained between each client system and the database. Instead, when a client connection is initiated, a connection to the database is briefly opened, the requested data is retrieved from the database server, and the connection is closed. The client application then uses the data completely independently from the data store maintained by the database server. The client application can navigate through its subset of the data, as well as make changes to the data, and the data remains cached at the client until the application indicates that it needs to post any changes back to the database server. At that point, a new connection is briefly opened to the server and all of the changes made by the client application are posted to the database in an update batch and the connection is closed. The core ADO.NET component that enables this disconnected scenario is the DataSet. The DataSet is essentially a miniature in-memory database that is maintained independently of the back-end database. Connections to the data source are opened only to populate the DataSet or to post changes made to the data in the DataSet back to the database. This disconnected computing scenario minimizes the system overhead and improves application throughput and scalability. The in-memory database provided by the ADO.NET DataSet provides many of the functions that you’ll find in a full-blown database, including support for data relations, the capability to create views, and support for data constraints, as well as support for foreign key constraints. However, being an in-memory structure, it doesn’t provide support for many of the more advanced database features that you would find in enterprise-level database products like SQL Server. For example, the DataSet doesn’t support triggers, stored procedures, or user-defined functions. Support for disconnected Web-based applications was one of Microsoft’s priorities in the design of ADO.NET; however, that isn’t all that ADO.NET is capable of. The disconnected model may be appropriate for Web applications, but it really isn’t the best model for client/server and desktop applications. These types of applications can perform better and more efficiently when they run in a connected fashion. To support this connected style of computing, ADO.NET also provides a DataReader object. The DataReader essentially provides fast forward–only cursor style of data access that operates in a connected fashion. While the DataSet provides the basis for disconnected Web applications, the DataReader enables the fast connected style of data access needed by desktop and client/server applications. 172 Microsoft SQL Server 2005 Developer’s Guide In this section, you got a high-level overview of the ADO.NET data access middleware. Here you saw that ADO.NET provides the tools to build applications that support both disconnected Web applications as well as connected client/server style applications. In the next section, you’ll get a close look at the different namespaces that make up the ADO.NET architecture. ADO.NET Namespaces ADO.NET is implemented as a set of classes that exist within the .NET Framework. These ADO.NET classes are grouped together beneath the .NET Framework’s System.Data namespace. Several important namespaces make up the ADO.NET data access technology. First, the .NET Data Providers are implemented in the System. Data.SqlClient, System.Data.OracleClient, System.Data.OleDbClient, and System.Data.Odbc namespaces. The classes in these four namespaces provide the underlying database connectivity that’s required by all of the other ADO.NET objects. The System.Data.SqlClient namespace provides connectivity to SQL Server 7, SQL Server 2000, and SQL Server 2005 databases. The System.Data. OracleClient namespace provides connectivity to Oracle 8 and 9 databases. The System.Data.OleDbClient namespace provides connectivity to SQL Server 6.5 and earlier databases, as well as Access and Oracle databases. And the System.Data. Odbc namespace provides connectivity to legacy databases using ODBC drivers. These classes also provide support for executing commands, retrieving data in a fast forward-only style of access, and loading ADO.NET DataSets. Next, there are the classes contained in the System.Data namespace itself. These classes can be considered the core of the ADO.NET technology, and they provide support for the new ADO.NET DataSet class and its supporting classes. As you learned earlier in this chapter, the DataSet is an in-memory database cache that’s designed to be used in a disconnected fashion. The DataSet consists of a complete collection of tables, columns, constraints, rows, and relationships, plus appropriately named DataTables, DataColumns, DataConstraints, DataRows, and DataRelations. You can see an illustration of the overall ADO.NET architecture in Figure 6-1. .NET Data Providers The.NET Data Providers are responsible for connecting your .NET application to a data source. The .NET Framework comes with four built-in .NET Data Providers. Each of the .NET Data Providers is maintained in its own namespace within the .NET Framework. Chapter 6: Developing Database Applications with ADO.NET 173 Namespaces for the .NET Data Providers Four .NET Data Providers are delivered with the .NET Framework: the .NET Data Provider for SQL Server, the .NET Data Provider for Oracle, the .NET Data Provider for OLE DB, and the .NET Data Provider for ODBC. The .NET Data Provider for User Interface WebForms WinForms DataSet DataTable DataColumn DataConstraint DataRow DataRelationCollection .NET Data Provider DataReader DataAdapter SelectCommand DeleteCommand InsertCommand UpdateCommand Command Parameters Connection Data Source Figure 6-1 Overall ADO.NET architecture 174 Microsoft SQL Server 2005 Developer’s Guide SQL Server is contained in the System.Data.SqlClient namespace. The .NET Data Provider for Oracle is contained in the System.Data.OracleClient namespace. The .NET Data Provider for OLE DB is contained in the System.Data.OleDbClient namespace. And the .NET Data Provider for ODBC is contained in the System.Data. Odbc namespace. System.Data.SqlClient The System.Data.SqlClient is the .NET managed data provider for SQL Server. The System.Data.SqlClient namespace uses SQL Server’s native TDS (Tabular Data Stream) protocol to connect to the SQL Server system. Using the native TDS protocol makes the .NET Data Provider for SQL Server the fastest possible connection between a client application and SQL Server. System.Data.OleDb The System.Data.OleDb namespace is the .NET managed data provider for OLE DB data sources. Whereas the System.Data.SqlClient namespace can be used to access SQL Server 7, 2000, or 2005 databases, the System.Data.OleDb namespace is used to access SQL Server 6.5 databases or earlier, as well as Oracle and Access databases. Theoretically, the .NET Data Provider for OLE DB can access any database where there’s an OLE DB Provider—with the exception of the Microsoft OLE DB Provider for ODBC. Microsoft purposely restricted the capability to access ODBC from the .NET Data Provider for OLE DB. System.Data.OracleClient The System.Data.OracleClient namespace is the .NET managed data provider for Oracle databases. The .NET Data Provider for Oracle requires that the Oracle 8 or higher client be installed on the system. The System.Data.OracleClient namespace uses Oracle’s native OCI (Oracle Call Interface) to connect to Oracle 8 and higher databases. System.Data.Odbc The System.Data.Odbc namespace is the .NET managed data provider for ODBC data sources. Microsoft designed the .NET Data Provider for ODBC to be able to access any ODBC-compliant database. However, Microsoft officially supports only connections using the Microsoft SQL Server ODBC driver, the Microsoft ODBC driver for Oracle, and the Microsoft Jet ODBC driver. However, we have successfully used this provider to connect to DB2 databases as well. Chapter 6: Developing Database Applications with ADO.NET 175 Core Classes for the .NET Data Providers All of the.NET Data Providers included in the .NET Framework are essentially architected the same. In other words, the classes contained in each namespace have nearly identical methods, properties, and events. However, the classes each use a slightly different naming convention. For instance, all of the classes in the .NET Data Provider for SQL Server, found in the System.Data.SqlClient namespace, begin with a prefix of “Sql”; the classes that are part of the .NET Provider for OLE DB, found in the System.Data.OleDb namespace, all begin with the prefix of “OleDb”. Both namespaces contain classes that are used to initiate a connection to a target data source. For the System.Data.SqlClient namespace, this class is named SqlConnection. For the System.Data.OleDb namespace, this class is named OleDbConnection. In each case, the methods that are provided and their parameters are basically the same. Because the function and usage of these classes are basically the same, they are grouped together in the following section under their generic function names. The following section presents an overview of the primary classes contained in the .NET Data Provider namespaces. Connection The Connection class is used to open a connection to a target data source. A Connection object is required in order to populate either the DataReader object or the DataSet object with data from the target data source. Likewise, an active Connection object is required in order to execute any commands or stored procedures that exist on the database from the client .NET applications. Unlike most other .NET objects, Connection objects are not automatically destroyed when they go out of scope. This means that you must explicitly close any open ADO.NET Connection objects in your applications. If multiple Connection objects are opened that use the same connection string, they will be automatically added to the same connection pool. NOTE The actual functionality provided by the OleDbConnection class and the OdbcConnection class is dependent on the capabilities of the underlying OLE DB Provider and ODBC driver. Not all providers and drivers will necessarily support the same functionality. Command The Command class is used to execute either a stored procedure or a SQL statement on the data source that’s associated with the active Connection object. Three types of commands are supported: ExecuteReader, ExecuteNonQuery, and ExecuteScalar. ExecuteReader commands return a result set. ExecuteNonQuery commands are used 176 Microsoft SQL Server 2005 Developer’s Guide to execute SQL action queries like Insert, Update, and Delete statements that do not return any rows. ExecuteScalar commands are used to execute stored procedures or SQL queries that return a single value. Parameter The Parameter class is used to represent a parameter that’s passed to a Command object. Parameter objects have properties that define their attributes. For instance, the different properties of a Parameter object specify the parameter’s name, its direction, its data type, its size, and its value. Parameter names are not case-sensitive, but when naming Parameter objects that represent stored procedure parameters, naming the parameter the same as the stored procedure parameter is typically a good idea. For instance, if the Parameter object represents a stored procedure parameter named @CustomerID, using that same name when instantiating the Parameter object is a good practice. A Parameter object can also be mapped to a DataColumn in the DataSet. DataReader The DataReader class returns a forward-only stream of data from the target data source that’s associated with the active connection object. Unlike objects in most other ADO.NET classes that are instantiated by calling the constructor, objects created from the DataReader class are instantiated by calling the ExecuteReader method. DataAdapter The basic task of the DataAdapter class is to serve as a link between a DataSet object and the data source represented by the active Connection object. The DataAdapter class includes properties that allow you to specify the actual SQL statements that will be used to interact between the DataSet and the target database. In other words, the DataAdapter is responsible for both filling up the DataSet as well as sending changes made in the DataSet back to the data source. For example, the DataAdapter class provides the SelectCommand property, which controls the data that will be retrieved; the InsertCommand property, which indicates how new data in the DataSet will be added to the database; the UpdateCommand property, which controls how changed rows in the DataSet will be posted to the database; and the DeleteCommand property, which controls how rows deleted in the DataSet will be deleted from the database. CommandBuilder The CommandBuilder class provides a mechanism for automatically generating the SQL commands that will be used to update the target database with changes in an attached DataSet. The CommandBuilder uses the metadata returned by the SQL Chapter 6: Developing Database Applications with ADO.NET 177 statement in the DataAdapter’s SelectCommand property to generate any required Insert, Update, and Delete statements. Changes made in the DataSet are not automatically posted to the database unless SQL commands are assigned to the DataAdapter InsertCommand, UpdateCommand, and DeleteCommand properties or unless a CommandBuilder object is created and attached to the active DataAdapter object. Only one CommandBuilder object can be associated with a given DataAdapter at one time. Transaction The Transaction class represents a SQL transaction. SQL transactions basically allow multiple database transactions to be treated as a unit where an entire group of database updates can either be posted to the database or all be undone as a unit. The Transaction object uses the BeginTransaction method to specify the start of a transaction and then either the Commit method to post the changes to the database or the Rollback method to undo the pending transaction. A Transaction object is attached to the active Connection object. Error The Error class contains error information that is generated by the target data source. The active Connection object is automatically closed when an error with a severity of greater than 20 is generated by the target database. However, the connection can be subsequently reopened. Exception The Exception class is created whenever the .NET Data Provider encounters an error generated by one of its members. An Exception object always contains at least one instance of the Error object. You trap exceptions in your code by using the .NET Frameworks Try-Catch structure error handling. Core Classes in the ADO.NET System.Data Namespace The core classes that make up the ADO.NET technology are found in the .NET Framework’s System.Data namespace. The following section presents an overview of the functionality of the most important classes found in the System.Data namespace. 178 Microsoft SQL Server 2005 Developer’s Guide DataSet At the heart of the new ADO.NET architecture is the DataSet. The DataSet class is located in the .NET Framework at System.Data.DataSet. The DataSet is essentially a cache of records that have been retrieved from the database. You can think of the DataSet as a miniature database. It contains tables, columns, constraints, rows, and relations. These DataSet objects are called DataTables, DataColumns, DataRows, Constraints, and Relations. The DataSet essentially allows a disconnected application to function as if it were actively connected to a database. Applications typically need to access multiple pieces of related database information in order to present useful information to the end user. For example, to work with an order an application would typically need to access a number of different database tables, including product tables, customer tables, inventory tables, and shipping tables. All of the related information from this set of tables can be grouped together in the DataSet, providing the disconnected application with the capability to work with all of the related order information that it needs. In the disconnected model, going back to the data source to get each different piece of related information would be inefficient, so the DataSet is typically populated all at once via the active Connection object and DataAdapter from the appropriate .NET Data Provider. A database connection is briefly opened to fill the DataSet and then closed. Afterward the DataSet operates independently of the back-end database. The client application then accesses the Table, DataRow, Data Column, and DataView objects that are contained within the DataSet. Any changes made to the data contained in the DataSet can be posted back to the database via the DataAdapter object. In a multitier environment, a clone of the DataSet containing any changed data is created using the GetChanges method. Then the cloned DataSet is used as an argument of the DataAdapter’s Update method to post the changes to the target database. If any changes were made to the data in the cloned DataSet, these changes can be posted to the original DataSet using the DataSet’s Merge method. Figure 6-2 provides an overview of the ADO.NET DataSet architecture. DataTable The DataTable class is located in the .NET Framework at System.Data.DataTable. The DataTable class represents a table of in-memory data that is contained with a DataSet object. The DataTable object can be created automatically by returning result sets from the DataAdapter to the DataSet object. DataTable objects can also be created programmatically by adding DataColumns objects to the DataTable’s DataColumns collection. Each DataTable object in a DataSet is bindable to data-aware user interface objects found in the .NET Framework’s WinForm and WebForm classes. . all of the other ADO.NET objects. The System.Data.SqlClient namespace provides connectivity to SQL Server 7, SQL Server 2000, and SQL Server 2005 databases. The System.Data. OracleClient namespace. Provider DataReader DataAdapter SelectCommand DeleteCommand InsertCommand UpdateCommand Command Parameters Connection Data Source Figure 6-1 Overall ADO.NET architecture 174 Microsoft SQL Server 2005 Developer’s Guide SQL Server is contained in the System.Data.SqlClient namespace. The .NET Data Provider for Oracle. Framework Data Provider for SQL Server Using the SqlConnection Object Using the SqlCommand Object Using the SqlDependency Object Using the SqlDataReader Object Using the SqlDataAdapter Object Copyright