A well-designed application that uses a relational database management system in the backend should make extensive use of stored procedures.

A stored procedure is a named collection of SQL statements that you store in a database. To the client, a stored procedure acts similar to a function. You call the stored procedure by name, you can pass it parameter values, and it can return parameter values back to your client.

There are many advantages to incorporating stored procedures into your application logic including:

  • Shared application logic among various client applications
  • Faster execution
  • Reduced network traffic
  • Improved database security

This article will show you how to use stored procedures in conjunction with the SqlDataAdapter in order to fill and update data contained in a DataSet.

Creating a Stored Procedure

Creating a stored procedure is a fairly straightforward process which you can complete inside the Visual Studio .NET IDE.

Figure 1: Stored Procedures node.
Figure 1: Stored Procedures node.

Visual Studio .NET creates the following code template in the Code Editor window.

CREATE PROCEDURE dbo.StoredProcedure1
/*
   (
      @parameter1 datatype = 
        default value,
      @parameter2 datatype OUTPUT
   )
*/
AS
   /* SET NOCOUNT ON */
   RETURN

The CREATE PROCEDURE statement indicates that you'll create a new stored procedure called StoredProcedure1. After you've declared the procedure name, you'll declare any parameters that the stored procedure will use. The AS keyword follows the parameter declarations, and then you can see the SQL code that makes up the body of the stored procedure. You can use the RETURN keyword to exit from the stored procedure. This is where you can send an integer status value back to the caller. The following code creates a simple stored procedure that takes no parameters and returns a result set back to the caller.

Once you've entered the code into the Code Editor window, save the stored procedure. Your saved stored procedure should show up under the Stored Procedure node in the Server Explorer window. Notice that the CREATE keyword has been changed to the ALTER keyword in the Code Editor window. You can use the ALTER keyword to make any changes to existing stored procedures. To test the stored procedure, right-click the procedure's node in the Server Explorer window and choose Run Stored Procedure. SQL Server writes the output from the stored procedure to the Output window. It should contain a list of the publisher information and a return value of zero as shown in Figure 2.

Figure 2: Output windows.
Figure 2: Output windows.

Creating a Stored Procedure with Parameters

Now that you know how to create a basic stored procedure, let's create a more advanced stored procedure that includes parameters. Navigate to and expand the Pubs database node in Visual Studio .NET's Server Explorer window. Right-click on the stored procedure node and select Create a New Stored Procedure from the popup menu. Change the name of the stored procedure to up_UpdatePubInfo and add the following code to the body of the stored procedure.

The difference between this stored procedure and the previous one is that this stored procedure uses parameter values. You declare the parameters of the stored procedure as local variables by preceding the parameter's name with an @ sign. In addition to the name, you define the data type and the direction of the parameter. The caller of the stored procedure passes in the necessary input parameters. Although not used in this stored procedure, you can use output parameters to return singleton values (such as a COUNT or SUM) back to the caller. The OUTPUT keyword designates the parameter as an output parameter. This stored procedure uses input parameters to update the publisher information in the database. Once you have entered the code into the Code Editor window, save the stored procedure.

Note: Stored procedures can also use a Return parameter and an InputOutput parameter type. For more information on these types, consult SQL Server Books Online.

To test the stored procedure, right-click the procedure's node in the Server Explorer window and choose Run Stored Procedure. A parameter input window displays as shown in Figure 3. Enter some test data making sure you use a pub_id that corresponds to an existing publisher and click OK to run the stored procedure. After you execute the stored procedure, right-click the publisher table node in the Server Explorer window and choose Retrieve Data from Table. Verify that the changes updated to the database.

Figure 3: Parameter input windows.
Figure 3: Parameter input windows.

Using the SQLDataAdapter Object in Conjunction with a Stored Procedure to fill a DataSet

In order to fill a DataSet with the publisher information, you can use a SqlDataAdapter as a broker between the SQL Server and the DataSet. The Fill method of the SqlDataAdapter retrieves the data from the database and populates the DataSet. Before you can execute the Fill method of the SqlDataAdapter, you must execute its SelectCommand property to a valid SqlCommand object. This SelectCommand is responsible for executing the T-SQL statement that returns the result set, which in turn fills the DataSet object.

