Chapter 5. Under the Hood with ADO.NET

In This Chapter

  • Using the .NET Framework Data Providers

  • Connecting to data sources

  • Managing connection strings

  • Executing commands

  • ccessing data with DataReaders

  • Filling DataSets with DataAdapters

  • Converting DataSets to XML

  • Using ADO.NET in the real world

Visual Studio provides many controls, designers, editors, and wizards for accessing data in Windows and Web applications. These tools are made possible by ADO.NET, the data access technology of the .NET Framework.

ADO.NET provides a common coding structure for accessing data, regardless of the data source. The Visual Studio data access tools, such as DataSet Designer, generate ADO.NET code behind the scenes for you. This chapter shows you how to "get under the hood" with ADO.NET and write your own ADO.NET code to access your data.

Note

ADO is short for ActiveX Data Objects, the previous version of the data access technologies, before the .NET Framework was introduced.

Meet the Players

The purpose of ADO.NET is to provide a simplified model for data access, regardless of the underlying data source. By using a model like ADO.NET, developers can improve their productivity because they use one data access model — ADO.NET — to access many different kinds of data sources.

ADO.NET provides a set of common components for accessing data:

  • .NET Framework Data Provider: Acts as a bridge between your application and a data source. Providers are available for many popular databases, including SQL Server and Oracle. When a native provider is unavailable for your data source, you use an Open Database Connectivity (ODBC) or Open Database Connectivity database (OLE DB) provider to access your data source.

    Each.NET Framework Data Provider offers a set of services for accessing data. See the following section for details on using data providers.

  • DataReader: Provides forward-only access to a data source one row at a time by using one of the .NET Framework Data Providers. See the section "Reading Data with DataReaders," later in this chapter.

    Note

    DataReaders are a service of the .NET Framework Data Providers. DataReader is listed separately here because it represents an important model for data access. DataSets are populated with DataReaders.

  • DataSet: Provides an in-memory cache of data that's retrieved from a data source by way of one of the .NET Framework Data Providers. See the section "Caching Data with DataSets," later in this chapter.

Figure 5-1 shows the interplay of the .NET Framework Data Providers, DataReaders, and DataSets in ADO.NET.

ADO.NET provides a simple model for accessing data.

Figure 5-1. ADO.NET provides a simple model for accessing data.

You access the components of ADO.NET through the System.Data namespace in .NET.

The services of the .NET framework are organized into groups, or namespaces. To read more about namespaces, see Book V, Chapter 1.

Picking a Provider

ADO.NET uses data providers to provide access to many different kinds of data sources, such as SQL Server and Oracle databases. Table 5-1 summarizes the data providers available in the .NET Framework.

Table 5-1. .NET Framework Data Providers

Data Source

.NET Namespace

What It Accesses

SQL Server

System.Data.SqlClient

SQL Server 7.0 databases and later

OLE DB

System.Data.OleDB

SQL Server 6.5, Oracle, and Microsoft Access databases by using OLE DB

ODBC

System.Data.Odbc

SQL Server, Oracle, and Microsoft Access databases by using ODBC

Oracle

System.Data.OracleClient

Oracle databases by using the Oracle client connectivity software

You should always use the provider that's tuned for your data source. For example, always use the SQL Server provider to access SQL Server 7.0 databases and later and use the Oracle provider to access Oracle databases. Microsoft recommends the OLE DB provider for accessing Microsoft Access databases.

Warning

Never use a Microsoft Access database in a multitiered application. (A multitiered application is one in which several layers each perform different functions in the process, such as data access, business rules, and presentation.) Access isn't an enterprise-quality database and doesn't always perform well across a network. Consider using SQL Server Express Edition if you need a lightweight database.

The SQL Server provider uses its own communication channel for transmitting commands to SQL Server. As a result, the SQL Server provider is faster than the OLE DB and ODBC providers, both of which add their own layers of communication channels.

Note

Always use the .NET Data Framework Provider for SQL Server to access SQL Server 7.0, SQL Server 2000, SQL Server 2005, and SQL Server 2008 databases.

Accessing providers

You access the .NET Framework Data Providers through their .NET namespaces. (Table 5-1 in the preceding section lists the namespaces for each of the .NET Framework Data Providers.) For example, to access the features of the SQL Server provider, type the following namespace in the Code Editor:

System.Data.SqlClient

Because of how ADO.NET is constructed, you can access data without choosing a specific data provider. For example, you can write code that retrieves data from a data source without knowing in advance whether you want to use the SQL Server provider or the Oracle provider. Not choosing a data provider is useful when you need code to be flexible enough to choose your data source at runtime.

When you access data without using a data provider, you write provider-independent code. When you write it, you use the System.Data.Common namespace rather than one of the data provider namespaces listed in Table 5-1.

Writing provider-independent data access code isn't for the faint of heart. To read more about what's involved in writing provider-independent code, look for the topic provider independent code in the index of the Visual Studio 2010 documentation. You can find a good resource for the topic at http://msdn2.microsoft.com/en-us/library/t9f29wbk(VS.80).aspx.

Objectifying the providers

Each.NET Framework Data Provider provides access to a common set of data access features and services. You use the following features and services of the providers to connect to your data source and retrieve data:

  • Connection: Connects your application to a data source

  • Command: Executes an SQL statement against a data source

  • DataReader: Reads a forward-only stream of data one row at a time

  • DataAdapter: Retrieves data for a DataSet and sends updates to the data source

Each .NET Framework Data Provider has its own flavor of the features and services in the preceding list. For example, the DataAdapter for the SQL Server data provider is the SqlDataAdapter.

Figure 5-2 illustrates the relationship among the data providers' features and services.

Each data provider has a set of services for accessing data.

Figure 5-2. Each data provider has a set of services for accessing data.

You access the features and services of the data providers through the provider's namespace. For example, you access the Oracle data provider's DataReader, OracleDataReader, at System.Data.OracleClient. Table 5-1 lists the namespaces for each of the data providers.

The features and services in the preceding list are objects of the .NET Framework Data Providers. Before you can use any of these features, you must create a new instance of the object you want to use. To read more about creating object instances, see Book III, Chapter 3.

Making Connections

