Hitchhikers Guide to Visual Studio and SQL Server: Best Practice Architectures and Examples, 7th Edition (Microsoft Windows Server System Series)
Ready to drill down into the classes you'll use to access SQL Server? As I said earlier, there are two general categories of data classes you'll use to access and manage information extracted from your data source. The classes used to connect to the data source, run the query, and return the data are implemented by the .NET Data Provider, as exposed by the System.Data.SqlClient namespace. These are described next. The classes used to manage the returned resultsets in memory are exposed in the System.Data namespace, which is discussed later in this chapter. To make the discovery and exploration process easy, this section highlights the parts of the .NET Framework namespace that you'll most likely reference in your application. Sure, many of the classes exposed by the Object Browser are used by Visual Studio and other tools behind the scenes and might not be of much use to you unless you're writing your own tools, so let's focus on the handful of namespaces you will need to referenceat least, in applications accessing SQL Server.
The following summaries highlight the core (and most frequently used) SqlClient classes. I discuss each of these classes in detail in later chapters, so you can look forward to far more detail and a rich set of examples. The SqlConnection Class
When you need to access data in a SQL Server database, you need to open a "connection" to it. In the most basic sense, a connection is simply a two-directional communications link to a specific instance of SQL Server from your application. When interacting with SQL Server, a connection transmits and receives provider-unique Tabular Data Stream (TDS) packets. As long as the connection is open, SQL Server dedicates a block of memory to the connection and monitors it for traffic generated when you tell ADO.NET to run a query, fetch a resultset, or any other T-SQL or utility operation. In some cases, SQL Server and the provider also perform background handshake operations to determine if the server and client are still working. When the connection is broken (on either end), the resources used by the connection are released (at least, on the server end). Typically, a connection to SQL Server can handle only one "conversation" at a time. However, if you enable Multiple Active Result Sets (MARS), you might (just might) be able to hold several conversations at once on a single connectionwell, sort of. While MARS permits you to have more than one operation being executed on the same connection, the resultsets from these operations are simply interleavedthe command execution is still sequential. As with a telephone party line (where several customers share the same phone line), there are specific rules and protocols to observe so that the connection can be safely and efficiently shared. I discuss MARS in Chapter 9just don't jump the gun and start enabling it before you're sure it's necessary. Because you can't reference any SQL Server data without a connection of some kind, one of the first SqlClient classes you'll have to work with is SqlConnection. It's important enough to devote Chapter 9 to the SqlConnection class. There, I discuss how to use the SqlConnection to establish a link between a specified SQL Server instance and how to manage the connection pool used by ADO.NET to optimize the connection process. Later in Chapter 13, I show how to use a "context" connection when coding SQL CLR procedures. This SqlConnection class is used to capture the parameters needed to establish your application's credentials, point to a specific SQL Server instance or MDF filename, set a default database, and set many other options. These option/value pairs are stored as a string in the SqlConnection object's ConnectionString property. As I'll cover in Chapter 9, the ConnectionString determines what, who, and how your application gets connected, as well as if the SqlConnection is pooled or not. In ADO.NET 2.0, the SqlConnection class also exposes the underlying schema of the target DBMS using the GetSchema method. Again, this is a feature useful for building schema-driven tools and utilities. The SqlConnectionStringBuilder Class
The 2.0 version of ADO.NET also includes another new classthe SqlConnectionStringBuilder. Developed for use behind the scenes in Visual Studio, this class can help build a ConnectionString without having to code a string containing the correct argument/value pairs. Because the SqlConnectionStringBuilder properties are strongly typed, you'll be able to construct this crucial ConnectionString more safely. I illustrate how to use this class in Chapter 9. The SqlCommand Class
Once the connection has been established, you must use the SqlCommand class to execute T-SQL queries against an instantiated and open SqlConnection object. Unlike ADO classic, the only way to execute T-SQL is to use the SqlCommand object. Its properties describe the query, contain the T-SQL, and include a pointer to the connection. The SqlCommand is also used to manage client-side transactions. As shown in Chapter 11, "Executing SqlCommand Objects," a SqlCommand object can return a rowset in the form of a SqlDataReader, a single object value (a scalar), a data stream, XML, and, in some cases, a rows affected value. You also use SqlCommand objects to execute stored procedures, manage input parameters, and return their rowsets, OUTPUT, and RETURN value parameters. The higher-level classes, like the SqlDataAdapter and the Visual Studio-generated TableAdapter, use the SqlCommand object behind the scenes to execute queries. The SqlParameter and SqlParameterCollection Classes
The SqlCommand class exposes a SqlParameter class used to manage query input, input/output, OUTPUT, and RETURN value parameters. In SQL Server, the SqlCommand object's Parameters collection manages a set of named parameters that are automatically passed to SQL Server at runtime so they can be inserted into the query as they are needed. This same set of SqlParameter objects can be automatically populated with OUTPUT parameters that carry the new Identity value. I illustrate how this is done in Chapter 13. The SqlTransaction Class
The SqlCommand class can also help manage transactions through its Transaction property, which can point to a SqlTransaction object. This way, you can tie the success or failure of one or more "atomic" queries or database DML operations with other related operations. At any time, you can instruct SQL Server to accept or roll back the changes using SqlTransaction class methods. The SqlTransaction class is also used to create ADO.NET's form of pessimistic locking cursors. These are discussed in Chapter 11. The SqlDataReader Class
This class is used to extract raw data from the TDS stream that is returned when you execute a query that returns a rowset or BLOB stream. The SqlDataReader is unusual, in that no Microsoft data access interface since DB-Library has exposed anything like itall pre-ADO.NET interfaces have hidden this raw data stream to make data access easier (albeit less flexible) for developers. Hard-coding the SqlDataReader means you might have to handle every aspect of connecting, resultset management, and fetching for each column one at a time. While the SqlDataReader can return rows very quickly, it can be code-intensive and expensive to support if you choose to address the individual rows and columns in code. In addition, since the SqlDataReader requires manual connection management, it makes applications more prone to errors caused by orphaned SqlConnection objects, as I discuss in Chapter 9. However, since ADO.NET 2.0 exposes the new DataTable and DataSet Load methods, it's relatively easy to eliminate costly row-by-row parsing code, as I illustrate in Chapter 11. The SqlDataReader class is implemented in much the same way as other .NET Data Providers implement their specific DataReader objects. You'll also discover that the SqlBulkCopy class introduced with ADO.NET 2.0 can accept data from any of these DataReader streamswhether they're generated by SqlClient or by Odbc, OleDb, or some provider-specific .NET Data Provider. The SqlDataAdapter Class
When you need to create an application that fetches data from a simple table-based data stream and (possibly) update that data, consider use of the SqlDataAdapter, shown in Figure 8.14. Figure 8.14. The SqlDataAdapter manages a set of SqlCommand and SqlConnection classes.
For reasons I don't endorse or agree with, Microsoft has chosen to "hide" the SqlDataAdapter class wizards and several data-centric icons from the toolbox. While it's still fully functional, the SqlDataAdapter is now missing from its usual toolbox "Data" tab location.
Think of the SqlDataAdapter as a "super-class". It's one of the most sophisticated classes in ADO.NET and has been in place since the first version of the .NET Framework. Unlike the new Visual Studiogenerated TableAdapter, the SqlDataAdapter is a repository for a set of other classes that perform specific support tasks and supports methods to perform both simple DataTable population and update operations. The key here is that the SqlDataAdapter is designed to provide updateability to a single table's rowset, even though its DataSet is capable of containing many DataTable instances. In practice, the Visual Studio tools assume that you're populating the SqlDataAdapter, DataSet, and DataTable objects from a single table's rowset (hopefully with a subset of the table's rows). Yes, the SqlDataAdapter can fetch data from two or more tablesbuilding a separate DataTable for each rowset returned. The SelectCommand associated with the SqlDataAdapter can also execute a SELECT that returns the product of a JOIN or any number of unrelated rowsets. However, the SqlDataAdapter contains only one set of DML commands to change the target DataTable. This means when you execute the UPDATE method, you get only one set of action commands to executeeven though there might be 2 or 22 tables at the root of the rowset.
Database updateability assumes that the DBA has granted update rights to the base tablethey usually don't. This does lessen the usefulness of the SqlDataAdaptereven if you don't plan to use its Update method to post changes to the database.
The SqlDataAdapter Fill method is a powerful tool when fetching rows from several tables at once in a single round-trip. It's a great tool to populate lookup tables bound to pick lists in your UI. You set up the SqlDataAdapter by using the Data Adapter Configuration Wizard (DACW) or by setting specific properties:
To make the process of configuring the SqlDataAdapter easier, Visual Studio (still) includes the Data Adapter Configuration Wizard, but it (still) sets only a few of the properties listed. While it's hard to find, you can reconfigure Visual Studio 2005 to place the DACW launch icons back on the toolbox menu (as discussed in Chapter 7, "Managing Data Tools and Data Binding") and step you through the process of building a SelectCommand. From there, the DACW uses the SqlCommandBuilder to at least attempt to generate the DML queries. Visual Studio augments this code to help manage identity values and updated rowsets but does not expose any new functionality to let you alter the default (and dumb) behavior of the SqlCommandBuilder. As I'll show you in Chapter 9, the SqlCommandBuilder is very limited in its flexibility. Most developers learn to build the DML queries themselvesor simply point to appropriate stored procedures. As already discussed, the new TableAdapter configuration wizard does expose a few more CommandBuilder switches to alter how concurrency is managed. ADO.NET and Updates
When I discuss database updates in Chapter 12, I'll show how the SqlDataAdapter is designed to handle the update chores for a single database tablea subset of whose rows are managed in an associated DataSet and DataTable. No, there is no way to get the SqlDataAdapter to handle more sophisticated operations unless you code the DML action SqlCommand objects yourself. That's exactly what I discuss in Chapter 10, "Managing SqlCommand Objects"it's not that hard. Even if you don't plan to change the target data, the SqlDataAdapter can help make your development faster and less trouble-prone. First, because the SqlDataAdapter automatically handles the SqlConnection object for you, your code won't have to open or close the connection. The SqlDataAdapter also knows how to handle complex SELECT queries that contain two or more SELECT statementseach is used to create a separate DataTable objectas long as you don't expect to update more than one of these tables. Introducing the SqlDataAdapter Fill and Update Methods
The most powerful features of the SqlDataAdapter are the Fill and Update methods. These take on a long list of tasks to ensure that your code need not concern itself with the clearly defined process of populating DataTable objects from rowsets. Basically, the Fill method performs the following operations:
The SqlDataAdapter Update method is just as sophisticated. It's designed to help eliminate considerable code when using ADO.NET to post changes made to an in-memory DataSet and DataTable objects back to the database. The Update method in ADO.NET 2.0 has expanded functionality that permits it to process several DML changes in a single round-trip to the server. This promises to reduce the time wasted making a round-trip to the server for each change made to the DataTable. The operations performed by the Update method include:
As you can see, the SqlDataAdapter is a seemingly complex beast, but it can save you a lot of time and code when working with single-table DataTable objects. When you need to fetch rows from several database tables, you have an important choice to make. As I discussed in Chapter 1, you have to decide whether or not to use SQL Server's ability to create a rowset product by joining two or more database tables, or let ADO.NET informally "join" the two rowsets using DataRelation objects. I'll discuss this issue again and cover the rest of the SqlDataAdapter functionality in Chapters 10 and 11. The SqlBulkCopy Class
Because so many developers try to use ADO.NET to move dozens to many millions of rows from place to place, Microsoft (wisely) decided to include a new class in ADO.NET 2.0SqlBulkCopy. For the first time since DB-Library, developers can program their applications to automate the process of uploading bulk data into SQL Server without having to learn how to program BCP or SSIS. Using SQL Server's unique bulkcopy mode, the SqlBulkCopy class passes data from a SqlDataReader up to a selected SQL Server table. I encourage you to look for places to leverage its powerit can shorten a day-long upload to a few minutes. SqlBulkCopy is designed to read data from any .NET Data Provider that exposes a DataReaderthat includes all of them. This means SqlBulkCopy can import from other SQL Server systems, Oracle, DB2, flat files, or even JET. The SqlDependency Class
Another new feature of ADO.NET leverages the .NET Framework 2.0 and SQL Server 2005's ability to notify your code when selected data changes. This means your code can execute a typical (albeit focused) SELECT command and have an event fire when any server-side changes are made to the data in that rowset. I discuss this in Chapter 11 when describing how to execute SqlCommand objects. The SqlError, SQLException, and SqlErrorCollection Classes
When your application throws a SqlException, you'll want to explore the properties of the SqlError class to see what went wrong. The SqlErrorCollection class is used to manage the set of SqlError objects returned in a SQLException. The SQLClientFactory Class
In situations where you need to build a generic application, you can leverage the "factory" classes to generate appropriate provider-specific classes to implement your data access interfaces. |
Категории