Chapter 6: Developing Database Applications with ADO.NET 189 A string variable containing the server name is passed in to the beginning of this subroutine, and then two SqlConnection objects, cn and cn2, are created that have identical connection strings. In both cases, the ConnectionString property uses the SERVER keyword to identify the SQL Server instance to connect to and the INTEGRATED SECURITY keyword to specify that Windows integrated security will be used. After the two SqlConnection objects have been created, a Try-Catch loop is used to open the connection to SQL Server and capture any run-time errors. Since the values of these connection strings are identical, they will both be part of the same connection pool. If the connection strings were different in any way, then two separate connection pools would have been created. After the connections have been established, the Close method is used to close each connection. Pooling Related Connection String Keywords While the .NET Framework Data Provider for SQL Server automatically handles connection pooling for you, there are still several connection string keywords that you can use to alter the SQLConnection object’s connection pooling behavior. Table 6-2 presents the ConnectionString values you can use to customize the SQL Server .NET Data Provider’s connection pooling behavior. Name Description Connection Lifetime After a connection is closed, it’s returned to the pool. Then its creation time is compared with the current time and the connection is destroyed if the difference exceeds the value specified by Connection Lifetime. A value of 0 specifies that pooled connections will have the maximum lifespan. Connection Reset When ‘True’, this specifies that the connection is reset when it’s removed from the pool. For Microsoft SQL Server version 7.0, you can set this value to ‘False’ to avoid an additional server round trip after opening a connection. However, the previous connection state and database context will not be reset. Enlist When this value is ‘True’, the connection is automatically created in the current transaction context of the creation thread if a transaction context exists. Max Pool Size Specifies the maximum number of connections allowed in the pool. Min Pool Size Specifies the minimum number of connections maintained in the pool. Pooling When this value is ‘True’, connection pooling is automatically enabled. ‘False’ allows you to turn off connection pooling. Table 6-2 Pooling-Related Connection String Keywords 190 Microsoft SQL Server 2005 Developer’s Guide NOTE For those connection string keywords that contain spaces, the spaces are a required part of the keyword. Using the SqlCommand Object Executing dynamic SQL statements and stored procedures are two of the most common database actions that are required by an application. Dynamic SQL statements are SQL statements that are read by the database server and executed when they are sent to the database server from the client application. When the database receives these SQL statements, they are first parsed to ensure that their syntax is correct, and then the database engine creates an access plan—essentially determining the best way to process the SQL statement—and then executes the statements. Unlike dynamic SQL statements, which are often used for executing SQL DML operations like creating tables or for data access operations like performing ad hoc queries, stored procedures are typically used to perform predefined queries and database update operations. Stored procedures form the backbone of most database applications. The primary difference between dynamic SQL statements and stored procedures is that stored procedures are typically created before the application is executed and reside in the database itself. This gives stored procedures a significant performance advantage over dynamic SQL statements because the jobs of parsing the SQL statement and creating the data access plan have already been completed. It’s worth noting that changes made to data contained in an ADO.NET DataSet can be posted back to the database using dynamic SQL statements created by the SqlCommandBuilder class, or else they can be written back to the database using stored procedures. However, you don’t need to use the DataSet and DataAdapter in order to update the database. In cases where you don’t need the data binding and navigation functions provided by the DataSet, the Command objects can provide a much lighter-weight and more efficient method of updating the database. In the next sections, you’ll see how to use the SqlCommand object to execute an ad hoc query, then to execute a SQL DDL statement to build a table on the target database, followed by two examples using the stored procedure. The first stored procedure example illustrates passing parameters to a stored procedure, and the second example illustrates executing a stored procedure that supplies a return value. Table 6-3 lists all of the different SQL command execution methods supported by both the SqlCommand object and the OleDbCommand object. Chapter 6: Developing Database Applications with ADO.NET 191 Method Description ExecuteNonQuery The ExecuteNonQuery method is used to execute a SQL statement on the connected data source. It is used for DDL statements; action queries like Insert, Update, and Delete operations; as well as ad hoc queries. The number of rows affected is returned, but no output parameters or result sets are returned. ExecuteReader The ExecuteReader method is used to execute a SQL Select statement on the data source. A fast forward–only result is returned. ExecuteScalar The ExecuteScalar method is used to execute a stored procedure or a SQL statement that returns a single scalar value. The first row of the first column of the result set is returned to the calling application. Any other returned values are ignored. ExecuteXMLReader The ExecuteXMLReader method is used to execute a FOR XML SELECT statement that returns an XML data stream from the data source. The ExecuteXMLReader command is compatible only with SQL Server 2000 and later. Executing Dynamic SQL Statements Dynamic SQL provide an extremely flexible mechanism for working with the database. Dynamic SQL allows you to execute ad hoc queries and return the results from action queries, as well as executing SQL DDL statements to create database objects. The following SQLCommandNonQuery subroutine provides an example illustrating how you can use dynamic SQL with the ADO.NET SqlCommand object to check for the existence of a table and conditionally create it if it doesn’t exist: Private Sub SQLCommandNonQuery(cn As SqlConnection) Dim sSQL As String = "" Dim cmd As New SqlCommand(sSQL, cn) Try ' First drop the table sSQL = "IF EXISTS " _ & "(SELECT * FROM dbo.sysobjects WHERE id = " _ & "object_id(N’[Department]’) " _ & "AND OBJECTPROPERTY(id, N’IsUserTable’) = 1) " _ & "DROP TABLE [department]" cmd.CommandText = sSQL Table 6-3 SqlCommand SQL Statement Execution Methods 192 Microsoft SQL Server 2005 Developer’s Guide cmd.ExecuteNonQuery() ' Then create the table sSQL = "CREATE TABLE Department " _ & "(DepartmentID Int NOT NULL, " _ & "DepartmentName Char(25), PRIMARY KEY(DepartmentID))" cmd.CommandText = sSQL cmd.ExecuteNonQuery() Catch e As Exception MsgBox(e.Message) End Try End Sub In the first part of the SQLCommandNonQuery subroutine, you can see where the SQL Server connection object is passed as a parameter. The sSQL variable that will be used to contain the dynamic SQL statements and an instance of the SqlCommand object named cmd are instantiated. In this example, the constructor of the cmd SqlCommand object uses two parameters—the first being a string containing the SQL statement that will be executed and the second being the SqlConnection object that will provide the connection to the target database server. Here the sSQL string is initially empty. Next, a Try-Catch structure is set up to execute the SQL commands. The first action that you can see within the Try-Catch block assigns a SQL statement to the sSQL variable that checks for the existence of the department table. In this SQL statement, you can see that a SELECT statement queries the SQL Server sysobjects table to determine if a User Table named Department exists. If the Department table is found, a DROP TABLE statement will be executed to remove the table from the target database. Otherwise, if the Department table isn’t found, no further action will be taken. In order to actually execute the SQL statement, that value in the sSQL variable is then assigned to the CommandText property of the cmd object, and then the ExcuteNonQuery method of the cmd SqlCommand object is used to send the command to the SQL Server system. The ExecuteNonQuery method is used to execute a SQL statement that doesn’t return a result set or a specific return value. After the first DROP TABLE SQL command has been issued, the same sequence is followed to execute a Create Table command. First the sSQL variable is assigned a SQL CREATE TABLE statement that creates a table named Department that consists of two columns. The first column is an integer data type named DepartmentID, which is also the primary key, and the second column is a 25-character data type named DepartmentName. Then the value in the sSQL variable is copied to the cmd object’s CommandText property, and the ExecuteNonQuery method is called to execute the CREATE TABLE SQL statement. Following the successful completion Chapter 6: Developing Database Applications with ADO.NET 193 of the ExecuteNonQuery method, the Department Table will exist in the database that was earlier identified in the sDB variable. If an error occurs during any of the operations contained in the Try block, the code in the Catch block will be executed, and a message box will be displayed showing the text of the exception condition. Executing Parameterized SQL Statements In addition to executing dynamic SQL statements, the SqlCommand object can also be used to execute stored procedures and parameterized SQL statements. The primary difference between dynamic SQL and prepared SQL is that dynamic SQL statements must be parsed and an access plan must be created before each run. (Technically, some database systems like SQL Server are very smart about the way this is handled, and they will actually store dynamic statements for a period of time. Then when the statement is subsequently executed, the existing access plan will be used. Even so, this depends on the database activity, and with dynamic SQL there’s no guarantee that the plan will be immediately available.) You can think of prepared SQL statements as sort of a cross between stored procedures and dynamic SQL. Like stored procedures, they can accept different parameter values at run time. Like dynamic SQL, they are not persistent in the database. The SQL statement is parsed, and the access plan is created when the application executes the SQL statements. However, unlike dynamic SQL, the prepared SQL is parsed and the access plan is created only once, when the statement is first prepared. Subsequent statement execution takes advantage of the existing access plan. The access plan will typically remain in the procedure cache until the connection is terminated. The following example shows how to create and execute a prepared SQL statement using the ADO. NET SqlCommand object: Private Sub SQLCommandPreparedSQL(cn As SqlConnection) ' Set up the Command object's parameter types Dim cmd As New SqlCommand("INSERT INTO department VALUES" & _ "(@DepartmentID, @DepartmentName)", cn) Dim parmDepartmentID = _ New SqlParameter("@DepartmentID", SqlDbType.Int) parmDepartmentID.Direction = ParameterDirection.Input Dim parmDepartmentName = _ New SqlParameter("@DepartmentName", SqlDbType.Char, 25) parmDepartmentName.Direction = ParameterDirection.Input ' Add the parameter objects to the cmd Parameter’s collection cmd.Parameters.Add(parmDepartmentID) cmd.Parameters.Add(parmDepartmentName) 194 Microsoft SQL Server 2005 Developer’s Guide Try cmd.Prepare() ' Execute the prepared SQL statement to insert 10 rows Dim i As Integer For i = 0 To 10 parmDepartmentID.Value = i parmDepartmentName.Value = "New Department " & CStr(i) cmd.ExecuteNonQuery() Next Catch e As Exception MsgBox(e.Message) End Try End Sub At the top of the CommandPrepareSQL subroutine, you can see where the SqlConnection object named cn is passed in, followed by the creation of a new SqlCommand object named cmd. In this example, the constructor takes two arguments. The first argument is used to assign a SQL statement to the cmd object. This can either be a SQL statement or the name of a stored procedure. Here, the SQL statement is an INSERT statement that adds the values of two columns to the Department table. NOTE The Department table was created in the earlier section of this chapter. The important point to note in this example is the format of the parameter markers that are used in the SQL statement. Parameter markers are used to indicate the replaceable characters in a prepared SQL statement. At run time, these parameters will be replaced with the actual values that are supplied by the SqlCommand object’s Parameters collection. Unlike ADO, which uses the question mark character (?) to indicate replaceable parameters, the SqlCommand object requires that all parameter markers begin with the @ symbol. This example shows two parameter markers: @DepartmentID and @DepartmentName. The second argument of the SqlCommand constructor associates the cmd SqlCommand object with the cn SqlConnection object that was passed in earlier. Next, you can see where two SqlParameter objects are created. The first parameter object, named parmDepartmentID, will be used to supply values to the first parameter marker (@DepartmentID). Likewise, the second parameter object, named parmDepartmentName, will supply the values used by the second replaceable parameter (@DepartmentName). The code example used in this subroutine shows Chapter 6: Developing Database Applications with ADO.NET 195 three parameters being passed to the SqlParameter’s constructor. The first parameter supplies the parameter name. Here you need to make sure that the name supplied to the SqlParameter object’s constructor matches the name that was used in the parameter marker of the prepared SQL statement. The second parameter that’s passed to this overloaded version of the SqlParameter constructor specifies the parameter’s data type. Here the Direction property is set to input using the ParameterDirection.Input enumeration. Table 6-4 lists the valid enumerations for the SqlParameter Direction property. After the SqlParameter objects have been created, the next step is to add them to the SqlCommand object’s Parameters collection. In the previous listings, you can see that you use the Add method of the SqlCommand object’s Parameters collection to add both the parmDepartmentID and parmDepartmentName SqlParameter objects to the cmd SqlCommand object. The order in which you add the SqlParameter objects isn’t important. Next, within the Try-Catch block the Prepare statement is used to prepare the statement. Note that the Prepare method is executed after all of the parameter attributes have been described. NOTE Using the Prepare operation provides an important performance benefit for parameterized queries because it instructs SQL Server to issue an sp_prepare statement, thereby ensuring that the statement will be in the Procedure cache until the statement handle is closed. Next a For-Next loop is used to add ten rows to the newly created Department table. Within the For-Next loop, the Value property of each parameter object is assigned a new data value. For simplicity, the parmDepartmentID parameter is assigned the value of the loop counter contained in the variable i, while the parmDepartmentName parameter is assigned a string containing the literal “New Department” along with the current value of the loop counter. Finally, the SqlCommand object’s ExecuteNonQuery method is used to execute the SQL statement. In this case, ExecuteNonQuery was Enumeration Description ParameterDirection.Input The parameter is an input parameter. ParameterDirection.InputOutput The parameter is capable of both input and output. ParameterDirection.Output The parameter is an output parameter. ParameterDirection.ReturnValue The parameter represents a return value. Table 6-4 SqlParameterDirection Enumeration 196 Microsoft SQL Server 2005 Developer’s Guide used because this example is using a SQL action query that doesn’t return any values. From the SQL Server perspective, running the ExecuteNonQuery method results in the server issuing an sp_execute command to actually perform the insert. NOTE If you need to pass a null value as a parameter, you need to set the parameter to the value DBNull.Value. If an error occurs during any of these operations, the code in the Catch block will be executed and a message box will be displayed showing the text of the exception condition. Executing Stored Procedures with Return Values Stored procedures are the core of most database applications—and for good reason. In addition to their performance benefits, stored procedures can also be a mechanism for restricting data access to the predefined interfaces that are exposed by the stored procedures. Similar to prepared SQL statements, stored procedures get significant performance benefits from the fact that they are compiled before they are used. This allows the database to forgo the typical parsing steps that are required as well as skipping the need to create an access plan. Stored procedures are the true workhorse of most database applications, and they are almost always used for database insert, update, and delete operations, as well as for retrieving single values and results sets. In the following examples, you see how to execute SQL Server stored procedures using the SqlCommand object. In the first example that follows, you’ll see how to execute a stored procedure that accepts a single input parameter and returns a scalar value. The following listing presents the T-SQL source code required to create the CostDiff stored procedure that will be added to the sample AdventureWorks database. You can create this stored procedure by executing this code using SQL Server Management Studio. CREATE PROCEDURE CostDiff @ProductID int AS DECLARE @CostDiff money SELECT CostDiff = (ListPrice - StandardCost) FROM Production.Product WHERE ProductID = @ProductID RETURN @CostDiff Chapter 6: Developing Database Applications with ADO.NET 197 In this listing, you can see that the CostDiff stored procedure accepts a single input parameter. That parameter is an Integer value that’s used to identify the ProductID. The CostDiff stored procedure returns the cost difference of that ProductID from the Production.Product table in the AdventureWorks database. The cost difference is calculated by retrieving the ListPrice number and subtracting it from the value in the StandardCost column. The results are then assigned to the @CostDiff variable, which is returned as a scalar value by the stored procedure. After the sample stored procedure has been created in the AdventureWorks database, it can be called by your ADO.NET applications. The following example shows how to use the SqlCommand class from VB.NET to execute the CostDiff stored procedure and retrieve the scalar value that it returns: Private Sub SQLCommandSPScalar(cn As SqlConnection) ' Create the command object and set the SQL statement Dim cmd As New SqlCommand("CostDiff", cn) cmd.CommandType = CommandType.StoredProcedure ' Create the parameter cmd.Parameters.Add("@ProductID", SqlDbType.Int) cmd.Parameters("@ProductID").Direction = _ ParameterDirection.Input cmd.Parameters("@ProductID").Value = 1 Try Dim nCostDiff As Decimal nCostDiff = cmd.ExecuteScalar() ' Put to textbox on displayed form txtMid.Text = nCostDiff Catch e As Exception MsgBox(e.Message) End Try End Sub In the beginning of this routine you can see where the cn SqlConnection object is passed in, followed by the creation of the SqlCommand object named cmd. In this example, the constructor for the SqlCommand object uses two parameters. The first parameter is a string that accepts the command that will be executed. This can be either a SQL statement or the name of the stored procedure. In this example, you can see that the name of the CostDiff stored procedure is used. The second parameter is used for the name of the SqlConnection object that will be used to connect to the target database. After the cmd SqlCommand object has been created, its CommandType property is set to CommandType.StoredProcedure, indicating that a stored procedure will be executed. The CommandType property can accept any of the values shown in Table 6-5. 198 Microsoft SQL Server 2005 Developer’s Guide CommandType Values Description CommandType.StoredProcedure The command is a stored procedure. CommandType.TableDirect The command is the name of a database table. CommandType.Text The command is a SQL statement. After the SqlCommand object’s CommandType property is set to CommandType. StoredProcedure, the SqlParameter object used to supply the input value to the CostDiff stored procedure is created. SqlParameter objects can be created either by using the SqlParameter class constructor or by executing the SqlCommand object’s Parameters collection Add method. In this example, the parameter is created using the Add method of the SqlCommand object’s Parameters collection. The first parameter supplied to the Add method is a string containing the name of the parameter, in this case “@ProductID”. Again, note that replaceable parameters used by the SqlParameter object must begin with the ampersand symbol (@). The second parameter uses the SqlDbType.Int enumeration to indicate that the parameter will contain an Integer value. The next line sets the Direction property to the value ParameterDirection.Input to indicate that this is an input parameter. Finally, the SqlParameter object’s Value property is set to 1—storing a value of 1 to pass to the CostDiff stored procedure. The next section of code sets up a Try-Catch block to execute the CostDiff stored procedure. The important point to note in the Try-Catch block is that the cmd SqlCommand object’s ExecuteScalar method is used to execute the CostDiff stored procedure and the return value is assigned to the nCostDiff variable. The contents of the nCostDiff variable are then assigned to a text box named txtMid that is defined on the Windows form for this project. As in earlier examples, if the stored procedure fails, a message box showing the error text will be displayed to the end user. Executing Transactions Transactions enable you to group together multiple operations that can be performed as a single unit of work, which helps to ensure database integrity. For instance, transferring funds from your saving account to your checking account involves multiple database operations, and the transfer cannot be considered complete unless all of the operations are successfully completed. A typical transfer from your savings account to your checking account requires two separate but related operations: a withdrawal from your savings account and a deposit to your checking account. If either operation fails, the transfer is not completed. Therefore both of these Table 6-5 CommandType Values . cmd.CommandText = sSQL Table 6-3 SqlCommand SQL Statement Execution Methods 192 Microsoft SQL Server 2005 Developer’s Guide cmd.ExecuteNonQuery() ' Then create the table sSQL = "CREATE. 6-4 SqlParameterDirection Enumeration 196 Microsoft SQL Server 2005 Developer’s Guide used because this example is using a SQL action query that doesn’t return any values. From the SQL Server. Keywords 190 Microsoft SQL Server 2005 Developer’s Guide NOTE For those connection string keywords that contain spaces, the spaces are a required part of the keyword. Using the SqlCommand Object Executing