Tải bản đầy đủ - 0 (trang)
9-9. Perform Asynchronous Database Operations Against SQL Server

9-9. Perform Asynchronous Database Operations Against SQL Server

Tải bản đầy đủ - 0trang

CHAPTER 9 ■ DATABASE ACCESS



usually require the result of the operation before it can continue. However, sometimes it’s useful to

execute a database operation asynchronously, meaning that you start the method in a separate thread

and then continue with other operations.



■ Note To execute asynchronous operations over a System.Data.SqlClient.SqlConnection connection, you

must specify the value Asynchronous Processing=true in its connection string.



The SqlCommand class implements the asynchronous execution pattern similar to that discussed in

recipe 4-2. As with the general asynchronous execution pattern described in recipe 4-2, the arguments of

the asynchronous execution methods (BeginExecuteNonQuery, BeginExecuteReader, and

BeginExecuteXmlReader) are the same as those of the synchronous variants (ExecuteNonQuery,

ExecuteReader, and ExecuteXmlReader), but they take the following two additional arguments to support

asynchronous completion:





A System.AsyncCallback delegate instance that references a method that the

runtime will call when the asynchronous operation completes. The method is

executed in the context of a thread-pool thread. Passing null means that no

method is called and you must use another completion mechanism (discussed

later in this recipe) to determine when the asynchronous operation is complete.







An object reference that the runtime associates with the asynchronous operation.

The asynchronous operation does not use nor have access to this object, but it’s

available to your code when the operation completes, allowing you to associate

useful state information with an asynchronous operation. For example, this object

allows you to map results against initiated operations in situations where you

initiate many asynchronous operations that use a common callback method to

perform completion.



The EndExecuteNonQuery, EndExecuteReader, and EndExecuteXmlReader methods allow you to retrieve

the return value of an operation that was executed asynchronously, but you must first determine when it

has finished. Here are the four techniques for determining if an asynchronous method has finished:





Blocking: This method stops the execution of the current thread until the

asynchronous operation completes execution. In effect, this is much the same as

synchronous execution. However, in this case, you have the flexibility to decide

exactly when your code enters the blocked state, giving you the opportunity to

carry out some additional processing before blocking.







Polling: This method involves repeatedly testing the state of an asynchronous

operation to determine whether it’s complete. This is a very simple technique and

is not particularly efficient from a processing perspective. You should avoid tight

loops that consume processor time. It’s best to put the polling thread to sleep for a

period using Thread.Sleep between completion tests. Because polling involves

maintaining a loop, the actions of the waiting thread are limited, but you can

easily update some kind of progress indicator.



453



www.it-ebooks.info



CHAPTER 9 ■ DATABASE ACCESS







Waiting: This method uses an object derived from the

System.Threading.WaitHandle class to signal when the asynchronous method

completes. Waiting is a more efficient version of polling and in addition allows

you to wait for multiple asynchronous operations to complete. You can also

specify timeout values to allow your waiting thread to fail if the asynchronous

operation takes too long, or if you want to periodically update a status indicator.







Callback: This a method that the runtime calls when an asynchronous operation

completes. The calling code does not need to take any steps to determine when

the asynchronous operation is complete and is free to continue with other

processing. Callbacks provide the greatest flexibility, but also introduce the

greatest complexity, especially if you have many concurrently active

asynchronous operations that all use the same callback. In such cases, you must

use appropriate state objects to match completed methods against those you

initiated.



■ Caution When using the asynchronous capabilities of the SQL Server data provider, you must ensure that your

code does not inadvertently dispose of objects that are still being used by other threads. Pay particular attention to

SqlConnection and SqlCommand objects.



The Code

Recipe 4-2 provides examples of all of the completion techniques summarized in the preceding list. The

following example demonstrates the use of an asynchronous call to execute a stored procedure on a SQL

Server database. The code uses a callback to process the returned result set.

using

using

using

using



System;

System.Data;

System.Threading;

System.Data.SqlClient;



namespace Apress.VisualCSharpRecipes.Chapter09