In order to demonstrate how to execute a stored procedure to fill a DataSet, create a new Windows Application project in Visual Studio. Add a class to the project and rename it Publishers. Add an Imports statement above the class definition to import the SqlClient namespace. Declare a private, class-level instance of each of the following classes: SqlConnection, SqlDataAdapter, SqlCommand, and DataSet.

Imports System.Data.SqlClient

Public Class Publishers
    Private cnPubs As SqlConnection
    Private daPubs As SqlDataAdapter
    Private cmdSelPubInfo As
       SqlCommand
    Private dsPubs As DataSet
End Class

**Create a class constructor (Sub New) **

In the body of the constructor, instantiate the Connection object and pass in the connection string information. Instantiate the SELECT command and set the Connection, CommandType, and CommandText properties. Instantiate the DataAdapter and set its SelectComand property to the cmdSelPubInfo object. Finally, instantiate the DataSet object, which will hold the result set returned from the database.

Public Sub New()
  'Connection
  Dim strCon As String
  strCon = "server=localhost;"
  strCon += _
    "integrated security=true;"
  strCon += "database=pubs"
  cnPubs = New SqlConnection(strCon)

  'select command
  cmdSelPubInfo = New SqlCommand()
  cmdSelPubInfo.Connection = cnPubs
  cmdSelPubInfo.CommandType = _
    CommandType.StoredProcedure
  cmdSelPubInfo.CommandText = _
    "up_GetPubInfo"

  ' DataApapter
  daPubs = New SqlDataAdapter()
  daPubs.SelectCommand = _
    cmdSelPubInfo

  'Dataset
  dsPubs = New DataSet()
End Sub

Note: This assumes you have a local instance of SQL Server and are logged on with a trusted connection.

Create a function procedure called GetPubInfo in the class that takes no input parameters and returns a DataSet to the caller. In the body of the function, use the Fill method of the DataAdapter to fill the DataSet, then return it to the caller.

Public Function GetPubInfo() As _
    DataSet
  daPubs.Fill(dsPubs)
  Return dsPubs
End Function

Note: Error handling has been omitted for clarity.

The Fill method of the DataAdapter will implicitly open the connection if it is closed and close it after the data is retrieved. If you explicitly open the connection, however, the DataAdapter will not close it.

To test the method, place a DataGrid on Form1. Switch to the Code Editor window and declare a private class level instance of the Publishers class and the DataSet class after the Inherits statement.

Public Class Form1
    Inherits System.Windows.Forms.Form
    Private objPublishers As 
      Publishers
    Private dsPubInfo As DataSet

In the Forms constructor code, instantiate a new instance of the Publishers class. Set dsPubInfo to the DataSet returned by calling the GetPubInfo method of the Publishers class. Set the DataSource property of the DataGrid to the first (and only) table in the dsPubInfo tables collection.

Public Sub New()
    MyBase.New()

    'This call is required by the 
    'Windows Form Designer.
    InitializeComponent()

    'Add any initialization after 
    'the InitializeComponent() 
    'call
    objPublishers = New _
        Publishers()
    dsPubInfo = _
        objPublishers.GetPubInfo()
    Me.DataGrid1.DataSource = _
        dsPubInfo.Tables(0)
End Sub

Run the application in the debugger and verify that the results are displayed in the grid.

Updating Data Using the SqlDataAdapter in Conjunction with a Stored Procedure

In addition to the SelectCommand property, the SqlDataAdapter object includes the UpdateCommand, InsertCommand, and the DeleteCommand properties. The Update method of the SqlDataAdapter will call the appropriate SqlCommand object for each updated, inserted, or deleted DataRow in the DataTable passed with the update call. When updating data, input parameters pass the values of the updated fields to the stored procedure.

The SqlParameter class encapsulates properties and methods for working with parameters. The SqlParameter class includes properties such as the ParameterName, SqlDBType, Direction, Size, Value, SourceColumn, and SourceVersion. The ParameterName, SqlDBType, Direction, and Size properties are set to match the parameter definition in the stored procedure. For example, the following code creates a parameter object that matches the @pub_id parameter in the up_UpdatePubInfo stored procedure created previously.

