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.
ADO is short for ActiveX Data Objects, the previous version of the data access technologies, before the .NET Framework was introduced.
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.
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.
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.
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
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.
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.
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.
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
.
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.
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.
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.
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.
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:
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.
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();
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.
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";
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.
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.
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();
You should always explicitly close your connection to your data source. Closing the connection releases resources.
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.
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 |
---|---|---|
| Serves as name or network address of server |
|
| Uses SSL encryption |
|
| Sets the name of the database to access |
|
| Determines whether to use Windows security |
|
| Sets the password to use when not using integrated security |
|
| Sets the user ID to use when not using integrated security |
|
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.
ADO.NET uses the ConnectionString
property to set the connection object's DataSource
and DataBase
properties.
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.
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.
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:
Right-click the My Project folder in an existing Windows project to access the project's properties.
Click the Settings tab.
A grid appears where you enter application settings.
Select Connection String as the settings type.
Click the ellipsis button in the Value cell.
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.
See the section in Book V, Chapter 2 about connecting to databases for more information on creating a connection string.
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.
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.
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.
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
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:
Create a new data connection by choosing Tools
The Add Connection dialog box appears.
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.
Right-click the data connection in Server Explorer.
You can open Server Explorer by choosing View
Choose Properties from the shortcut menu.
The Properties window appears.
Highlight the ConnectionString
property.
Copy and paste the property.
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.
Table 5-4. Connection String Builders
Data Provider | Object |
---|---|
SQL Server |
|
OLE DB |
|
ODBC |
|
Oracle |
|
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
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;
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:
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.
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";
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.
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:
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.
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();
To set the SqlCommand
object Connection
property, enter this line:
VB
MySqlCommand.Connection = MySqlConnection
C#
MySqlCommand.Connection = MySqlConnection;
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";
Set the CommandType
property to StoredProcedure
by entering the following line:
VB
MySqlCommand.CommandType = CommandType.StoredProcedure
C#
MySqlCommand.CommandType = CommandType.StoredProcedure;
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();
To set the ParameterName
and Value
properties for the SqlParameter
, assign values to the properties as shown:
Add the SqlParameter
object to the SqlCommand Parameters
collection, like this:
VB
MySqlCommand.Parameters.Add(MyParameter)
C#
MySqlCommand.Parameters.Add(MyParameter);
Call the Open
method of the SqlConnection
object:
VB
MySqlConnection.Open()
C#
MySqlConnection.Open();
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.
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.
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 |
|
OLE DB |
|
ODBC |
|
Oracle |
|
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:
Call the Command
object's ExecuteReader
method.
ExecuteReader
builds the DataReader
.
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.
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.
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 DataReader
— ExecuteReader
and Read
— a lot of setup work is involved. The following steps walk you through using a DataReader
to retrieve records:
Select a .NET Framework Data Provider, as described in the section "Picking a Provider," earlier in this chapter.
Create a new connection object for the data provider, as described in the earlier section "Making Connections."
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
.
Create a new Command
object for your data provider to retrieve data for the object, as described in the preceding section.
Call the Open
method of the connection object to establish a connection to the data source.
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.
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.
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.
Close your connection object, as described in the section "Closing your connection," earlier in this chapter.
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();
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.
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.
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
.
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.
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:
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
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) }
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
.
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
.
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 |
|
Ole DB |
|
ODBC |
|
Oracle |
|
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.
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
:
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.
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;
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();
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";
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;
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.
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:
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();
Set the SqlCommandBuilder DataAdapter
property:
builder.DataAdapter = MySqlDataAdapter
The SqlCommandBuilder
uses the DataAdapter SelectCommand
property to build the Insert, Update
, and Delete
commands.
Call the Update
method instead of the Fill
method in Step 6 in the preceding section.
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:
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
.
Open a Windows Form and then drag and drop the Department table from the Data Sources window.
A DepartmentTableAdapter
is created.
Double-click the form to access the form's Load
event.
The Code Editor appears.
Type the following code in the Code Editor:
The Code Editor displays a list of properties and methods available for DepartmentTableAdapter
, as shown in Figure 5-7.
Select a property or method from the list.
Use the TableAdapter Configuration Wizard to generate TableAdapters
.
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();
You must add a reference to System.Transactions
before you can access the objects in the namespace.
The System.Transactions
namespace is the model for all kinds of transactions in .NET, not just database transactions. (See the Visual Studio 2010 documentation.)
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 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.
18.118.126.11