Chapter 16. Database Connectivity

Microsoft ADO.NET is the .NET equivalent of the JDBC API and provides the programmer with consistent access to a variety of data sources. Most commonly, these will be SQL-based relational databases, but they can also be other tabular data sources, such as flat files and spreadsheets. ADO.NET consists of two major components:

  • Data Provider. The Data Provider includes the functionality required to manage a data source, including connection management, transaction support, and data retrieval and manipulation. The Data Provider also acts as a source of data to higher-level data manipulation components such as the DataSet.

  • DataSet. The DataSet is a disconnected, in-memory cache of data. The DataSet provides a simplified relational data model in which data from multiple sources can be loaded and manipulated. Data can be written from a DataSet back to the original data source or forwarded to another component for further processing.

The functionality of the core JDBC API is most directly comparable with the Data Provider. Although the JDBC RowSet interface provides functionality similar to that of a DataSet, the exact capabilities of a RowSet are implementation specific, and Java version 1.4 doesn’t include a concrete implementation. Therefore, we won’t provide a direct comparison of DataSet and RowSet.

Note

References to JDBC in this chapter are to JDBC 3.0 as distributed with Java 2 Standard Edition (J2SE) version 1.4; this includes the javax.sql package that was formerly available as the JDBC 2.0 Optional Package. Throughout this chapter, we assume you have a good understanding of relational databases and structured query language (SQL).

Data Providers

An ADO.NET Data Provider is a set of components that encapsulate the basic mechanisms for connecting to and interacting with a data source. Table 16-1 describes the key architectural elements of a Data Provider.

Table 16-1. Elements of an ADO.NET Data Provider

Element

Comments

Connection

Provides connectivity to the data source, connection pooling, transaction control, and factory methods to create Command components. Comparable to the JDBC java.sql.Connection interface.

Command

Encapsulates data manipulation and query commands. Provides support for parameter configuration and management, as well as command execution. Comparable to the JDBC Statement, PreparedStatement, and CallableStatement interfaces from the java.sql package.

Data Reader

Provides a lightweight, forward-only reader to access the results of an executed command. Comparable to the JDBC ResultSet interface.

Data Adapter

Provides a bridge between a Data Provider and a DataSet. Data Adapters are closely affiliated with DataSet objects, which are discussed in the "DataSet" main section later in this chapter.

Interfaces and Implementations

As with JDBC, the components of an ADO.NET Data Provider are defined by a set of interfaces. Table 16-2 provides a comparison of the key JDBC and Data Provider interfaces. The Java interfaces are all from the java.sql package, while the .NET interfaces are from the System.Data namespace.

Table 16-2. Comparison of Key JDBC and .NET Data Provider Interfaces

Java Interface

.NET Interface

.NET Class

Connection

IDbConnection

OleDbConnection

  

SqlConnection

Statement

IDbCommand

OleDbCommand

PreparedStatement

 

SqlCommand

CallableStatement

  

ResultSet

IDataReader

OleDbDataReader

  

SqlDataReader

Table 16-2 also lists classes from the System.Data.SqlClient and System.Data.OleDb namespaces of the .NET class libraries that provide concrete implementations of each interface; these are components of the following Data Provider implementations provided with .NET:

  • OLE DB.NET Data Provider. Provides connectivity to any data source that implements an OLE DB interface. All class names in this Data Provider implementation are prefixed with the string OleDb.

  • SQL Server.NET Data Provider. Provides optimized connectivity to Microsoft SQL Server 7.0 and later versions. All class names in this Data Provider implementation are prefixed with the string Sql.

There is also a Data Provider implementation for connectivity to ODBC data sources, which requires a separate download from the Microsoft Web site.

Although a Data Provider is defined by interfaces, much of the .NET documentation focuses on the use of the concrete Data Provider implementations. Our approach is to focus predominantly on the use of the Data Provider interfaces; this provides a more accurate comparison with the JDBC model and results in more portable and data source–independent code.

We’ll use the SQL Server Data Provider as the basis for implementation discussions and examples. And we’ll highlight only key features and differences of the OLE DB Data Provider; for complete details, consult the .NET and OLE DB documentation.

Data Provider Sample Code