Each of the .NET Framework Data Providers has a connection object for establishing a connection to a data source. The connection object is the communication pipeline from the .NET Framework Data Providers and the underlying data source to your application. Table 5-2 lists the connection objects for each data provider.

Table 5-2. .NET Framework Data Provider Connection Objects

Provider

Connection Object

SQL Server

SqlConnection

OleDbConnection

ODBC

OdbcConnection

Oracle

OracleConnection

Connecting to a database

You use the connection object for your data provider to connect to your data source. As you can imagine, connecting to a data source is a prerequisite for retrieving data from the data source. You use a connection object to

  • Pass a connection string to the connection object. A connection string includes your username and password for accessing the data source. The connection object sends the connection string to the data source for validation.

  • Open the connection. Opening a connection allows you to communicate with the data source.

  • Send commands by using the connection. Send queries to retrieve and update the data source. See the section "Using Commands," later in this chapter.

  • Close the connection. Each connection to the data source consumes resources on the server. Always close the connection as soon as you execute your commands.

ADO.NET uses a feature called connection pooling that groups multiple database requests into a single connection. Connections that are exactly the same — same server, same database, same user credentials — are pooled by default with ADO.NET. Pooling connections together so that they can be reused reduces the overhead required to open and close connections.

Tip

You should leave connection pooling enabled. You can disable connection pooling if you're trying to achieve optimized performance for a specific application by using pooling='false' in your connection strings.

