Executing Stored Procedures
Stored procedures are a performance-optimized, robust way to encapsulate and compile queries on a database server. In this section, you see how to execute stored procedures using the SQL Server .NET Data Provider and the classes in the System.Data.SqlClient namespace.
Supplying Input Parameters
If we were to revisit our SQL code responsible for inserting employee records and place that code into a stored procedure, we would end up with something like the SQL code shown in Listing 15.4.
Listing 15.4. A Record Insert with Parameters
USE dbHumanRes
GO
CREATE PROCEDURE insert_employee
(@id int,
@first_name varchar(25),
@middle_initial char(1),
@last_name varchar(25),
@extension char(4),
@job_code char(4),
@division_id int)
AS INSERT INTO [dbHumanRes].[dbo].[employee]
(id,
first_name,
middle_initial,
last_name,
extension,
job_code,
division_id)
VALUES
( @id,
@first_name,
@middle_initial,
@last_name,
@extension,
@job_code,
@division_id)
|
This SQL code can be directly compiled as a stored procedure in SQL Server. Notice that the insert_employee stored procedure accepts seven input parameters. Because parameters are parameters in ADO.NET, regardless of whether we are talking about plain SQL commands or stored procedures, our design pattern established with Listing 15.2 doesn't need to change much. Listing 15.5 shows how we can rework our previous parameterized query code to work with a stored procedure.
To help illustrate the differences and similarities between the OLE DB Data Provider and the SQL Server Data Provider, we have written the code this time around using the components from the System.Data.SqlClient namespace and not the System.Data.OleDb namespace.
Listing 15.5. A Record Insert Using a Stored Procedure
Imports System.Data
Imports System.Data.SqlClient
Imports System.Text
Module Module1
Sub Main()
Dim dbConn As SqlConnection
Dim connStr As String
Dim firstName As String = "John"
Dim middleInitial As String = "M"
Dim lastName As String = "Burton"
Dim extension As String = "5001"
Dim jobCode As String = "DEV"
Dim divID As Integer = 3
Try
connStr = "Provider=SQLOLEDB;Data Source=HR-DB01;" & _
"Integrated Security=SSPI;Initial Catalog=dbHumanRes"
dbConn = New SqlConnection(connStr)
Console.WriteLine("Trying db connect with -> { 0} ", connStr)
dbConn.Open()
Dim query As StringBuilder = New StringBuilder("insert_employee")
'Create a new command object using the command text and
'the previously created connection object
Dim insertQry As New SqlCommand(query.ToString, dbConn)
insertQry.Parameters.Add("@last_name", SqlDbType.VarChar, 25)
insertQry.Parameters.Add("@first_name", SqlDbType.VarChar, 25)
insertQry.Parameters.Add("@middle_initial", SqlDbType.Char, 1)
insertQry.Parameters.Add("@extension", SqlDbType.Char, 4)
insertQry.Parameters.Add("@job_code", SqlDbType.Char, 4)
insertQry.Parameters.Add("@division_id", SqlDbType.Int, 4)
'Flag the nature of the command text...
insertQry.CommandType = CommandType.StoredProcedure
Console.WriteLine("Trying command -> { 0} ", query.ToString)
Dim rowsAffected As Integer = insertQry.ExecuteNonQuery()
Console.WriteLine("{ 0} rows inserted.", rowsAffected)
Catch appErr As Exception
Console.WriteLine("An error occurred: { 0} ", appErr.Message
Finally
dbConn.Close()
End Try
End Sub
End Module
|
The parameter statements look a bit different from our previous example, as well. Notice that we are free to use named parameters when working with the SqlClient namespace. That is, we can actually identify a parameter by its name (as defined and declared inside the target stored procedure). We don't have to pass parameters in along a fixed order. We are also specifying the data type and size. These should match the parameter declarations in the stored procedures as well. Table 15.12 shows the possible SqlDbType enumeration values.
Table 15.12. The SqlDbTypes EnumerationMember
| Description
|
---|
BigInt
| A 64-bit signed integer (equivalent to System.Int64)
|
Binary
| A fixed-length stream of binary data ranging between 1 and 8,000 bytes
|
Bit
| A bit value that can be 0, 1, or null (equivalent to System.Boolean)
|
Char
| A fixed-length stream with a minimum of 1 and a maximum of 8,000 characters (equivalent to System.String)
|
DateTime
| A date and time value (equivalent to System.DateTime)
|
Decimal
| A numeric value with fixed precision and scale (equivalent to System.Decimal)
|
Float
| A floating-point value (equivalent to System.Double)
|
Image
| A stream of binary data, variable in length
|
Int
| A 32-bit signed integer (equivalent to System.Int32)
|
Money
| A floating-point currency value; has an accuracy to the ten- thousandth of a currency unit (equivalent to System.Decimal)
|
NChar
| A fixed-length stream of 1 to 4,000 Unicode characters (data type equivalent: System.String)
|
NText
| A variable-length stream of 1 to 1,073,741,823 characters (data type equivalent: System.String)
|
NVarChar
| A variable-length stream of 1 to 4,000 Unicode characters (data type equivalent: System.String)
|
Real
| A floating-point value (data type equivalent: System.Single)
|
SmallDateTime
| A date and time data value (data type equivalent: System.DateTime)
|
SmallInt
| A 16-bit signed integer (data type equivalent: System.Int16)
|
SmallMoney
| A currency value with an accuracy to the ten-thousandth of a currency unit (data type equivalent: System.Decimal)
|
Text
| A variable-length stream of 1 to 2,147,483,647 non-Unicode characters (data type equivalent: System.String)
|
Timestamp
| A date and time value in the format of yyyymmddhhmmss (data type equivalent: System.DateTime)
|
TinyInt
| An 8-bit unsigned integer (data type equivalent: System.Byte)
|
UniqueIdentifier
| A globally unique identifier (data type equivalent: System.GUID)
|
VarBinary
| A variable-length stream of binary data of 1 to 8,000 bytes
|
VarChar
| A variable-length stream of 1 to 8,000 non-Unicode characters (data type equivalent: System.String)
|
Variant
| A data type that can accommodate varying data formats, including numeric, string, and binary data (data type equivalent: System.Object)
|
Stored procedures also have the capability to place values in output parameters.
Capturing Output Parameters
Listing 15.6 shows a new version of the stored procedure from Listing 15.5. In this version, we have decided to return the ID of the recently inserted row as an output parameter. To do this, we have added one more parameter, @new_id, which is identified in the parameters list as an OUTPUT parameter. We have also added a SELECT statement at the end of the stored procedure, which assigns the current value of @@IDENTITY (a SQL Server system function) into the @new_id parameter. The @@IDENTITY function will return the last assigned identity value used by SQL Server. Because the primary key ID column for the employee table is defined as an identity column, the value we see returned here should be the assigned ID value for the employee record that we have just inserted.
Listing 15.6. Insert Stored Procedure with an Output Parameter
USE dbHumanRes
GO
CREATE PROCEDURE insert_employee
(@id int,
@first_name varchar(25),
@middle_initial char(1),
@last_name varchar(25),
@extension char(4),
@job_code char(4),
@division_id int,
@new_id int OUTPUT)
AS INSERT INTO [dbHumanRes].[dbo].[employee]
(id,
first_name,
middle_initial,
last_name,
extension,
job_code,
division_id)
VALUES
( @id,
@first_name,
@middle_initial,
@last_name,
@extension,
@job_code,
@division_id)
SET @new_id = @@IDENTITY
|
With this stored procedure in mind, let's consider the changes that we would have to make to our applet. First, we would need another addition to our parameters collection to account for the new @new_id
parameter:
insertQry.Parameters.Add("@new_id", SqlDbType.Int, 4)
There is a problem here: The Add method doesn't allow you to specify a “direction” to the parameter. In other words, there is no way for you to indicate that the @new_id parameter is not an input parameter but is instead an output parameter. However, a property is available on the SqlParameter and OleDbParameter classes that enables you to specify a direction. The Direction property returns or accepts a ParameterDirection enumeration value. All the possible ParameterDirection values are listed in Table 15.13.
Table 15.13. The ParameterDirection EnumerationMember
| Description
|
---|
Input
| The parameter is an input parameter.
|
InputOutput
| The parameter can function both as an input parameter and an output parameter.
|
Output
| The parameter is an output parameter.
|
ReturnValue
| The parameter is a return value.
|
The Parameters.Add method actually returns a parameter instance (in this case, SqlParameter). With all our previous parameter additions, we have not worried about “catching” this returned parameter object. Here, because we need to set the Parameter.Direction property, we do need to worry about it. The code change is simple:
Dim parm As SqlParameter
parm = insertQry.Parameters.Add("@new_id", SqlDbType.Int, 4)
parm.Direction = ParameterDirection.Output
To examine the contents of the output parameter after the stored procedure has been executed, you would just reference the Value property of the appropriate SqlParameter object like this:
Console.WriteLine("Assigned ID: { 0} ", _
insertQry.Parameters("@new_id").Value)
Stored procedures also have the capability to return values outside the parameters interface with a Return statement.
Capturing Return Values
To see return values in action, let's take one more pass at our stored procedure. This time, we will be adding code that checks for a special value of 9999 for the employee's extension (perhaps an extension of 9999 is an indication to an operations group that the employee has a phone but still needs his extension configured). Put aside the question of whether or not the stored procedure is the correct place to place logic like this—this example will at least let us verify the syntax for capturing the return value.
Listing 15.7 shows our insert procedure, revised yet again.
Listing 15.7. Insert Stored Procedure with a Return Statement
CREATE PROCEDURE insert_employee
(@id int,
@first_name varchar(25),
@middle_initial char(1),
@last_name varchar(25),
@extension char(4),
@job_code char(4),
@division_id int,
@new_id int OUTPUT)
AS INSERT INTO [dbHumanRes].[dbo].[employee]
(id,
first_name,
middle_initial,
last_name,
extension,
job_code,
division_id)
VALUES
( @id,
@first_name,
@middle_initial,
@last_name,
@extension,
@job_code,
@division_id)
SET @new_id = @@IDENTITY
IF (SELECT (@extension)) = '9999'
Return 1
Else
Return 0
|
Even though return values aren't technically treated as part of the parameters collection by SQL Server, they are lumped into that category by the SqlClient namespace. In fact, they are treated the same way as all other parameters with the exception of their Direction property, which should be set to ParameterDirection.ReturnValue:
parm = insertQry.Parameters.Add("@ext_flag", SqlDbType.Int, 4)
parm.Direction = ParameterDirection.ReturnValue
Note
The OLE DB .NET Data Provider requires that any return parameters be added to the parameters collection first. The SQL Server provider does not suffer from this limitation.
You can examine the return value in an identical fashion to the way that we examined the contents of the output parameter:
If insertQry.Parameters("ext_flag").Value <> 0 Then
Console.WriteLine("Phone needs configuration")
End If
Listing 15.8 pulls all these concepts together.
Listing 15.8. Working with Output from a Stored Procedure
Imports System.Data
Imports System.Data.SqlClient
Imports System.Text
Module Module1
Sub Main()
Dim dbConn As SqlConnection
Dim connStr As String
Dim firstName As String = "John"
Dim middleInitial As String = "M"
Dim lastName As String = "Burton"
Dim extension As String = "5001"
Dim jobCode As String = "DEV"
Dim divID As Integer = 3
Try
connStr = "Provider=SQLOLEDB;Data Source=HR-DB01;" & _
"Integrated Security=SSPI;Initial Catalog=dbHumanRes"
dbConn = New SqlConnection(connStr)
Console.WriteLine("Trying db connect with -> { 0} ", connStr)
dbConn.Open()
Dim query As StringBuilder = New StringBuilder("insert_employee")
'Create a new command object using the command text and
'the previously created connection object
Dim insertQry As New SqlCommand(query.ToString, dbConn)
insertQry.Parameters.Add("@last_name", SqlDbType.VarChar, 25)
insertQry.Parameters.Add("@first_name", SqlDbType.VarChar, 25)
insertQry.Parameters.Add("@middle_initial", SqlDbType.Char, 1)
insertQry.Parameters.Add("@extension", SqlDbType.Char, 4)
insertQry.Parameters.Add("@job_code", SqlDbType.Char, 4)
insertQry.Parameters.Add("@division_id", SqlDbType.Int, 4)
Dim parm As SqlParameter
parm = insertQry.Parameters.Add("@new_id", SqlDbType.Int, 4)
parm.Direction = ParameterDirection.Output
parm = insertQry.Parameters.Add("@ext_flag", SqlDbType.Int, 4)
parm.Direction = ParameterDirection.ReturnValue
insertQry.CommandType = CommandType.StoredProcedure
Console.WriteLine("Trying command -> { 0} ", query.ToString)
Dim rowsAffected As Integer = insertQry.ExecuteNonQuery()
Console.WriteLine("{ 0} rows inserted.", rowsAffected)
Console.WriteLine("Assigned ID: { 0} ", _
insertQry.Parameters("@new_id").Value)
If insertQry.Parameters("ext_flag").Value <> 0 Then
Console.WriteLine("Phone needs configuration")
End If
Catch appErr As Exception
Console.WriteLine("An error occurred: { 0} ", appErr.Message)
Finally
dbConn.Close()
End Try
End Sub
End Module
|