The DBDemo class, listed below, is a fully functional, if simplistic, application that demonstrates the Data Provider functionality we discuss in this chapter. DBDemo manipulates a simple SQL table containing the name, age, and sex of different people. Specifically, when executed, the DBDemo application does the following:

  1. Creates a connection to a SQL Server.

  2. Creates three records: one each for Bob, Fred, and Betty.

  3. Sets Fred’s age to 28.

  4. Retrieves and displays the name, sex, and age of each person in the SQL table and displays them on the console, producing the following output:

    Bob is 32 years old; Bob is male.
    Fred is 28 years old; Fred is male.
    Betty is 43 years old; Betty is female.
  5. Deletes the records of all people.

  6. Closes the SQL Server connection.

DBDemo doesn’t attempt to exhibit good design or best practice; it’s purely a vehicle to demonstrate how to use a .NET Data Provider. We have listed DBDemo in its entirety here, and we’ll also use appropriate excerpts throughout this chapter to demonstrate the current feature we’re discussing. Before executing the DBDemo application, the reader must

  • Have access to a Microsoft SQL Server. The SQL Server Desktop Engine (MSDE) that ships with Visual Studio .NET is sufficient for this example.

  • Create a database on the SQL Server named MyDataBase, which can be done using the SQL Server Enterprise Manager.

  • Create a people table in the newly created MyDataBase, which can easily be done with the SQL Server Query Analyzer, using the following SQL script:

    CREATE TABLE people (
        name varchar (20) NOT NULL,
        age int NOT NULL,
        sex varchar (10) NOT NULL)
  • Modify the DBDemo code by changing the value of the private DBDemo.sqlServerName field to the name of the SQL Server being accessed. For example, if your SQL Server instance is running on the mySqlHost.com server, you should do the following:

    private string sqlServerName = "mySqlHost.com" ;
  • Build DBDemo using the C# compiler; instructions on using the compiler are available in Chapter 3.

using System;
using System.Data;
using System.Data.SqlClient;

public class DBDemo {

    // Private field to hold DB connection reference
    private IDbConnection sqlCon = null;
    // Private field to hold the SQL Server Name
    private string sqlServerName = "MySQLServer";

    private void Connect() {
        // Create a connection to the specified SQL Server using
        // a database named MyDatabase
        // and integrated Windows security
        string conStr = "Data Source=" + sqlServerName +
            " ; Database=MyDataBase;" +
            " Integrated Security=SSPI";
        sqlCon = new SqlConnection(conStr);
        // Open the SQL Server Connection
        sqlCon.Open();
    }

    private void Disconnect() {
        // Close the SQL Server connection
        sqlCon.Close();
    }

    private void InsertPeople() {
        // Create and start a transaction
        IDbTransaction sqlTx = sqlCon.BeginTransaction();

        // Create the SQL command and assign it to
        // participate in the local transaction
        IDbCommand sqlCmd = sqlCon.CreateCommand();
        sqlCmd.CommandType = CommandType.Text;
        sqlCmd.Transaction = sqlTx;

        try {
            // Insert three records into the "people" table
            sqlCmd.CommandText
                = "INSERT INTO people (name,age,sex)" +
                " VALUES ('Bob', 32, 'male')";
            sqlCmd.ExecuteNonQuery();
            sqlCmd.CommandText
                = "INSERT INTO people (name,age,sex)" +
                " VALUES ('Fred', 27, 'male')";
            sqlCmd.ExecuteNonQuery();
            sqlCmd.CommandText
                = "INSERT INTO people (name,age,sex)" +
                " VALUES ('Betty', 43, 'female')";
            sqlCmd.ExecuteNonQuery();

            // Commit the transaction
            sqlTx.Commit();
        } catch {
            // An exception has occurred,
            // rollback the transaction
            sqlTx.Rollback();
        }
    }

    private void DeletePeople() {
        // Create the SQL command to delete all records from
        // the "people" table
        IDbCommand sqlCmd = sqlCon.CreateCommand();
        sqlCmd.CommandType = CommandType.Text;
        sqlCmd.CommandText = "DELETE FROM people";

        // Execute the DELETE command
        sqlCmd.ExecuteNonQuery();
    }

    private void ListPeople() {
        // Create and configure the SQL command
        IDbCommand sqlCmd = sqlCon.CreateCommand();
        sqlCmd.CommandType = CommandType.Text;
        sqlCmd.CommandText = "SELECT name, sex, age FROM people";

        // Execute the SQL command and create the IDataReader
        IDataReader sqlRdr = sqlCmd.ExecuteReader();

        // Loop through the results and display each record
        while (sqlRdr.Read()) {
            Console.WriteLine("{0} is {1} years old; {0} is {2}.",
                sqlRdr.GetString(0),       // Typed get
                sqlRdr["age"],             // Named indexer
                sqlRdr[1]);                // Integer indexer
        }

        // Close the IDataReader
        sqlRdr.Close();
    }