You follow the same sequence of events each time you want to access a data source. For example, to use a connection object to access an SQL Server 2008 database, follow these steps:

  1. Pick your .NET Framework Data Provider.

    You should always use the .NET Framework Data Provider for SQL Server to access SQL Server 7.0 databases and later. You access the SQL Server data provider by using the System.Data.SqlClient namespace.

  2. Create a new SqlConnection connection object.

    To create the new SqlConnection object MyConnection, type the following lines of code in the Code Editor:

    • VB

      Dim MyConnection As New System.Data.SqlClient.SqlConnection
    • C#

      System.Data.SqlClient.SqlConnection MyConnection =
         new System.Data.SqlClient.SqlConnection();
  3. Create a new connection string to connect to an SQL Server 2008 database.

    The connection string to connect to the Adventure Works sample database installed on a local instance of SQL Server 2008 Express Edition using integrated security is

    Data Source=(local)sqlexpress;Initial Catalog=AdventureWorks;Integrated
        Security=True

    Visual Studio provides many tools for building connection strings. See the later section "Stringing up connections" for more information on creating and managing connection strings.

  4. Assign the connection string you create in Step 3 to the ConnectionString property of the MyConnection SqlConnection object, as shown in the following example:

    • VB

      MyConnection.ConnectionString = ("Data Source=(local)sqlexpress;Initial
          Catalog=AdventureWorks;Integrated Security=True")
    • C#

      MyConnection.ConnectionString = "Data Source=(local)"+"sqlexpress;Init
          ial  Catalog=AdventureWorks;"+"Integrated Security=True";
  5. Call the Open method in MyConnection to open the database connection with the following line:

    • VB

      MyConnection.Open()
    • C#

      MyConnection.Open();

    The data provider connects to the data source by using the connection string specified in Step 4.

  6. Send commands to the database to retrieve or update data.

    See the upcoming section "Using Commands" to see how to send commands to a data source.

  7. Call the Close method in MyConnection to close the database connection with the following line:

    • VB

      MyConnection.Close()
    • C#

      MyConnection.Close();

Here's the entire code listing for the preceding code example:

VB

Dim MyConnection As New System.Data.SqlClient.SqlConnection
MyConnection.ConnectionString = ("Data Source=(local)sqlexpress;Initial Catalog=
    AdventureWorks;Integrated Security=True")
MyConnection.Open()
'send commands
MyConnection.Close()

C#.

System.Data.SqlClient.SqlConnection MyConnection =
   new System.Data.SqlClient.SqlConnection();
MyConnection.ConnectionString = "Data Source=(local)"+    "sqlexpress;Initial
   Catalog=AdventureWorks;"+ "Integrated Security=True";
MyConnection.Open();
// Send Commands
MyConnection.Close();

To use the connection object for any .NET Framework Data Providers, substitute the name of the provider's connection object listed in Table 5-2 where you see SqlConnection in the preceding code example.

For example, to work with an ODBC connection, type this line:

VB:

Dim MyConnection As New System.Data.Odbc.OdbcConnection

C#:

System.Data.Odbc.OdbcConnection MyConnection =
     new System.Data.Odbc.OdbcConnection();

Tip

Always use structured exception handling every time you open a connection. (See Book III, Chapter 7.)

Closing your connection

You should always explicitly close your connection to your data source. Closing the connection releases resources.

Note

Opening your connections with a Using block is a good way to remember to always close your connections. A Using block consists of starting and ending statements that create and dispose of the resource, respectively. The following code shows a Using block:

VB

Using resource as New resourceType
.
.
.
End Using

C#.

using( resource = new resourcetype )
{
}

You place, between the Using ... End Using statements, statements that access the resource. A database connection is an example of a resource you can use with a Using block, as shown in the following code sample:

VB

Using MySqlConnection As New System.Data.SqlClient.
   SqlConnection()
  MySqlConnection.ConnectionString = ("Data Source=(local)
   sqlexpress;Initial Catalog=
   AdventureWorks;Integrated Security=True")
  MySqlConnection.Open()
  'send commands
End Using

C#.

using( MySqlConnection = new System.Data.SqlClient.SqlConnection() )
{
    MySqlConnection.ConnectionString =
    "Data Source=(local)sqlexpress;Initial Catalog="+
            "AdventureWorks;Integrated Security=True";
    MySqlConnection.Open();
    // Send Commands
}

There's no need to explicitly call the connection object's Close method with the Using block. The End Using statement automatically closes the connection for you.

Note

Instead of creating a new, identical connection to a data source each time, ADO.NET reuses existing connections, in a process known as connection pooling. When you explicitly close your connection, the connection is returned to the connection pool, where it can be reused.

Stringing up connections

Nothing stops you in your tracks faster than an incorrect connection string. Without a valid connection string, your code can't establish a connection to the data source. If you're lucky enough to connect to the same data sources over and over, you have to build a working connection string only once. As long as the data source doesn't change, you can reuse your connection string.

Whether you work with the same data sources day in and day out, or are always using different data sources, Visual Studio and .NET provide many tools to help you build and manage your connection strings. See the next section for more information.

A data provider's connection object uses the connection string to establish a connection to a data source when the connection object's Open method is called. A connection string is a set of name/value pairs (keywords) separated by semicolons, as shown in the following example:

Data Source=(local)sqlexpress;Initial Catalog=AdventureWorks;Integrated
    Security=True

The set of keywords used to create a connection string are determined by the data source. Common name/value pairs used to connect to the SQL Server database are described in Table 5-3.

Table 5-3. Common SQL Server Connection-String Keywords

Keyword

What It Does

Usage

Data Source or Server

Serves as name or network address of server

server=myserver, data source=myserverserver instance, server=(local)

Encrypt

Uses SSL encryption

encrypt=true

Initial Catalog or Database

Sets the name of the database to access

Initial Catalog=AdventureWorks

Integrated Security

Determines whether to use Windows security

integrated security=true

Password or Pwd

Sets the password to use when not using integrated security

password=mypassword

User ID

Sets the user ID to use when not using integrated security

user id=myuserid

Tip

Always use integrated security to access your data sources.

See the topic Securing connection strings in the Visual Studio 2010 documentation for more information. See the Impersonation topic in the Visual Studio 2010 documentation to read more about using integrated security with ASP.NET.

You set a connection string by using the ConnectionString property of the connection object.

Note

ADO.NET uses the ConnectionString property to set the connection object's DataSource and DataBase properties.

Building connection strings

Visual Studio has many ways to help you build connection strings, including

  • Application settings in Windows applications

  • ASP.NET configuration settings

  • The Add Connection dialog box

  • .NET Framework Data Provider connection string builders

The .NET Framework makes extensive use of configuration setting files, such as application settings in Windows applications and configuration settings in ASP.NET, to store all kinds of information related to your application. Visual Studio provides tools for adding settings, such as connection strings, to .NET configuration files. For example, you can use the ASP.NET Web Site Administration Tool to add several predefined and custom settings to your Web site. See Book IV, Chapter 6 for more information on using the Web Site Administration Tool.

Warning

Don't store connection strings in your source code. Your connection strings can be extracted from compiled code, in essence enabling anyone to bypass security measures you put into place.

Adding connection strings to Windows projects

Windows projects store application settings, such as connection strings, in an XML configuration file named app.config.

To add connection strings to the application settings of a Windows project, follow these steps:

  1. Right-click the My Project folder in an existing Windows project to access the project's properties.

  2. Click the Settings tab.

    A grid appears where you enter application settings.

  3. Type MyDbString in the Name cell of the grid.

  4. Select Connection String as the settings type.

  5. Click the ellipsis button in the Value cell.

    The Connection Properties dialog box appears.

  6. Connect to your data source by using the Connection Properties dialog box.

    The dialog box returns a connection string to the Value cell from the Connection Properties dialog box, as shown in Figure 5-3.

    Use the Connection Properties dialog box to build your connection string.

    Figure 5-3. Use the Connection Properties dialog box to build your connection string.

    See the section in Book V, Chapter 2 about connecting to databases for more information on creating a connection string.

  7. Save the settings.

    Visual Studio creates a new app.config file with your connection string.

You access the connection string by using the My.Settings expression when writing code in VB. (Use Properties.Settings when writing code in C#.) See the upcoming section "Using Commands" to see an example of accessing a connection string from application settings.

Adding connection strings to Web projects

Web projects use the web.config file to store configuration settings, such as connection strings. The easiest way to add new connection strings to the web.config file is with the Data Source Configuration Wizard. (Book V, Chapter 1 walks you through using the wizard in the section about adding data to Web Forms).

You can use the Data Source Configuration Wizard to configure many different kinds of data sources. Configuring a database with the wizard prompts you to select an existing data connection or configure a new connection. After creating the configuration string, the wizard saves the connection string in the web.config file, as shown in Figure 5-4.

Use the Data Source Configuration Wizard to save connection strings in the web.config file.

Figure 5-4. Use the Data Source Configuration Wizard to save connection strings in the web.config file.

The wizard writes the connection string to the web.config file. Alternatively, you can add the connection string manually to the web.config file. For example, a connection string for the Adventure Works database might look like this:

<connectionStrings>
   <add name="AdventureWorksConnectionString" connectionString="Data
    Source=(local)sqlexpress;Initial Catalog=AdventureWorks;Integrated
    Security=True"
           providerName="System.Data.SqlClient" />
</connectionStrings>

Add your connection string between the <connectionStrings></connectionStrings> tags.

Building connection strings manually

A common theme in building connection strings is the use of the Add Connection dialog box. It's used in both the application settings for Windows and the configuration settings for Web applications.

You access the Add Connection dialog box by choosing Tools

Building connection strings manually

You can grab the connection string from Server Explorer and reuse it elsewhere, in either code or a configuration-settings file. To copy a connection string from a data connection in Server Explorer, follow these steps:

  1. Create a new data connection by choosing Tools

    Building connection strings manually

    The Add Connection dialog box appears.

  2. Use the Add Connection dialog box to create a connection string.

    A new data connection appears in Server Explorer, as shown in Figure 5-5.

  3. Right-click the data connection in Server Explorer.

    You can open Server Explorer by choosing View

    Building connection strings manually
  4. Choose Properties from the shortcut menu.

    The Properties window appears.

  5. Highlight the ConnectionString property.

  6. Copy and paste the property.

Using the ADO.NET connection string builders

Each of the .NET Framework Data Providers includes a service for building and managing connection strings. The connection string builders provide the properties needed to build a connection string. The builder outputs a properly formatted connection string that you pass to the data provider's connection object. Table 5-4 lists the connection string builders for each data provider.

Copy and paste the ConnectionString property from Server Explorer.

Figure 5-5. Copy and paste the ConnectionString property from Server Explorer.

Table 5-4. Connection String Builders

Data Provider

Object

SQL Server

SqlConnectionStringBuilder

OLE DB

OleDbConnectionStringBuilder

ODBC

OdbcConnectionStringBuilder

Oracle

OracleConnectionStringBuilder

The following code uses the SQL Server data provider's connection string builder, SqlConnectionStringBuilder, to build a connection string for the Adventure Works sample database:

VB

Dim builder As New System.Data.SqlClient.SqlConnectionStringBuilder
builder.DataSource = "(local)sqlexpress"
builder.InitialCatalog = "AdventureWorks"
builder.IntegratedSecurity = True

C#.

System.Data.SqlClient.SqlConnectionStringBuilder builder =
    new System.Data.SqlClient.SqlConnectionStringBuilder();
builder.DataSource = "(local)sqlexpress";
builder.InitialCatalog = "AdventureWorks";
  builder.IntegratedSecurity = True;

You pass the connection string from SqlConnectionStringBuilder to the connection object's ConnectionString property like this:

VB

Dim MyConnection As New System.Data.SqlClient.SqlConnection
MyConnection.ConnectionString = builder.ConnectionString

C#.

System.Data.SqlClient.SqlConnection MyConnection =
     new System.Data.SqlClient.SqlConnection();
MyConnection.ConnectionString = builder.ConnectionString;

Using Commands

You use a .NET Framework Data Provider's Command object to execute queries after connecting to a data source. Each of the data providers has a Command object. To use a Command object, follow these steps:

  1. Associate the Command object with a connection object.

    Use the Connection property to make the association, like this:

    • VB

      MySqlCommand.Connection = MySqlConnection
    • C#

      MySqlCommand.Connection = MySqlConnection;

      The Connection object is the communication pipeline between the command and the data source.

  2. Specify an SQL statement. Use the Command object CommandText property to set the SQL statement.

    For example, to set the SQL statement for an ODBC data provider Command object, type

    • VB

      MyOdbcCommand.CommandText = "SELECT * FROM CUSTOMER"
    • C#

      MyOdbcCommand.CommandText = "SELECT * FROM CUSTOMER";
  3. Call one of the Command object's Execute methods.

    The Execute method runs the SQL statement specified in the Command object CommandText property. The Command object has three Execute methods:

    • ExecuteReader: Returns a DataReader object. Use ExecuteReader any time you want fast access to a forward-only stream of data.

    • ExecuteScalar: Returns a single value. Use ExecuteScalar when you know that your query will return only one value, such as a SELECT Count(*) query.

    • ExecuteNonQuery: Doesn't return any rows. Use ExecuteNonQuery any time you need to execute a query, such as a CreateTable statement, that doesn't return any rows.

Note

You must call the Open method of the data provider's connection object before you call the Command object Execute method.

Most data-centric applications make extensive use of stored procedures for data access. To use a stored procedure with a Command object, you must

  • Set the Command object CommandType property to StoredProcedure.

  • Use the Command object Parameters collection to define the stored procedure's input and output parameters.

To use the following stored procedure to access an SQL Server database by using the SQL Server data provider, follow these steps:

  1. Create the SqlConnection object and set the object's ConnectionString property, as shown in the following example:

    • VB

      Dim MySqlConnection As New System.Data.SqlClient.SqlConnection
      MySqlConnection.ConnectionString = My.Settings.MyDbString
    • C#

      System.Data.SqlClient.SqlConnection MySqlConnection =
           new System.Data.SqlClient.SqlConnection();
      MySqlConnection.ConnectionString =
           Properties.Settings.MyDbString;

    The ConnectionString property accesses the MyDbString connection string from the project's app.config file. See the earlier section "Adding connection strings to Windows projects" for more information about the app.config file.

  2. Create the SqlCommand object by entering the following lines:

    • VB

      Dim MySqlCommand As New System.Data.SqlClient.SqlCommand
    • C#

      System.Data.SqlClient.SqlCommand MySqlCommand =
           new System.Data.SqlClient.SqlCommand();
  3. To set the SqlCommand object Connection property, enter this line:

    • VB

      MySqlCommand.Connection = MySqlConnection
    • C#

      MySqlCommand.Connection = MySqlConnection;
  4. Set the CommandText property of the SqlCommand object to the name of the stored procedure you want to execute, as the following lines show:

    • VB

      MySqlCommand.CommandText = "uspGetEmployeeManagers"
    • C#

      MySqlCommand.CommandText = "uspGetEmployeeManagers";
  5. Set the CommandType property to StoredProcedure by entering the following line:

    • VB

      MySqlCommand.CommandType = CommandType.StoredProcedure
    • C#

      MySqlCommand.CommandType = CommandType.StoredProcedure;
  6. Create a new SqlParameter object, like this:

    • VB

      Dim MyParameter As New System.Data.SqlClient.SqlParameter
    • C#

      System.Data.SqlClient.SqlParameter My Parameter =
           new System.Data.SqlClient.SqlParameter();
  7. To set the ParameterName and Value properties for the SqlParameter, assign values to the properties as shown:

    • VB

      MyParameter.ParameterName = "@EmployeeID"
      MyParameter.Value = "6"
    • C#

      MyParameter.ParameterName = "@EmployeeID";
      MyParameter.Value = "6";
  8. Add the SqlParameter object to the SqlCommand Parameters collection, like this:

    • VB

      MySqlCommand.Parameters.Add(MyParameter)
    • C#

      MySqlCommand.Parameters.Add(MyParameter);
  9. Call the Open method of the SqlConnection object:

    • VB

      MySqlConnection.Open()
    • C#

      MySqlConnection.Open();
  10. Call the Command object's Execute method with the following statement:

    • VB

      MySqlDataReader = MySqlCommand.ExecuteReader()
    • C#

      MySqlDataReader = MySqlCommand.ExecuteReader();

    The Execute method passes the stored procedure from the Command object to the database by using the connection object.

The Command object has a CommandBuilder you can use to automatically generate Command objects for single-table data access. See the topic CommandBuilder object in the Visual Studio 2010 documentation.

Reading Data with DataReaders

ADO.NET DataReaders are old-school data-access services. You use a DataReader any time you need fast, forward-only access to your data. Unlike a DataSet, which retrieves your data into an in-memory database model, there's no storage mechanism with a DataReader. When you retrieve data by using a DataReader, you had better have your "catcher's mitt" open to store the data.

Note

A DataReader is often called a firehose cursor.

Each of the .NET Framework Data Providers provides a DataReader. Table 5-5 lists the DataReader objects for each provider.

Table 5-5. DataReader Objects

Provider

DataReader Object

SQL Server

SqlDataReader

OLE DB

OleDbDataReader

ODBC

OdbcDataReader

Oracle

OracleDataReader

You use the ExecuteReader method of the Command object to retrieve data for a DataReader.

Using a DataReader to retrieve data from a data source involves these steps:

  1. Call the Command object's ExecuteReader method.

    ExecuteReader builds the DataReader.

  2. Call the DataReader Read method to advance to the next record.

    The first time you call Read, the next record is the first record because the DataReader is positioned in front of the first record.

  3. Use the DataReader Get accessors to retrieve data from the row of data.

    The DataReader retrieves one row at a time.

    See the section "Retrieving data with the Get accessors," later in this chapter, to see the DataReader Get accessors in action.

  4. Advance to the next record by using the Read method.

    Because the DataReader retrieves one row at a time from the data source, use common practice to execute the Read method by using a loop.

    See the following section to see the DataReader used with a while loop.

Even though you need only two methods to use the DataReaderExecuteReader and Read — a lot of setup work is involved. The following steps walk you through using a DataReader to retrieve records:

  1. Select a .NET Framework Data Provider, as described in the section "Picking a Provider," earlier in this chapter.

  2. Create a new connection object for the data provider, as described in the earlier section "Making Connections."

  3. Declare a new variable of the DataReader object type for your data provider.

    For example, to declare a new DataReader variable for the ODBC data provider, you would type the following:

    • VB

      Dim odbcReader As System.Data.Odbc.OdbcDataReader
    • C#

      System.Data.Odbc.OdbcDataReader odbcReader =
           new System.Data.Odbc.OdbcDataReader();

    Notice that you aren't using the New keyword to create a new instance of the DataReader object. There are no constructors for DataReaders. Calling the Command object ExecuteReader method builds the DataReader.

  4. Create a new Command object for your data provider to retrieve data for the object, as described in the preceding section.

  5. Call the Open method of the connection object to establish a connection to the data source.

  6. Call the ExecuteReader method of the Command object you create in Step 4 and pass the results to the DataReader object you create in Step 2.

    For example, to call the ExecuteReader method on an OdbcCommand object and pass the results to an OdbcDataReader object you create in Step 3, type the following:

    • VB

      odbcReader = odbcCommand.ExecuteReader()
    • C#

      odbcReader = odbcCommand.ExecuteReader();

    The OdbcCommand object executes the query against the OdbcConnection object when ExecuteReader is called and builds the OdbcDataReader object.

  7. Call the Read method of the DataReader object to retrieve one record from the data source.

    For example, to call the Read method for the OdbcDataReader you create in Step 3, type the following:

    • VB

      odbcReader.Read()
    • C#

      odbcReader.Read();

    The OdbcDataReader object advances to the next record.

    The default position of the DataReader is before the first row in the result set. The Reader method advances the DataReader to the next record.

  8. Retrieve values from the DataReader.

    See the section "Retrieving data with the Get accessors," later in this chapter, for more information about using the DataReader Get accessors to retrieve data.

  9. Close your connection object, as described in the section "Closing your connection," earlier in this chapter.

    Tip

    Close the Connection object by calling the Close method of the connection object or using a Using block.

Here's the entire code listing:

VB

Dim odbcConnection As New System.Data.Odbc.OdbcConnection
Dim odbcReader As System.Data.Odbc.OdbcDataReader
Dim odbcCommand As New System.Data.Odbc.OdbcCommand
odbcConnection.ConnectionString = My.Settings.MyOdbcConnectionString
odbcCommand.Connection = odbcConnection
odbcCommand.CommandText = "SELECT * FROM CUSTOMER"
odbcConnection.Open()
odbcReader = odbcCommand.ExecuteReader()
odbcReader.Read()
'do something here with the data in the row
odbcConnection.Close()

C#.

System.Data.Odbc.OdbcConnection odbcConnection =
     new System.Data.Odbc.OdbcConnection();
System.Data.Odbc.OdbcDataReader odbcReader =
     new System.Data.Odbc.OdbcDataReader();
System.Data.Odbc.OdbcCommand idbcCommand =
     new System.Data.Odbc.OdbcCommand();
odbcConnection.ConnectionString =
     Properites.Settings.MyOdbcConnectionString;
odbcCommand.Connection = odbcConnection;
odbcCommand.CommandText = "SELECT * FROM CUSTOMER";
odbcConnection.Open();
odbcReader = odbcCommand.ExecuteReader();
odbcReader.Read();
//do something here with the data in the row
odbcConnection.Close();

Stepping through data

The DataReader Read method advances the DataReader to the next row in the result set. As long as more rows are present, the Read method returns the value True. The Read method is typically used to test the Read method's return value in a while loop.

A while loop loops through a set of statements as long as a test condition remains True. You can use a while loop to iterate through each row in a DataReader's result set and perform the same action on each row. For example, to use a while loop to step through a SqlDataReader object named MySqlDataReader, type the following lines:

VB

While (MySqlDataReader.Read() = True)
   Me.lstDepartments.Items.Add(MySqlDataReader("Name"))
End While

C#.

while( MySqlDataReader.Read() )
{
     lstDepartments.Items.Add(
       Convert.ToString(MySqlDataReader["Name"]));
}

The preceding example gets the value in column Name of the MySqlDataReader result set and adds the value to the items collection of a lstDepartments list box. The while loop performs the statement inside the loop as long as the Read method returns True. You end up with a list box full of items from the SqlDataReader. See the next section, about how to get data from a row.

Tip

Use the HasRows property of the DataReader to test whether the DataReader has more rows. The HasRows property returns the value True if more rows are present.

Retrieving data with the Get accessors

The DataReader object provides access to a forward-only result set which presents one row at a time. When you're deciding how to work with the data in a row, you have to consider whether you want to work with the data

  • In its native format or by using a typed accessor

  • In a single column in the row or all the columns in the row

DataReader provides several Get accessors for retrieving data. You should retrieve the data by using a typed accessor, such as GetDateTime.

Note

The data types used in the underlying data source aren't the same as the data types used in the .NET Framework. The typed accessors convert the value from its native database type to a .NET Framework type.

DataReader provides several options for retrieving data by using typed accessors. Each DataReader provides a set of common typed Get accessors, such as

  • GetChar: Retrieves data as a char data type

  • GetDateTime: Retrieves data as a DateTime data type

  • GetInt16: Retrieves data as an Int16 data type

  • GetString: Retrieves data as a string data type

SqlDataReader provides special types that work exclusively with SQL Server database types. Examples include

  • GetSqlChars

  • GetSqlDateTime

  • GetSqlInt16

  • GetSqlString

Use GetSql typed accessors when you're using the SqlDataReader. GetSql typed accessors are more precise than the .NET data types.

Tip

If you're unsure of the column's native data type, query the data source. DataReaders provide the GetFieldType and GetDataTypeName methods you can use.

Of course, you can also retrieve data in its native format. DataReader provides several methods, such as

  • GetValue and GetValues

  • Item

When you access the data in its native format, you must ensure that the data is converted to the appropriate .NET Framework data type. For example, the following statement retrieves a column by using the Item method and uses the ToString method to convert it to a string:

VB

myString = MySqlDataReader.Item("Name").ToString()

C#.

myString = MySqlDataReader.Item["Name"].ToString();

To read more about converting data types, see Book III, Chapter 2. Additionally, see the topic mapping data types in the Visual Studio 2010 help documentation to see how native data types are mapped to .NET Framework data types.

Most Get accessors use a zero-based column index to retrieve a column. A zero-based index starts counting elements at zero instead of at one. For example, in a table with the columns CustID, FirstName, and LastName, the FirstName column might have the column index of one. To access the FirstName column by using the GetSqlString accessor, you type the following line:

VB

MySqlDataReader.GetSqlString(1)

C#.

MySqlDataReader.GetSqlString(1);

Using column indexes to retrieve values may be fast for the DataReader, but it's slow and confusing to a programmer. Fortunately, DataReaders have two methods for accessing columns by name:

  • Item: Returns the column's value in its native format

  • GetOrdinal: Returns the index number of the column

Each of these approaches has its drawbacks. If you use the Item method, you must explicitly convert the column to a .NET data type. Using GetOrdinal hits the data source twice — once to get the ordinal and again to retrieve the data by using the ordinal.

Here's an example of using GetOrdinal to access data:

VB

Dim nameCol, groupNameCol As Integer
nameCol = MySqlDataReader.GetOrdinal("Name")
groupNameCol = MySqlDataReader.GetOrdinal("GroupName")
  While (MySqlDataReader.Read() = True)
    myString = String.Format("{0} {1}", MySqlDataReader.
     GetString(nameCol), MySqlDataReader.
     GetString(groupNameCol))
    Me.lstDepartments.Items.Add(myString)
  End While

C#.

int nameCol, groupNameCol;
nameCol = MySqlDataReader.GetOrdinal("Name");
groupNameCol = MySqlDataReader.GetOrdinal("GroupName");
while( MySqlDataReader.Read())
{
  myString = String.Format("{0} {1}", MySqlDataReader.
   GetString(nameCol), MySqlDataReader.
   GetString(groupNameCol));
  lstDepartments.Items.Add(myString)
}

Tip

Use the GetValues method to retrieve all the columns in a row at one time. The GetValues method requires you to pass in an array that the method fills with the columns from the row.

Here's an example of using the GetValues method:

VB

Dim MyArray(MySqlDataReader.FieldCount − 1) As Object
MySqlDataReader.GetValues(MyArray)

C#.

Object[] MyArray = new Object[MySqlDataReader.FieldCount-1];
MySqlDataReader.GetValues(MyArray);

In the first line of this example, you use the DataReader FieldCount property to set the size of the array. In the second line, you pass the array to the GetValues method. The array is filled with the values for the entire row.

The GetValues method retrieves values in their native data formats. The values must be converted to .NET data types.

With the values in the array, you can access them by using the properties and methods of arrays. To read more about using arrays, see Book III, Chapter 2.

The GetValues method, which is a fast way to grab an entire row, is typically used by GetValues to pass the array to the ItemArray method of the DataRow object.

DataRows are rows in a DataTable. You specify ahead of time which columns and data types exist in the DataTable. When you add a new row by using ItemArray, the data is plugged into the columns and converted to the column's data type. The following code fills an array and passes the array to a new DataRow in the myTable DataTable:

VB

MySqlDataReader.GetValues(array)
row = myTable.NewRow()
row.ItemArray = array

C#.

MySqlDataReader.GetValues(array);
row = myTable.NewRow();
  row.ItemArray = array;

The values in the array are converted to the appropriate data type for each column in the DataRow.

Retrieving schema info

The DataReader includes a GetSchemaTable method that you can use to retrieve the schema information about the result set. The GetSchemaTable method returns a DataTable, as shown in the following code:

VB

Dim table As New DataTable
table = MySqlDataReader.GetSchemaTable()

C#.

DataTable table = new DataTable():
  table = MySqlDataReader.GetSchemaTable();

You can use the DataRow's ItemArray method, as described in the preceding section, to add new rows to a table built with GetSchemaTable.

Caching Data with DataSets

DataSets are an important element in data access. ADO.NET provides the DataSet as a built-in memory cache for storing data retrieved from a data source. See the section about understanding DataSets in Book V, Chapter 3.

The workhorse behind the DataSet is the DataAdapter. It provides the following services to the DataSet:

  • Populates the DataSet with data from a data source by using the Fill method

  • Updates a data source with changes made in the DataSet by using the Update method

Each of the .NET Framework Data Providers has a DataAdapter object. Table 5-6 lists the DataAdapter object for each data provider.

Table 5-6. DataAdapter Objects

Data Provider

DataAdapter Object

SQL Server

SqlDataAdapter

Ole DB

OleDbDataAdapter

ODBC

OdbcDataAdapter

Oracle

OracleDataAdapter

The DataAdapter object uses a set of Command objects to send SQL statements to a data source. The DataAdapter object exposes the Command objects by using the following set of properties:

  • SelectCommand

    You must specify a SelectCommand before you can call the DataAdapter Fill method.

  • InsertCommand

  • UpdateCommand

  • DeleteCommand

See the earlier section "Using Commands" to read more about the properties of Command objects.

Filling a DataSet

A DataAdapter acts as a bridge between the DataSet and the data source. Filling a DataSet involves these tasks:

  • Create a DataAdapter and a DataSet.

  • Create a Command object that holds the SELECT statement or stored procedure to retrieve data from the data source.

  • Set the DataAdapter SelectCommand property to the Command object.

  • Call the DataAdapter Fill method and pass in the DataSet.

As is the case with all the data access features of ADO.NET, filling a DataSet isn't as simple as creating a few objects and then calling the Fill method. You have to complete a number of prerequisite steps, such as creating a connection object.

The following example walks you through using a DataAdapter to fill a DataSet:

  1. Pick the .NET Framework Data Provider best suited for your data source, as described in the earlier section "Picking a Provider."

    This example uses the SQL Server data provider to fill a DataSet by using data from the Adventure Works sample database in SQL Server 2008.

  2. Create a new connection and connection string, as described earlier in this chapter, in the section "Making Connections."

    Enter the following lines of code:

    • VB

      Dim MySqlConnection As New System.Data.SqlClient.SqlConnection
      MySqlConnection.ConnectionString = My.Settings.MyDbString
    • C#

      System.Data.SqlClient.SqlConnection MySqlConnection =
           new System.Data.SqlClient.SqlConnection();
          MySqlConnection.ConnectionString =
         Properties.Settings.MyDbString;
  3. Create a new DataAdapter and DataSet, as shown in the following example:

    • VB

      Dim MySqlDataAdapter As New System.Data.SqlClient.SqlDataAdapter()
      Dim MyDataSet As New System.Data.DataSet
    • C#

      System.Data.SqlClient.SqlDataAdapter MySqlDataAdapter =
           new System.Data.SqlClient.SqlDataAdapter();
      System.Data.DataSet MyDataSet =
           new System.Data.DataSet();
  4. Create a new Command object, associate the Command object with your connection object, and set the CommandText property, as described in the earlier section "Using Commands."

    For example, the following code sample creates a new SqlCommand object, associates the object with an SqlConnection object, and sets the SqlCommand object to an SQL statement:

    • VB

      Dim MySelectCommand As New System.Data.SqlClient.SqlCommand
      MySelectCommand.Connection = MySqlConnection
      MySelectCommand.CommandText = "SELECT * FROM HumanResources.Department"
    • C#

      System.Data.SqlClient.SqlCommand MySelectCommand =
           new System.Data.SqlClient.SqlCommand();
      MySelectCommand.Connection = MySqlConnection;
          MySelectCommand.CommandText =
             "SELECT * FROM HumanResources.Department";
  5. Set the DataAdapter's SelectCommand property to the Command object you create in Step 4, as shown in the following:

    • VB

      MySqlDataAdapter.SelectCommand = MySelectCommand
    • C#

      MySqlDataAdapter.SelectCommand = MySelectCommand;
  6. Call the DataAdapter Fill method and pass in the DataSet as a parameter:

    • VB

      MySqlDataAdapter.Fill(MyDataSet)
    • C#

      MySqlDataAdapter.Fill(MyDataSet);

Behind the scenes, the Fill method does the following:

  • Retrieves data from the data source by passing the SQL statement specified in the SelectCommand to a DataReader.

  • Creates a DataTable by using the column information from the data source and adds the rows to the DataTable.

  • Adds the DataTable to the specified DataSet.

Figure 5-6 shows the relationship among the objects of the SQL Server data provider used in the preceding example. The relationships are similar for other .NET Framework data providers.

The SQL Data Adapter is the bridge between the data source and the DataSet.

Figure 5-6. The SQL Data Adapter is the bridge between the data source and the DataSet.

Updating data with the Update method

You call the DataAdapter Update method to send data updates from the DataSet to the data source. The Update method uses InsertCommand, UpdateCommand, and DeleteCommand to update the data source.

The Update method requires you to pass in a DataSet, a DataTable, or an array of DataRows, as shown in the following example:

VB

MySqlDataAdapter.Update(MyDataSet)

C#.

MySqlDataAdapter.Update(MyDataSet);

The DataAdapter uses the Command objects specified in its properties to execute SQL statements against the data source.

Each of the .NET Framework Data Providers has a CommandBuilder object that you can use to automatically generate commands against a single-table data source. CommandBuilder automatically builds the commands for a DataAdapter to use.

To use SqlCommandBuilder with the example from the preceding section, follow these steps:

  1. Create a new SqlCommandBuilder object after Step 5 in the preceding section.

    • VB

      Dim builder As New System.Data.SqlClient.SqlCommandBuilder()
    • C#

      System.Data.SqlClient.SqlCommandBuilder builder =
           new System.Data.SqlClient.SqlCommandBuilder();
  2. Set the SqlCommandBuilder DataAdapter property:

    builder.DataAdapter = MySqlDataAdapter

    The SqlCommandBuilder uses the DataAdapter SelectCommand property to build the Insert, Update, and Delete commands.

  3. Call the Update method instead of the Fill method in Step 6 in the preceding section.

Using TableAdapters

TableAdapter, a new data access feature in ADO.NET, encapsulates all the retrieve and update commands and a connection object for a single table. Visual Studio provides extensive support for creating TableAdapters by using the TableAdapter Configuration Wizard in DataSet Designer.

You can call the commands of a generated TableAdapter by using the standard "dot" notation of IntelliSense in the Code Editor. To access the properties and methods of a generated TableAdapter, follow these steps:

  1. Use DataSet Designer to create a new DataSet with the Department table from the Adventure Works database.

    See the section about exploring DataSet Designer in Book V, Chapter 3 for more information on creating DataSets.

  2. Open a Windows Form and then drag and drop the Department table from the Data Sources window.

    A DepartmentTableAdapter is created.

  3. Double-click the form to access the form's Load event.

    The Code Editor appears.

  4. Type the following code in the Code Editor:

    • VB

      Me.DepartmentTableAdapter.
    • C#

      DepartmentTableAdapter.

    The Code Editor displays a list of properties and methods available for DepartmentTableAdapter, as shown in Figure 5-7.

    Use IntelliSense to access the methods and properties of TableAdapters generated by the designers in Visual Studio.

    Figure 5-7. Use IntelliSense to access the methods and properties of TableAdapters generated by the designers in Visual Studio.

  5. Select a property or method from the list.

    Tip

    Use the TableAdapter Configuration Wizard to generate TableAdapters.

Using transactions

SQL statements are executed by databases in transactions. A transaction is a group of statements executed against a database. You use statements such as BEGIN TRANSACTION and COMMIT WORK to mark the start and end of a transaction. Between the start and end are SQL statements that retrieve and modify data. The ROLLBACK WORK statement rolls back, or undoes, the statements executed in the transaction.

The .NET Framework provides a new model for transaction processing with the System.Transactions namespace. You use it to create two kinds of database transactions:

  • Implicit transactions: Use the TransactionScope object to encapsulate a block of ADO.NET code in a transaction, as shown in this code:

    • VB

      Using scope As New System.Transactions.TransactionScope()
      .
      .
      .
      End Using
    • C#

      using(scope = new System.Transactions.TransactionScope())
      {
      .
      .
      .
      }
  • Explicit transaction: Create a CommittableTransaction object where you specifically call the object's Commit and Rollback methods. The following code creates a new CommittableTransaction object:

    • VB

      Dim tx As New System.Transactions.CommittableTransaction
    • C#

      System.Transactions.CommittableTransaction txt =
            new System.Transactions.CommittableTransaction();

Note

You must add a reference to System.Transactions before you can access the objects in the namespace.

Note

The System.Transactions namespace is the model for all kinds of transactions in .NET, not just database transactions. (See the Visual Studio 2010 documentation.)

Supporting XML with ADO.NET

ADO.NET provides extensive support for XML. You can use ADO.NET to do the following:

  • Fill a DataSet by using an XML document. Call the DataSet ReadXml method to populate a DataSet with an XML document.

  • Create or infer a DataSet schema from an XML Schema definition. Call a DataSet ReadXmlSchema or InferXmlSchema methods to create the DataSet schema.

  • Create an XML document or XML Schema from a DataSet. Call the DataSet GetXml method to write the DataSet content as an XML document. Call GetXmlSchema to write an XML Schema file from the DataSet schema.

  • Synchronize a DataSet contents with an XML document. Use the XmlDataDocument object to create an XML document with the data from a DataSet.

The following sample code creates a DataSet and populates it with data from an XML document with the name recipe.xml:

VB

Dim ds As New System.Data.DataSet
ds.ReadXml("recipe.xml", XmlReadMode.InferSchema)

C#.

System.Data.DataSet ds = new System.Data.DataSet();
  ds.ReadXml("recipe.xml", XmlReadMode.InferSchema);

Using the DataSet from the preceding code example, write an XML Schema to the recipe.xsd file with this code:

VB

ds.WriteXmlSchema("recipe.xsd")

C#.

ds.WriteXmlSchema("recipe.xsd");

You can combine the extensive ADO.NET support for XML with SQL Server 2008 support for XML. Using SQL Server 2008, you can

  • Use the xml data type to store entire XML documents or fragments.

  • Associate XML Schemas with xml data types to create typed XML.

  • Retrieve data stored in relational tables as XML markup using the FOR XML clause.

  • Retrieve XML data as relational data by using the OPENXML function.

The Adventure Works sample database provides several examples of using the xml data type. To read more about using XML with SQL Server 2008, see the topic Using XML in SQL Server in the SQL Server 2008 Books Online documentation.

Using ADO.NET in Your Applications

Using the features described in this chapter, you can use ADO.NET to access data seven ways from Sunday. Couple these features with all the designers in Visual Studio, and you have even more choices. Here are some recommendations for accessing data with ADO.NET:

  • Populate custom data entities with DataReaders. Many developers create their own custom data types for storing the data entities their application uses. For example, you can create a Customer data type and a CustomerAddress data type. Use a DataReader to populate your Customer and CustomerAddress data types. You can use this approach to separate the application from its underlying data source. (See Book III, Chapter 2.)

  • Use typed DataSets for prototyping or in conjunction with other data storage. Although typed DataSets are very fast to build, they perform slower than custom data entities in your application. They can be especially slow in Web applications. You don't have to use a typed DataSet for all your data access — you can use a combination of approaches. Typed DataSets are a good way to quickly build prototype applications.

  • Create data access class libraries. Whatever approach you use, usually you should encapsulate your data access methods in a separate class file or class library. You create public methods or properties that return DataSets, DataTables, hashtables, arrays, or custom data entities that your data controls consume. See Book III, Chapter 2 for more information on hashtables and arrays.

For example, here's a method signature that returns a DataTable from the class DataAccess:

  • VB

    Public Function GetDepartments() As System.Data.DataTable
  • C#

    public System.Data.DataTable GetDepartments()
    {
    }

The GetDepartments method encapsulates all the data access code populating the DataTable.

To use the DataTable as a data source for a BindingSource component, enter these lines:

  • VB

    Dim data As New DataAccess
    Me.MyBindingSource.DataSource = data.GetDepartments
  • C#

    DataAccess data = new DataAccess();
    MyBindingSource.DataSource = data.GetDepartments();

Use the BindingSource component as the data source for a databound control.

  • Evaluate whether to use stored procedures or ad hoc SQL queries. People are on both sides of the camp on this issue. Ad hoc queries don't automatically make the sky fall.

  • Extend generated typed DataSets. Use partial classes to add features to typed DataSets. For example, you may decide to use a DataReader to populate a simple lookup table.

  • Create your own helper classes. A great deal of repetition occurs in building data access code. You can create your own helper classes, though, to cut down on all the repetition.

  • Use the Enterprise Library. Another way to deal with all the repetition involved in coding data access is to use the Enterprise Library. It has all the best practices of using ADO.NET baked right in.

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

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