Dim UpdParam As New 
  SqlParameter("@pub_id",
  SqlDbType.Char, 4)

In this case, an overloaded constructor of the SqlParameter class sets the appropriate properties. Although the Direction property has not been explicitly set, Input is the default value. If the parameter direction is InputOutput, Output, or ReturnValue, you must explicitly set the direction. For example, the following code explicitly sets the Direction property of a SqlParameter object.

The SourceColumn property maps a DataColumn from the DataTable passed in when you call the Update method of the SqlDataAdapter object. This mapping allows the implicit loading of the SqlParameter's Value property from the DataTable during updates. If you do not set the SourceColumn parameter, you must explicitly set the Value property of the SqlParameter object.

The SourceVersion property defaults to a value of Current, which is the current value of the field in the DataRow and is the value that you intend to update in your database. You can also set the SourceVersion of a SqlParameter object to a value of Original, which is the value of the field when the DataTable was originally loaded from the database. Passing both of these values to a stored procedure allows for concurrency checking before the data update takes place. The following stored procedure uses the original value to check if another user has changed the data before it performs an update.

In order to execute this stored procedure, you define an @Original_pub_name SqlParameter object that uses the Original value of the field as its SourceVersion.

Note: Concurrency issues in disconnected data scenarios are an important topic. You should read more about this issue.

Once you've defined a SqlParameter object for each parameter identified in the stored procedure, you can add the SqlParameter objects to the Parameters collection of the SqlCommand object responsible for executing the stored procedure. The following code demonstrates adding a SqlParameter object to the Parameters collection.

cmdUpdPubInfo.Parameters.Add(UpdParam)

As an alternative, the overloaded Add method allows you to create the SqlParameter object and add it to the collection in one step.

The Fill method of the DataAdapter will implicitly open the connection if it is closed and close it after the data is retrieved. If you explicitly open the connection, however, the DataAdapter will not close it.

In order to demonstrate how to use a SqlDataAdapter object in conjunction with a stored procedure to update data, open the Windows Application project you created earlier. Using the Server Explorer window, create the up_UpdPubName stored procedure in the Pubs database. Open the Publishers class in the Code Editor and add the following class-level declaration to create a SqlCommand object.

Private cmdUpdPubInfo As SqlCommand

In the body of the class constructor, add code to instantiate the update command and set the Connection, CommandType, and CommandText properties.

'Update command
cmdUpdPubInfo = New SqlCommand()
cmdUpdPubInfo.Connection = cnPubs
cmdUpdPubInfo.CommandType = _
  CommandType.StoredProcedure
cmdUpdPubInfo.CommandText = _
  "up_UpdPubName"

Next, define and add the necessary update parameters to the Parameters collection.

'Update command parameters

Add code after the DataAdapter instantiation to set the UpdateCommand property of the DataAdapter to the cmdUpdPubInfo object.

daPubs.UpdateCommand = cmdUpdPubInfo

Create a subprocedure called UpdatePubInfo that takes a DataSet object as an input parameter. In the body of the function, call the update method of the DataAdapter object and pass in the DataSet.

Add a button to Form1 and add code to the button's Update event that calls the UpdatePubInfo method of objPublisher. First, check if the DataSet has changes by calling the HasChanges method. If there are changes, filter the dsPublisher and pass only the changed rows by calling the GetChanges method of the DataSet.

Run the application in Debug mode. Make changes to the pub_name and click the Update button. Stop the application. Right-click on the publisher table node in the Server Explorer window and choose Retrieve Data from Table. Verify that the changes updated the database.

Now that you are familiar with using a stored procedure to implement updating data through the SqlDataApapter, you can implement data inserts and deletes in a similar manner. This will be an exercise left for the reader. The solution code for this article contains sample code that demonstrates inserts and updates using the SqlDataAdapter.

Summary

This article demonstrated how to use the SqlDataAdapter class and its parameters to update a database. You also read how to use the SqlDataAdapter in conjunction with stored procedures to propagate data changes in a disconnected DataSet back to the database. Encapsulating data update logic in stored procedures is an excellent way to improve the manageability, scalability, and security of your database-driven applications. One area this article does not address is data concurrency. Dealing with data concurrency is vital in a disconnected scenario.