    public void SetAge(string p_name, int p_age) {
        // Create a Text command to perform an UPDATE
        // the age of a specified person
        IDbCommand sqlCmd = sqlCon.CreateCommand();
        sqlCmd.CommandType = CommandType.Text;
        sqlCmd.CommandText = "UPDATE people SET age = @age" +
            " WHERE name = @name";

        // Instantiate and add parameters, order is irrelevant
        IDbDataParameter nameParam = sqlCmd.CreateParameter();
        nameParam.ParameterName = "@name";
        nameParam.DbType = DbType.String;
        nameParam.Value = p_name;
        sqlCmd.Parameters.Add(nameParam);

        IDbDataParameter ageParam = sqlCmd.CreateParameter();
        ageParam.ParameterName = "@age";
        ageParam.DbType = DbType.Int32;
        ageParam.Value = p_age;
        sqlCmd.Parameters.Add(ageParam);

        // Execute the command
        sqlCmd.ExecuteNonQuery();
    }

    public static void Main() {
        // Instantiate new DBDemo object
        DBDemo dbDemo = new DBDemo();

        // Open database connection
        dbDemo.Connect();

        // Create the demo people records
        dbDemo.InsertPeople();

        // Set the age of "Fred"
        dbDemo.SetAge("Fred", 28);

        // List the people records
        dbDemo.ListPeople();

        // Delete the people records
        dbDemo.DeletePeople();

        // Close the database connection
        dbDemo.Disconnect();
    }
}

Connections

In both JDBC and ADO.NET, a connection to the underlying data source is represented by a Connection object, which provides the starting point for all subsequent interaction with the data source.

Creating and Opening Connections

The IDbConnection interface is the ADO.NET equivalent of the JDBC Connection interface. An explicit instance of the JDBC Connection interface is obtained via the getConnection factory method of the java.sql.DriverManager or javax.sql.DataSource interface. IDbConnection instances are created using constructors; the programmer must explicitly instantiate the appropriate implementation for the underlying data source.

The IDbConnection implementation classes accept a connection string as a constructor argument containing connection configuration parameters in the form of name/value pairs separated by semicolons, similar in nature to a JDBC URL. The connection string can be passed as an argument to the IDbConnection implementation class constructor or set via the ConnectionString property after construction. Invalid or missing values in the connection string cause a System.ArgumentException to be thrown.

Once the IDbConnection has been created, it’s prepared for use by calling the Open method. This method should be called only once for each connection; repeated calls to the Open method cause a System.InvalidOperationException to be thrown. The state of a Connection can be determined using the IDbConnection.State property. State returns one of the following values from the System.Data.ConnectionState enumeration: Broken, Open, or Closed.

The DBDemo.Connect method creates and opens an IDbConnection to a Microsoft SQL Server with the name contained in the sqlServerName field.

private void Connect() {
    // Create a connection to the specified SQL Server using
    // a database named MyDatabase and integrated Windows security
    string conStr = "Data Source=" + sqlServerName +
        " ; Database=MyDataBase;" +
        " Integrated Security=SSPI";
    sqlCon = new SqlConnection(conStr);
    // Open the SQL Server Connection
    sqlCon.Open();
}

Table 16-3 contains a summary of the commonly used parameters for the SqlConnection class.

Table 16-3. Common SqlConnection Configuration Parameters

Parameter

Comments

Application Name

The application name to use for the connection. Defaults to .Net SqlClient Data Provider.

Connect Timeout

The number of seconds the connection will try to connect to the data source before failing and generating an error. Defaults to 15 seconds.

Data Source

The name or address of the Microsoft SQL Server instance with which to connect.

Database

The name of the database to use.

Integrated Security

Determines whether the connection is secure. Valid values are true, false, and sspi, which is equivalent to true. Defaults to false.

Packet Size

Size in bytes of the network packets used to communicate with SQL Server. Defaults to 8192.

Password

The user password to use to connect to the SQL Server.

User ID

The user name to use to connect to the SQL Server.

Workstation ID

The workstation name to use for the connection. Defaults to the local computer name.

Connection Pooling

If implemented by the data source vendor, JDBC Connection instances obtained via the DataSource.getConnection method will be pooled, meaning that a small number of connections will be shared to service requests from a larger number of clients.

All connections obtained through the .NET SQL and OLE Data Provider implementations are pooled by default. The native OLE DB implementation provides connection pooling for the OLE Data Provider, and the SQL Provider implements pooling directly in the .NET classes; consult the .NET documentation for details of the pooling strategy.