{

class Recipe09_09

{

// A method to handle asynchronous completion using callbacks.

public static void CallbackHandler(IAsyncResult result)

{

// Obtain a reference to the SqlCommand used to initiate the

// asynchronous operation.

using (SqlCommand cmd = result.AsyncState as SqlCommand)

{

// Obtain the result of the stored procedure.

using (SqlDataReader reader = cmd.EndExecuteReader(result))

{



454



www.it-ebooks.info



CHAPTER 9 ■ DATABASE ACCESS



// Display the results of the stored procedure to the console.

lock (Console.Out)

{

Console.WriteLine(

"Price of the Ten Most Expensive Products:");

while (reader.Read())

{

// Display the product details.

Console.WriteLine(" {0} = {1}",

reader["TenMostExpensiveProducts"],

reader["UnitPrice"]);

}

}

}

}

}

public static void Main()

{

// Create a new SqlConnection object.

using (SqlConnection con = new SqlConnection())

{

// Configure the SqlConnection object's connection string.

// You must specify Asynchronous Processing=true to support

// asynchronous operations over the connection.

con.ConnectionString = @"Data Source = .\sqlexpress;" +

"Database = Northwind; Integrated Security=SSPI;" +

"Asynchronous Processing=true";

// Create and configure a new command to run a stored procedure.

// Do not wrap it in a using statement because the asynchronous

// completion handler will dispose of the SqlCommand object.

SqlCommand cmd = con.CreateCommand();

cmd.CommandType = CommandType.StoredProcedure;

cmd.CommandText = "Ten Most Expensive Products";

// Open the database connection and execute the command

// asynchronously. Pass the reference to the SqlCommand

// used to initiate the asynchronous operation.

con.Open();

cmd.BeginExecuteReader(CallbackHandler, cmd);



455



www.it-ebooks.info



CHAPTER 9 ■ DATABASE ACCESS



// Continue with other processing.

for (int count = 0; count < 10; count++)

{

lock (Console.Out)

{

Console.WriteLine("{0} : Continue processing...",

DateTime.Now.ToString("HH:mm:ss.ffff"));

}

Thread.Sleep(500);

}

}

// Wait to continue.

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Main method complete. Press Enter.");

Console.ReadLine();

}

}

}



9-10. Write Database-Independent Code

Problem

You need to write code that can be configured to work against any relational database supported by an

ADO.NET data provider.



Solution

Program to the ADO.NET data provider interfaces in the System.Data namespace, as opposed to the

concrete implementations, and do not rely on features and data types that are unique to specific

database implementations. Use factory classes and methods to instantiate the data provider objects you

need to use.



How It Works

Using a specific data provider implementation (the SQL Server data provider, for example) simplifies

your code, and may be appropriate if you need to support only a single type of database or require

access to specific features provided by that data provider, such as the asynchronous execution for SQL

Server detailed in recipe 9-9. However, if you program your application against a specific data provider

implementation, you will need to rewrite and test those sections of your code if you want to use a

different data provider at some point in the future.

Table 9-6 contains a summary of the main interfaces you must program against when writing

generic ADO.NET code that will work with any relational database’s data provider. The table also

explains how to create objects of the appropriate type that implement the interface. Many of the recipes



456



www.it-ebooks.info



CHAPTER 9 ■ DATABASE ACCESS



in this chapter demonstrate the use of ADO.NET data provider interfaces over specific implementation,

as highlighted in the table.

Table 9-6. Data Provider Interfaces



Interface



Description



Demonstrated In



IDbConnection



Represents a connection to a relational database. You must

program the logic to create a connection object of the

appropriate type based on your application’s configuration

information, or use the DbProviderFactory.CreateConnection

factory method (discussed in this recipe).



Recipe 9-1



IDbCommand



Represents a SQL command that is issued to a relational

database. You can create IDbCommand objects of the appropriate

type using the IDbConnection.CreateCommand or

DbProviderFactory.CreateCommand factory method.



Recipe 9-5



IDataParameter



Represents a parameter to an IDbCommand object. You can create

IDataParameter objects of the correct type using the

IDbCommand.CreateParameter, IDbCommand.Parameters.Add, or

DbProviderFactory.CreateParameter factory method.



Recipe 9-6



IDataReader



Represents the result set of a database query and provides access

to the contained rows and columns. An object of the correct type

will be returned when you call the IDbCommand.ExecuteReader

method.



Recipes 9-5 and

9-6



IDbDataAdapter



Represents the set of commands used to fill a

System.Data.DataSet from a relational database and to update

the database based on changes to the DataSet. You must program

the logic to create a data adapter object of the appropriate type

based on your application’s configuration information, or use the

DbProviderFactory.CreateAdapter factory method (discussed in

this recipe).



The System.Data.Common.DbProviderFactory class provides a set of factory methods for creating all

types of data provider objects, making it very useful for implementing generic database code. Most

important, DbProviderFactory provides a mechanism for obtaining an initial IDbConnection instance,

which is the critical starting point for writing generic ADO.NET code. Each of the standard data provider

implementations (except the SQL Server CE data provider) includes a unique factory class derived from

DbProviderFactory. Here is the list of DbProviderFactory subclasses:



457



www.it-ebooks.info



