Visual CSharp 2005 Recipes (2006) [eng]
.pdf
298 C H A P T E R 8 ■ G R A P H I C S, M U LT I M E D I A , A N D P R I N T I N G
if ((Int32.Parse(job["StatusMask"].ToString()) & 1) == 1)
{
//Attempt to resume the job. int returnValue = Int32.Parse(
job.InvokeMethod("Resume", null).ToString());
//Display information about the return value. if (returnValue == 0)
{
MessageBox.Show("Successfully resumed job.");
}
else if (returnValue == 5)
{
MessageBox.Show("Access denied.");
}
else
{
MessageBox.Show(
"Unrecognized return value when resuming job.");
}
}
}
}
}
Figure 8-13 shows the window for this application.
Figure 8-13. Retrieving information from the print queue
Other WMI methods you might use in a printing scenario include AddPrinterConnection,
SetDefaultPrinter, CancelAllJobs, and PrintTestPage, all of which work with the Win32_Printer class. For more information about using WMI to retrieve information about Windows hardware, refer to the MSDN documentation.
C H A P T E R 9
■ ■ ■
Database Access
In the Microsoft .NET Framework, access to a wide variety of data sources is enabled through a group of classes collectively named Microsoft ADO.NET. Each type of data source is supported
through the provision of a data provider. Each data provider contains a set of classes that not only implement a standard set of interfaces (defined in the System.Data namespace), but also provide functionality unique to the data source they support. These classes include representations of connections, commands, properties, data adapters, and data readers through which you interact with a data source.
■Note ADO.NET is an extensive subsection of the .NET Framework class library and includes a great deal of advanced functionality. For comprehensive coverage of ADO.NET, read David Sceppa’s excellent book Microsoft ADO.NET Core Reference (Microsoft Press, 2002). An updated edition of this book to cover .NET Framework 2.0 is due out in early 2006.
Table 9-1 lists the data providers included as standard with the .NET Framework.
Table 9-1. .NET Framework Data Provider Implementations
Data Provider |
Description |
.NET Framework Data Provider for ODBC |
Provides connectivity (via COM Interop) to any |
|
data source that implements an ODBC interface. |
|
This includes Microsoft SQL Server, Oracle, and |
|
Microsoft Access databases. Data provider classes |
|
are contained in the System.Data.Odbc |
|
namespace and have the prefix Odbc. |
.NET Framework Data Provider for OLE DB |
Provides connectivity (via COM Interop) to any |
|
data source that implements an OLE DB |
|
interface. This includes Microsoft SQL Server, |
|
MSDE, Oracle, and Jet databases. Data provider |
|
classes are contained in the System.Data.OleDb |
|
namespace and have the prefix OleDb. |
.NET Framework Data Provider for Oracle |
Provides optimized connectivity to Oracle |
|
databases via Oracle client software version 8.1.7 |
|
or later. Data provider classes are contained in |
|
the System.Data.OracleClient namespace and |
|
have the prefix Oracle. |
|
(Continued) |
299
300 C H A P T E R 9 ■ D ATA B A S E A C C E S S
Table 9-1. Continued
Data Provider |
Description |
.NET Framework Data Provider for SQL Server |
Provides optimized connectivity to Microsoft SQL |
|
Server version 7 and later (including MSDE) by |
|
communicating directly with the SQL Server data |
|
source, without the need to use ODBC or OLE DB. |
|
Data provider classes are contained in the |
|
System.Data.SqlClient namespace and have the |
|
prefix Sql. |
.NET Compact Framework Data Provider |
Provides connectivity to Microsoft SQL Server CE. |
for SQL Server CE |
Data provider classes are contained in the |
|
System.Data.SqlServerCe namespace and have |
|
the prefix SqlCe. |
|
|
|
|
Where possible, the recipes in this chapter are programmed against the interfaces defined in the System.Data namespace. This approach makes it easier to apply the solutions to any database. Adopting this approach in your own code will make it more portable. However, the data provider classes that implement these interfaces often implement additional functionality specific to their own database. Generally, you must trade off portability against access to proprietary functionality when it comes to database code. Recipe 9-10 describes how you can use the System.Data.Common.DbProviderFactory and associated classes (new to .NET Framework 2.0) to write generic code that is not tied to a specific database implementation.
This chapter describes some of the most commonly used aspects of ADO.NET. The recipes in this chapter describe how to do the following:
•Create, configure, open, and close database connections (recipe 9-1)
•Employ connection pooling to improve the performance and scalability of applications that use database connections (recipe 9-2)
•Create and securely store database connection strings (recipes 9-3 and 9-4)
•Execute SQL commands and stored procedures, and use parameters to improve their flexibility (recipes 9-5 and 9-6)
•Process the results returned by database queries as either a set of rows or as XML (recipes 9-7 and 9-8)
•Execute database operations asynchronously, allowing your main code to continue with other tasks while the database operation executes in the background (recipe 9-9)
•Write generic ADO.NET code that can be configured to work against any relational database for which a data provider is available (recipe 9-10)
•Discover all instances of SQL Server 2000 and SQL Server 2005 available on a network (recipe 9-11)
C H A P T E R 9 ■ D ATA B A S E A C C E S S |
301 |
■Note Unless otherwise stated, the recipes in this chapter have been written to use SQL Server 2005 Express Edition running on the local machine and use the Northwind sample database provided by Microsoft. To run the examples against your own database, ensure the Northwind sample is installed and update the recipe’s connection string to contain the name of your server instead of .\sqlexpress. You can obtain the script to set up the Northwind database from the Microsoft web site. On that site, search for the file named SQL2000SampleDb.msi to find links to where the file is available for download. The download includes a Readme file with instructions on how to run the installation script.
9-1. Connect to a Database
Problem
You need to open a connection to a database.
Solution
Create a connection object appropriate to the type of database to which you need to connect. All connection objects implement the System.Data.IDbConnection interface. Configure the connection object by setting its ConnectionString property. Open the connection by calling the connection object’s Open method.
How It Works
The first step in database access is to open a connection to the database. The IDbConnection interface represents a database connection, and each data provider includes a unique implementation. Here is the list of IDbConnection implementations for the five standard data providers:
•System.Data.Odbc.OdbcConnection
•System.Data.OleDb.OleDbConnection
•System.Data.OracleClient.OracleConnection
•System.Data.SqlServerCe.SqlCeConnection
•System.Data.SqlClient.SqlConnection
You configure a connection object using a connection string. A connection string is a set of semicolon-separated name-value pairs. You can supply a connection string either as a constructor argument or by setting a connection object’s ConnectionString property before opening the connection. Each connection class implementation requires that you provide different information in the connection string. Refer to the ConnectionString property documentation for each implementation to see the values you can specify. Possible settings include the following:
•The name of the target database server
•The name of the database to open initially
•Connection time-out values
•Connection-pooling behavior (see recipe 9-2)
•Authentication mechanisms to use when connecting to secured databases, including provision of a username and password if needed
302 C H A P T E R 9 ■ D ATA B A S E A C C E S S
Once configured, call the connection object’s Open method to open the connection to the database. You can then use the connection object to execute commands against the data source (discussed in recipe 9-3). The properties of a connection object also allow you to retrieve information about the state of a connection and the settings used to open the connection. When you’re finished with
a connection, you should always call its Close method to free the underlying database connection and system resources. IDbConnection extends System.IDisposable, meaning that each connection class implements the Dispose method. Dispose automatically calls Close, making the using statement a very clean and efficient way of using connection objects in your code.
You achieve optimum scalability by opening your database connection as late as possible and closing it as soon as you have finished. This ensures that you do not tie up database connections for long periods, so you give all code the maximum opportunity to obtain a connection. This is especially important if you are using connection pooling.
The Code
The following example demonstrates how to use both the SqlConnection and OleDbConnection classes to open a connection to a Microsoft SQL Server database running on the local machine that uses integrated Windows security.
using System; using System.Data;
using System.Data.SqlClient; using System.Data.OleDb;
namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_01
{
public static void SqlConnectionExample()
{
// Create an empty SqlConnection object.
using (SqlConnection con = new SqlConnection())
{
// Configure the SqlConnection object's connection string.
con.ConnectionString = |
|
|
|
@"Data Source=.\sqlexpress;" + // |
local SQL Server instance |
||
"Database=Northwind;" + |
// |
the sample |
Northwind DB |
"Integrated Security=SSPI"; |
// |
integrated |
Windows security |
//Open the database connection. con.Open();
//Display information about the connection. if (con.State == ConnectionState.Open)
{
Console.WriteLine("SqlConnection Information:"); Console.WriteLine(" Connection State = " + con.State); Console.WriteLine(" Connection String = " +
con.ConnectionString);
Console.WriteLine(" Database Source = " + con.DataSource); Console.WriteLine(" Database = " + con.Database); Console.WriteLine(" Server Version = " + con.ServerVersion); Console.WriteLine(" Workstation Id = " + con.WorkstationId); Console.WriteLine(" Timeout = " + con.ConnectionTimeout); Console.WriteLine(" Packet Size = " + con.PacketSize);
}
C H A P T E R 9 ■ D ATA B A S E A C C E S S |
303 |
else
{
Console.WriteLine("SqlConnection failed to open."); Console.WriteLine(" Connection State = " + con.State);
}
// At the end of the using block Dispose() calls Close().
}
}
public static void OleDbConnectionExample()
{
// Create an empty OleDbConnection object.
using (OleDbConnection con = new OleDbConnection())
{
// Configure the OleDbConnection |
object's |
connection string. |
|
con.ConnectionString = |
|
|
|
"Provider=SQLOLEDB;" + |
// |
OLE |
DB Provider for SQL Server |
@"Data Source=.\sqlexpress;" |
+ // |
local SQL Server instance |
|
"Initial Catalog=Northwind;" |
+ // |
the |
sample Northwind DB |
"Integrated Security=SSPI"; |
|
// integrated Windows security |
|
//Open the database connection. con.Open();
//Display information about the connection. if (con.State == ConnectionState.Open)
{
Console.WriteLine("OleDbConnection Information:"); Console.WriteLine(" Connection State = " + con.State); Console.WriteLine(" Connection String = " +
con.ConnectionString);
Console.WriteLine(" Database Source = " + con.DataSource); Console.WriteLine(" Database = " + con.Database); Console.WriteLine(" Server Version = " + con.ServerVersion); Console.WriteLine(" Timeout = " + con.ConnectionTimeout);
}
else
{
Console.WriteLine("OleDbConnection failed to open."); Console.WriteLine(" Connection State = " + con.State);
}
//At the end of the using block Dispose() calls Close().
}
}
public static void Main()
{
//Open connection using SqlConnection. SqlConnectionExample(); Console.WriteLine(Environment.NewLine);
//Open connection using OleDbConnection. OleDbConnectionExample();
//Wait to continue. Console.WriteLine(Environment.NewLine);
304 C H A P T E R 9 ■ D ATA B A S E A C C E S S
Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();
}
}
}
9-2. Use Connection Pooling
Problem
You need to use a pool of database connections to improve application performance and scalability.
Solution
Configure the connection pool using settings in the connection string of a connection object.
How It Works
Connection pooling significantly reduces the overhead associated with creating and destroying database connections. Connection pooling also improves the scalability of solutions by reducing the number of concurrent connections a database must maintain. Many of these connections sit idle for a significant portion of their lifetimes. With connection pooling, instead of creating and opening a new connection object whenever you need one, you take an already open connection from the connection pool. When you have finished using the connection, instead of closing it, you return it to the pool and allow other code to use it.
The SQL Server and Oracle data providers encapsulate connection-pooling functionality that they enable by default. One connection pool exists for each unique connection string you specify when you open a new connection. Each time you open a new connection with a connection string that you used previously, the connection is taken from the existing pool. Only if you specify a different connection string will the data provider create a new connection pool. You can control some characteristics of your pool using the connection string settings described in Table 9-2.
■Note Once created, a pool exists until your process terminates.
Table 9-2. Connection String Settings That Control Connection Pooling
Setting |
Description |
Connection Lifetime |
Specifies the maximum time in seconds that a connection is allowed to |
|
live in the pool before it’s closed. The age of a connection is tested only |
|
when the connection is returned to the pool. This setting is useful for |
|
minimizing pool size if the pool is not heavily used and also ensures |
|
optimal load balancing is achieved in clustered database environments. |
|
The default value is 0, which means connections exist for the life of the |
|
current process. |
Connection Reset |
Supported only by the SQL Server data provider. Specifies whether |
|
connections are reset as they are taken from the pool. A value of True |
|
(the default) ensures a connection’s state is reset but requires an |
|
additional communication with the database. |
C H A P T E R 9 ■ D ATA B A S E A C C E S S |
305 |
Setting |
Description |
Max Pool Size |
Specifies the maximum number of connections that should be in the |
|
pool. Connections are created and added to the pool as required until |
|
this value is reached. If a request for a connection is made but there are |
|
no free connections, the caller will block until a connection becomes |
|
available. The default value is 100. |
Min Pool Size |
Specifies the minimum number of connections that should be in the |
|
pool. On pool creation, this number of connections is created and added |
|
to the pool. During periodic maintenance, or when a connection is |
|
requested, connections are added to the pool to ensure the minimum |
|
number of connections is available. The default value is 0. |
Pooling |
Set to False to obtain a nonpooled connection. The default value is True. |
|
|
The Code |
|
The following example demonstrates the configuration of a connection pool that contains a minimum of 5 and a maximum of 15 connections. Connections expire after 10 minutes (600 seconds) and are reset each time a connection is obtained from the pool. The example also demonstrates how to use the Pooling setting to obtain a connection object that is not from a pool. This is useful if your application uses a single long-lived connection to a database.
using System;
using System.Data.SqlClient;
namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_02
{
public static void Main()
{
// Obtain a pooled connection.
using (SqlConnection con = new SqlConnection())
{
// Configure the SqlConnection object's connection string.
con.ConnectionString |
= |
|
|
|
@"Data Source = .\sqlexpress;" +// |
local SQL Server instance |
|||
"Database = Northwind;" + |
// |
the sample Northwind DB |
||
"Integrated Security |
= SSPI;" + // |
integrated Windows security |
||
"Min Pool Size = |
5;" |
+ |
// |
configure minimum pool size |
"Max Pool Size = |
15;" + |
// |
configure maximum pool size |
|
"Connection Reset = True;" + |
// |
reset connections each use |
||
"Connection Lifetime |
= 600"; |
// |
set max connection lifetime |
|
//Open the database connection. con.Open();
//Access the database. . .
//At the end of the using block, the Dispose calls Close, which
//returns the connection to the pool for reuse.
}
// Obtain a nonpooled connection.
using (SqlConnection con = new SqlConnection())
306 C H A P T E R 9 ■ D ATA B A S E A C C E S S
{
// Configure the SqlConnection object's connection string. con.ConnectionString =
@"Data Source = .\sqlexpress;" +//local SQL Server instance
"Database = Northwind;" + |
//the sample |
Northwind DB |
"Integrated Security = SSPI;" + //integrated |
Windows security |
|
"Pooling = False"; |
//specify nonpooled connection |
|
//Open the database connection. con.Open();
//Access the database. . .
//At the end of the using block, the Dispose calls Close, which
//closes the nonpooled connection.
}
// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();
}
}
}
Notes
The ODBC and OLE DB data providers also support connection pooling, but they do not implement connection pooling within managed .NET classes, and you do not configure the pool in the same way as you do for the SQL Server or Oracle data providers. ODBC connection pooling is managed by the ODBC Driver Manager and configured using the ODBC Data Source Administrator tool in the Control Panel. OLE DB connection pooling is managed by the native OLE DB implementation. The most you can do is disable pooling by including the setting OLE DB Services=-4; in your connection string.
The SQL Server CE data provider does not support connection pooling, because SQL Server CE supports only a single concurrent connection.
9-3. Create a Database Connection String
Programmatically
Problem
You need to programmatically create or modify a syntactically correct connection string by working with its component parts or by parsing a given connection string.
Solution
Use the System.Data.Common.DbConnectionStringBuilder class or one of its strongly typed subclasses that form part of an ADO.NET data provider.
C H A P T E R 9 ■ D ATA B A S E A C C E S S |
307 |
How It Works
Connection strings are String objects that contain a set of configuration parameters in the form of name-value pairs separated by semicolons. These configuration parameters instruct the ADO.NET infrastructure how to open a connection to the data source you want to access and how to handle the life cycle of connections to that data source. As a developer, you will often simply define your connection string by hand and store it in a configuration file (see recipe 9-4). However, at times, you may want to build a connection string from component elements entered by a user, or you may want to parse an existing connection string into its component parts to allow you to manipulate it programmatically. The DbConnectionStringBuilder class (new to .NET Framework 2.0) and the classes derived from it provide both these capabilities.
DbConnectionStringBuilder is a class used to create connection strings from name-value pairs or to parse connection strings, but it does not enforce any logic on which configuration parameters are valid. Instead, each data provider (except the SQL Server CE data provider) includes a unique implementation derived from DbConnectionStringBuilder that accurately enforces the configuration rules for a connection string of that type. Here is the list of available DbConnectionStringBuilder implementations for standard data providers:
•System.Data.Odbc.OdbcConnectionStringBuilder
•System.Data.OleDb.OleDbConnectionStringBuilder
•System.Data.OracleClient.OracleConnectionStringBuilder
•System.Data.SqlClient.SqlConnectionStringBuilder
Each of these classes exposes properties for getting and setting the possible parameters for
a connection string of that type. To parse an existing connection string, pass it as an argument when creating the DbConnectionStringBuilder derived class or set the ConnectionString property. If this string contains a keyword not supported by the type of connection, an ArgumentException exception is thrown.
The Code
The following example demonstrates the use of the SqlConnectionStringBuilder class to parse and construct SQL Server connection strings.
using System;
using System.Data.SqlClient;
namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_03
{
public static void Main(string[] args)
{
string conString = @"Data Source=.\sqlexpress;" + "Database=Northwind;Integrated Security=SSPI;" +
"Min Pool Size=5;Max Pool Size=15;Connection Reset=True;" + "Connection Lifetime=600;";
//Parse the SQL Server connection string and display the component
//configuration parameters.
SqlConnectionStringBuilder sb1 =
new SqlConnectionStringBuilder(conString);