Table 16-4 summarizes the connection string parameters that can be used to specify the default connection pooling behavior of the SqlConnection class.

Table 16-4. SqlConnection Parameters for Connection Pooling

Property

Comments

Connection Lifetime

A connection is destroyed if it is older than this value in seconds when it is returned to the pool. The default value of 0 (zero) seconds prevents connections from being destroyed.

Connection Reset

If true, the connection is reset when taken from the pool; if false, it is not reset, avoiding the additional communication with the server. Defaults to true.

Max Pool Size

The maximum number of connections allowed in the pool. Defaults to 100.

Min Pool Size

The minimum number of connections allowed in the pool. Defaults to 0 (zero).

Pooling

When true, the new connection object is taken from or created in an appropriate pool; otherwise, the connection is created independent of pooling. Defaults to true.

Closing a Connection

Connections should always be closed using the IDbConnection.Close method or the Dispose method inherited from the IDisposable interface. If connection pooling is disabled, the underlying connection to the server will be closed; otherwise, the connection is maintained and the IDbConnection instance will be returned to the connection pool for future use. The DBDemo.Disconnect method demonstrates the use of IDbConnection.Close to close a database connection.

private void Disconnect() {
    // Close the SQL Server connection
    sqlCon.Close();
}

Changing the Active Database

The IDbConnection.ChangeDatabase method can be used to change the current database without the need to close and reopen a connection.

Transactions

In JDBC, transactions can be managed through the Connection interface using the setAutoCommit, setTransactionalIsolation, commit, and rollback methods. By default, JDBC utilizes autocommit mode, in which every database communication is handled in a separate transaction. To maintain explicit transaction control, a JDBC application must disable autocommit mode, which is done by passing false to the setAutoCommit method in the Connection interface.

In ADO.NET, a transaction is initiated via the IDbConnection.BeginTransaction method, which returns an IDbTransaction instance through which all transaction management is performed.

Table 16-5 provides a comparison of the classes and methods used to initiate and control transactions in both JDBC and ADO.NET.

Table 16-5. Transaction Control Classes and Methods

JDBC Connection Methods

ADO.NET Equivalent

Connection.setAutoCommit(false)

IDbConnection.BeginTransaction()

Connection.setTransactionIsolationIsolationLevel)

IDbConnection.BeginTransaction(IsolationLevel)

Connection.getTransactionIsolation()

IDbTransaction.IsolationLevel

Connection.commit()

IDbTransaction.Commit()

Connection.rollback()

IDbTransaction.Rollback()

Connection.save()

Not supported by generic interfaces, but the SQL implementation provides an equivalent SqlTransaction.Save method.

The same levels of transactional isolation are supported in both JDBC and ADO.NET. In ADO.NET, these are specified using members of the System.Data.IsolationLevel enumeration, which includes the following values: ReadCommitted, ReadUncommitted, RepeatableRead, and Serializable.

JDBC defines optional support for distributed transactions through the XAConnection and XADataSource interfaces. For both the SQL and OLE Data Provider implementations, the IDbTransaction functionality maps directly to the transactional capabilities of the underlying data source and is not integrated with any distributed transaction manager.

The DBDemo.InsertPeople method creates an IDbTransaction instance to ensure that all records are inserted into the people table atomically. Note that the transaction is created with no arguments, resulting in the default isolation level (ReadCommitted) being utilized. Also, the IDbTransaction instance must be assigned to the Transaction property of the IDbCommand prior to execution to enlist the command in the active transaction. If an exception occurs during the insertion of any of the records, the catch clause will execute and the partially executed transaction will be rolled back by the code contained in the catch clause calling the IDbTransaction.Rollback method.

private void InsertPeople() {
    // Create and start a transaction
    IDbTransaction sqlTx = sqlCon.BeginTransaction();

    // Create the SQL command and assign it to
    // participate in the local transaction
    IDbCommand sqlCmd = sqlCon.CreateCommand();
    sqlCmd.CommandType = CommandType.Text;
    sqlCmd.Transaction = sqlTx;

    try {
        // Insert three records into the "people" table
        sqlCmd.CommandText = "INSERT INTO people (name,age,sex)" +
            " VALUES ('Bob', 32, 'male')";
        sqlCmd.ExecuteNonQuery();
        sqlCmd.CommandText = "INSERT INTO people (name,age,sex)" +
            " VALUES ('Fred', 27, 'male')";
        sqlCmd.ExecuteNonQuery();
        sqlCmd.CommandText = "INSERT INTO people (name,age,sex)" +
            " VALUES ('Betty', 43, 'female')";
        sqlCmd.ExecuteNonQuery();

        // Commit the transaction
        sqlTx.Commit();
    } catch {
        // An exception has occurred, rollback the transaction
        sqlTx.Rollback();
    }
}