CHAPTER 9 ■ DATABASE ACCESS







System.Data.Odbc.OdbcFactory







System.Data.OleDb.OleDbFactory







System.Data.OracleClient.OracleClientFactory







System.Data.SqlClient.SqlClientFactory



You can obtain an instance of the appropriate DbProviderFactory subclass using the

DbProviderFactories class, which is effectively a factory of factories. Each data provider factory is

described by configuration information in the machine.config file, similar to that shown here for the SQL

Server data adapter. This can be changed or overridden by application-specific configuration

information if required.








description=".Net Framework Data Provider for SqlServer" type= ~CCC

"System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0, ~CCC

Culture=neutral, PublicKeyToken=b77a5c561934e089" />








foreach (DataRow prov in providers.Rows)

{

Console.WriteLine(" Name:{0}", prov["Name"]);

Console.WriteLine("

Description:{0}",

prov["Description"]);

Console.WriteLine("

Invariant Name:{0}",

prov["InvariantName"]);

}

}

// Obtain the DbProviderFactory for SQL Server. The provider to use

// could be selected by the user or read from a configuration file.

// In this case, we simply pass the invariant name.

DbProviderFactory factory =

DbProviderFactories.GetFactory("System.Data.SqlClient");

// Use the DbProviderFactory to create the initial IDbConnection, and

// then the data provider interface factory methods for other objects.

using (IDbConnection con = factory.CreateConnection())

{

// Normally, read the connection string from secure storage.

// See recipe 9-3. In this case, use a default value.

con.ConnectionString = @"Data Source = .\sqlexpress;" +

"Database = Northwind; Integrated Security=SSPI";



459



www.it-ebooks.info



CHAPTER 9 ■ DATABASE ACCESS



// Create and configure a new command.

using (IDbCommand com = con.CreateCommand())

{

com.CommandType = CommandType.StoredProcedure;

com.CommandText = "Ten Most Expensive Products";

// Open the connection.

con.Open();

// Execute the command and process the results.

using (IDataReader reader = com.ExecuteReader())

{

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Price of the Ten Most" +

" Expensive Products.");

while (reader.Read())

{

// Display the product details.

Console.WriteLine(" {0} = {1}",

reader["TenMostExpensiveProducts"],

reader["UnitPrice"]);

}

}

}

}

// Wait to continue.

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Main method complete. Press Enter.");

Console.ReadLine();

}

}

}



9-11. Discover All Instances of SQL Server on Your Network

Problem

You need to obtain a list of all instances of SQL Server that are accessible on the network.



Solution

Use the GetDataSources method of the System.Data.Sql.SqlDataSourceEnumerator class.



460



www.it-ebooks.info



CHAPTER 9 ■ DATABASE ACCESS



How It Works

The SqlDataSourceEnumerator class makes it easy to enumerate the SQL Server instances accessible on

the network. You simply obtain the singleton SqlDataSourceEnumerator instance via the static property

SqlDataSourceEnumerator.Instance and call its GetDataSources method. The GetDataSources method

returns a System.Data.DataTable that contains a set of System.Data.DataRow objects. Each DataRow

represents a single SQL Server instance and contains the following columns:





ServerName, which contains the name of the server where the SQL Server instance

is hosted







InstanceName, which contains the name of the SQL Server instance or the empty

string if the SQL Server is the default instance







IsClustered, which indicates whether the SQL Server instance is part of a cluster







Version, which contains the version of the SQL Server instance



The Code

The following example demonstrates the use of the SqlDataSourceEnumerator class to discover and

display details of all SQL Server instances accessible (and visible) on the network. The IsClustered and

Version columns may be blank for some versions of SQL Server.

using System;

using System.Data;

using System.Data.Sql;

namespace Apress.VisualCSharpRecipes.Chapter09

{

class Recipe09_11

{

public static void Main(string[] args)

{

// Obtain the DataTable of SQL Server instances.

using (DataTable SqlSources =

SqlDataSourceEnumerator.Instance.GetDataSources())

{

// Enumerate the set of SQL Servers and display details.

Console.WriteLine("Discover SQL Server Instances:");

foreach (DataRow source in SqlSources.Rows)

{

Console.WriteLine(" Server Name:{0}", source["ServerName"]);

Console.WriteLine("

Instance Name:{0}",

source["InstanceName"]);

Console.WriteLine("

Is Clustered:{0}",

source["IsClustered"]);

Console.WriteLine("

Version:{0}", source["Version"]);

}

}



461



www.it-ebooks.info



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

9-9. Perform Asynchronous Database Operations Against SQL Server

Tải bản đầy đủ ngay(0 tr)

×