Commands

JDBC provides three interfaces to represent SQL commands:

  • Statement. Used to issue SQL commands against the data source

  • PreparedStatement. A SQL statement that supports parameter substitution

  • CallableStatement. Provides support for calling SQL stored procedures

ADO.NET consolidates the major functionality of these three interfaces into a single interface named IDbCommand.

Creating Commands

In both JDBC and ADO.NET, connection objects act as factories for creating command objects. The JDBC Connection interface defines methods for creating instances of the Statement, PreparedStatement, and CallableStatement interfaces: the createStatement, prepareStatement, and prepareCall methods, respectively. The ADO.NET equivalent is the IDbConnection.CreateCommand method; CreateCommand takes no arguments and returns an IDbCommand instance. Use of the CreateCommand method can be seen at the start of the InsertPeople, DeletePeople, and SetAge methods of DBDemo.

Both the SQL and OLE Data Provider implementations of IDbCommand can be instantiated using constructors; however, use of the CreateCommand factory method ensures that the correct implementation of IDbCommand is always instantiated, provides default configuration for IDbCommand, and improves code portability.

Configuring Commands

Once IDbCommand is instantiated, it must be configured using the properties detailed in Table 16-6. If IDbCommand is created via the IDbConnection.CreateCommand method, the Connection property will be set to the correct value for the current IDbConnection. The key properties for any IDbCommand are CommandText and CommandType; the text contained in CommandText must be compatible with the CommandType value.

Table 16-6. Properties of IDbCommand

Property

Comments

CommandText

Gets or sets the text of the command to execute against the data source. The syntax of this text depends on the CommandType property.

CommandTimeout

Gets or sets the time (in seconds) that the executing command waits for a response before terminating and throwing an error. The default value is 30 seconds.

CommandType

Gets or sets the command type represented by this IDbCommand instance. Specified using the following members of the System.Data.CommandType enumeration:

 
  • StoredProcedure—. command text represents the name of a stored procedure.

  • TableDirect—. supported only by OleDbCommand. Command text represents one or more table names for which the entire contents should be returned.

  • Text—. command text represents an SQL command.

 

The default value is Text.

Connection

Gets or sets the IDbConnection instance used by this command.

Transaction

Gets or sets the IDbTransaction instance in which this command executes.

Configuring an IDbCommand to execute a direct SQL statement

When executing a direct SQL statement such as a SELECT or a DELETE command, the CommandType must be set to System.Data.CommandType.Text (the default value), and the CommandText property must be set to the text of the SQL statement. The DBDemo.DeletePeople method demonstrates the configuration of an IDbCommand to execute a SQL DELETE statement:

private void DeletePeople() {
    // Create the SQL command to delete all records from
    // the "people" table
    IDbCommand sqlCmd = sqlCon.CreateCommand();
    sqlCmd.CommandType = CommandType.Text;
    sqlCmd.CommandText = "DELETE FROM people";

    // Execute the DELETE command
    sqlCmd.ExecuteNonQuery();
}

The CommandText can also include parameter identifiers that are substituted with values at run time. The syntax for parameter substitution is implementation specific. The SQL Data Provider supports named parameters prefixed with the @ symbol, as demonstrated in the following excerpt from the DBDemo.SetAge method:

IDbCommand sqlCmd = sqlCon.CreateCommand();
sqlCmd.CommandType = CommandType.Text;
sqlCmd.CommandText = "UPDATE people SET age = @age" +
    " WHERE name = @name";

However, the OLE Data Provider supports only positional parameter substitution using the ? symbol, the same as the JDBC PreparedStatement. The preceding code fragment would appear as follows if implemented using an OLE Data Provider:

IDbCommand sqlCmd = sqlCon.CreateCommand();
sqlCmd.CommandType = CommandType.Text;
sqlCmd.CommandText = "UPDATE people SET age = ?" +
    " WHERE name = ?";

For both positional and named parameters, an IDbDataParameter instance must be provided for each parameter included in the command string. See the Parameters section coming up for details.

Configuring an IDbCommand to execute a stored procedure

When calling a stored procedure, you must set CommandType to System.Data.CommandType.StoredProcedure and the CommandText must be set to the name of the stored procedure. Later in the Parameters section we provide an alternative DBDemo.SetAge implementation that calls a stored procedure to update the age of a person. For now, the following code fragment demonstrates the configuration of an IDbCommand to execute a stored procedure named SetAge. IDbData-Parameter instances must be provided for each input and output parameter specified by the stored procedure as well as the stored procedure’s return value.

// Create a command to call the Stored Procedure SetAge
IDbCommand sqlCmd = sqlCon.CreateCommand();
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.CommandText = "SetAge";

Parameters

Parameters for use with stored procedures or for substitution into text commands are represented by IDbDataParameter instances. An IDbDataParameter object must be instantiated and configured to satisfy each parameter specified in an IDbCommand. The IDbCommand.Parameters property contains an IDataParameterCollection in which the parameter objects are placed before the command is executed. IDataParameterCollection inherits from IList, ICollection, and IEnumerable and thus provides flexibility for adding, retrieving, and manipulating the contained IDbDataParameter instances.

If the IDbCommand uses positional parameters (as with the OLE Data Provider implementation), the IDbDataParameter instances must be placed in the collection in the same order they occur in the statement. Named parameters can be added in any order and will be mapped correctly during command execution. If a stored procedure provides a return value, the parameter to receive the return value must always be the first IDbDataParameter in the collection.

Table 16-7 summarizes the members of IDbDataParameter.

Table 16-7. Members of IDbDataParameter

Member

Comments

DbType

Gets or sets the data type of the parameter using a value from the System.Data.DbType enumeration. The DbType property and the implementationspecific SqlDbType and OleDbType properties are linked such that setting the DbType changes the implementation property to an equivalent implementationspecific data type.

Direction

Gets or sets a value from the System.Data.ParameterDirection enumeration that indicates the type of parameter. Valid values are:

 
  • Input—. an input parameter

  • InputOutput—. capable of both input and output

  • Output—. an output parameter

  • ReturnValue—. a stored procedure or function return value

IsNullable

Gets or sets a bool value indicating whether the parameter accepts null values; the default value is false.

ParameterName

Gets or sets a String value representing the name of the parameter; the default is an empty string.

Precision

Gets or sets the maximum number of digits used to represent numeric Value properties; the default value is 0 (zero).

Scale

Gets or sets the number of decimal places that should be used for numeric Value properties; the default value is 0 (zero).

Size

Gets or sets the maximum size in bytes of the data within the parameter.

Value

Gets or sets the value of the parameter. This property accepts a System.Object instance that the implementation will attempt to convert to the appropriate data source type when used. See DbType, SqlDbType, and OleDbType for details.

The DBDemo.SetAge method demonstrates the creation and configuration of an UPDATE command that uses parameters to change the age of a specified person:

public void SetAge(string p_name, int p_age) {
    // Create a Text command to perform an UPDATE
    // the age of a specified person
    IDbCommand sqlCmd = sqlCon.CreateCommand();
    sqlCmd.CommandType = CommandType.Text;
    sqlCmd.CommandText = "UPDATE people SET age = @age" +
        " WHERE name = @name";

    // Instantiate and add parameters, order is irrelevant
    IDbDataParameter nameParam = sqlCmd.CreateParameter();
    nameParam.ParameterName = "@name";
    nameParam.DbType = DbType.String;
    nameParam.Value = p_name;
    sqlCmd.Parameters.Add(nameParam);

    IDbDataParameter ageParam = sqlCmd.CreateParameter();
    ageParam.ParameterName = "@age";
    ageParam.DbType = DbType.Int32;
    ageParam.Value = p_age;
    sqlCmd.Parameters.Add(ageParam);

    // Execute the command
    sqlCmd.ExecuteNonQuery();
}

We can replace this DBDemo.SetAge implementation with a version that calls a stored procedure to update the person’s age. First create a stored procedure in MyDatabase named SetAge, which can be done with the SQL Server Query Analyzer, using the following CREATE PROCEDURE script.

CREATE PROCEDURE SetAge
    (@name varchar(20), @age int, @sex varchar(10) OUTPUT)
AS
    DECLARE @oldAge int
    SELECT @oldAge = age, @sex = sex
        FROM people WHERE name = @name
    UPDATE people SET age = @age
        WHERE name = @name
    RETURN (@oldAge)

Note that the stored procedure returns the person’s original age as its return value, so the IDbDataParameter instance to receive this value must be added to the IDbCommand.Parameters property first. With these changes in mind, here’s the revised DBDemo.SetAge method:

public void SetAge(string p_name, int p_age) {

    // Create a command to call the Stored Procedure SetAge
    IDbCommand sqlCmd = sqlCon.CreateCommand();
    sqlCmd.CommandType = CommandType.StoredProcedure;
    sqlCmd.CommandText = "SetAge";

    // Instantiate and add parameters, return value must be first
    IDbDataParameter returnParam = sqlCmd.CreateParameter();
    returnParam.ParameterName = "@oldAge";
    returnParam.DbType = DbType.Int32;
    returnParam.Direction = ParameterDirection.ReturnValue;
    sqlCmd.Parameters.Add(returnParam);
    IDbDataParameter nameParam = sqlCmd.CreateParameter();
    nameParam.ParameterName = "@name";
    nameParam.DbType = DbType.String;
    nameParam.Direction = ParameterDirection.Input;
    nameParam.Value = p_name;
    sqlCmd.Parameters.Add(nameParam);

    IDbDataParameter ageParam = sqlCmd.CreateParameter();
    ageParam.ParameterName = "@age";
    ageParam.DbType = DbType.Int32;
    ageParam.Direction = ParameterDirection.Input;
    ageParam.Value = p_age;
    sqlCmd.Parameters.Add(ageParam);

    IDbDataParameter sexParam = sqlCmd.CreateParameter();
    sexParam.ParameterName = "@sex";
    sexParam.DbType = DbType.String;
    sexParam.Direction = ParameterDirection.Output;
    sexParam.Size = 10;
    sqlCmd.Parameters.Add(sexParam);

    // Execute the stored procedure
    sqlCmd.ExecuteNonQuery();

    // Display the return information
    Console.WriteLine("{0} was {1} years old and is a {2};" +
        " {0} is now {3} years old.",
        ((IDbDataParameter)sqlCmd.Parameters["@name"]).Value,
        ((IDbDataParameter)sqlCmd.Parameters["@oldAge"]).Value,
        ((IDbDataParameter)sqlCmd.Parameters["@sex"]).Value,
        ((IDbDataParameter)sqlCmd.Parameters["@age"]).Value);
}

Running DBDemo with the new SetAge method generates the following output:

Fred was 27 years old and is a male; Fred is now 28 years old.
Bob is 32 years old; Bob is male.
Fred is 28 years old; Fred is male.
Betty is 43 years old; Betty is female.

Notice the first line, which is generated by the new DBDemo.SetAge method. It contains Fred’s original age obtained from the stored procedure return value, as well as his sex, which was specified as an OUTPUT parameter.

The process of creating, adding, and accessing parameters is a little longwinded when working with the generic interfaces. Both the SQL and OLE Data Provider implementations provide typed convenience methods that simplify parameter management at the cost of code portability. To demonstrate this, we can cast sqlCon to an instance of SqlConnection (because DBDemo is built using the SQL Server Data Provider) and replace the SetAge method with the following implementation:

public void SetAge(string p_name, int p_age) {

    // Cast sqlCon to a SqlConnection
    SqlConnection newCon = (SqlConnection)sqlCon;

    // Create a command to call the Stored Procedure SetAge
    SqlCommand sqlCmd = newCon.CreateCommand();
    sqlCmd.CommandType = CommandType.StoredProcedure;
    sqlCmd.CommandText = "SetAge";

    // Instantiate and add parameters, return value must be first
    sqlCmd.Parameters.Add("@oldAge", SqlDbType.Int).Direction =
        ParameterDirection.ReturnValue;
    sqlCmd.Parameters.Add("@name", SqlDbType.VarChar).Value = p_name;

    sqlCmd.Parameters.Add("@age", SqlDbType.Int).Value = p_age;
    sqlCmd.Parameters.Add("@sex", SqlDbType.VarChar, 10).Direction =
        ParameterDirection.Output;

    // Execute the stored procedure
    sqlCmd.ExecuteNonQuery();

    // Display the return information
    Console.WriteLine("{0} was {1} years old and is a {2};" +
        "{0} is now {3} years old.",
        sqlCmd.Parameters["@name"].Value,
        sqlCmd.Parameters["@oldAge"].Value,
        sqlCmd.Parameters["@sex"].Value,
        sqlCmd.Parameters["@age"].Value);
}

Executing Commands and Processing Results

Once the properties and parameters of the IDbCommand are configured, the command can be executed; this has been demonstrated many times in the preceding examples. As with JDBC, the method used to execute a command depends on the contents of the command and the type of result expected or desired. Table 16-8 summarizes the methods available for executing a command.

Table 16-8. Command Execution Methods

Member

Comments

IDbCommand

 

ExecuteNonQuery()

Returns an int that contains the number of rows affected by an UPDATE, INSERT, or DELETE SQL command; for other statements, -1 is returned.

ExecuteReader()

Returns an IDataReader instance containing the result sets generated by the executed SELECT query or stored procedure. A discussion of IDataReader follows this table.

ExecuteScalar()

Returns an Object representing the data item from the first column in the first row of a result set returned by a statement, database function, or stored procedure.

SqlCommand

 

ExecuteXmlReader()

Supported only by SQL Server 2000 and later versions. Executes a command that has a FOR XML clause and returns the results in a System.Xml.XmlReader object. While the XmlReader is open, the IDbConnection cannot be accessed other than to close the connection, at which point the XmlReader is also closed. See Chapter 11, for details of the XmlReader class.

IDataReader

The IDataReader provides functionality similar to that of a JDBC ResultSet. However, the IDataReader is a forward-only, read-only mechanism for accessing the results of a SQL query; it provides none of the advanced updating, backward scrolling, or sensitivity features of the JDBC ResultSet interface.

Important

The columns returned in a Java ResultSet are numbered from 1, while the columns returned in a .NET IDataReader are numbered from 0 (zero).

Table 16-9 summarizes the commonly used members of the IDataReader interface.

Table 16-9. IDataReader Members

Member

Comments

IdataReader[key]

Returns an Object representing the value of the specified column in the current row. Columns can be specified using a 0-based integer index or a String containing the column name.

FieldCount

Gets the number of columns in the current row.

IsClosed

Returns true if the IDataReader is closed; false if it is open.

RecordsAffected

Returns an int indicating the number of rows inserted, updated, or deleted by the SQL statement. SELECT statements always return -1.

Close()

Closes the IDataReader and frees the IDbConnection for other uses.

GetDataTypeName()

Gets the name of the data source data type for a specified column.

GetFieldType()

Gets a System.Type instance representing the data type of the value contained in the column specified using a 0-based integer index.

GetName()

Gets the name of the column specified using a 0-based integer index.

GetOrdinal()

Gets the 0-based column ordinal for the column with the specified name.

GetSchemaTable()

Returns a System.Data.DataTable instance that contains metadata describing the columns contained in the IDataReader.

IsDBNull()

Return true if the value in the specified column contains a data source null; false otherwise.

NextResult()

If the IDataReader includes multiple result sets because multiple statements were executed, NextResult moves to the next result set in the collection of result sets. By default, the IDataReader is positioned on the first result set.

Read()

Advances the reader to the next record. The reader starts prior to the first record.

In addition to those members listed in Table 16-9, the IDataReader provides a set of methods for retrieving typed data from the current row of an IDataReader. Each of the following methods takes an integer argument that identifies the 0-based index of the column from which the data should be returned: GetBoolean, GetByte, GetBytes, GetChar, GetChars, GetDateTime, GetDecimal, GetDouble, GetFloat, GetGuid, GetInt16, GetInt32, GetInt64, GetString, GetValue, and GetValues. The GetValue and GetValues methods return the column data as instances of Object.

In both the SQL and OLE Data Provider implementations, if the data type of the column value doesn’t match the method used to retrieve it, an InvalidCastException is thrown; no data conversion is attempted.

The DBDemo.ListPeople method demonstrates the use of an IDataReader to display the contents of a result set by using various methods to access the returned data fields.

private void ListPeople() {
    // Create and configure the SQL command
    IDbCommand sqlCmd = sqlCon.CreateCommand();
    sqlCmd.CommandType = CommandType.Text;
    sqlCmd.CommandText = "SELECT name, sex, age FROM people";

    // Execute the SQL command and create the IDataReader
    IDataReader sqlRdr = sqlCmd.ExecuteReader();

    // Loop through the results and display each record
    while (sqlRdr.Read()) {
        Console.WriteLine("{0} is {1} years old; {0} is {2}.",
            sqlRdr.GetString(0),       // Typed get
            sqlRdr["age"],             // Named indexer
            sqlRdr[1]);                // Integer indexer
    }
}

The individual fields of the current row can be accessed repeatedly and in any order. As we’ve shown previously, the DBDemo.ListPeople method generates the following output:

Bob is 32 years old; Bob is male;
Fred is 28 years old; Fred is male;
Betty is 43 years old; Betty is female;

While the IDataReader is open, no operation can be performed using the IDbConnection other than to close it; closing the connection also closes the IDataReader.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset