Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article décrit la programmation asynchrone avec Microsoft.Data.SqlClient. Installez le package NuGet et importez l’espace Microsoft.Data.SqlClient de noms pour les exemples SQL. Microsoft. Data.SqlClient prend en charge les API basées sur les tâches sur .NET Framework et les .NET modernes.
Programmation asynchrone héritée
Avant .NET Framework 4.5, la programmation asynchrone avec System.Data.SqlClient a été effectuée avec les méthodes suivantes et la propriété de connexion Asynchronous Processing=true :
Cette fonctionnalité subsiste dans SqlClient dans .NET Framework 4.5.
Conseil
Microsoft.Data.SqlClient fournit également BeginExecuteNonQuery, BeginExecuteReader et BeginExecuteXmlReader pour la compatibilité. Supprimez Asynchronous Processing des chaînes de connexion lorsque vous migrez vers Microsoft.Data.SqlClient. Avec true ou false, le mot clé provoque ArgumentException avec le message Keyword not supported: 'asynchronous processing'. lorsque vous construisez SqlConnection ou affectez sa propriété ConnectionString, avant d’appeler Open ou OpenAsync. Le mot clé n’est pas ignoré et n’est pas requis pour les appels asynchrones.
Fonctionnalités de programmation asynchrone ajoutées dans .NET Framework 4.5
La fonctionnalité de programmation asynchrone fournit une technique simple pour rendre le code asynchrone. Pour plus d’informations sur la fonctionnalité de programmation asynchrone introduite dans .NET Framework 4.5, consultez :
- Programmation asynchrone en C#
- Programmation asynchrone avec Async et Await (Visual Basic)
- Utilisation des nouvelles méthodes asynchrones de SqlDataReader dans .NET Framework 4.5 (Première partie)
- Utilisation des nouvelles méthodes asynchrones de SqlDataReader dans .NET Framework 4.5 (Deuxième partie)
Lorsque votre interface utilisateur ne répond pas ou votre serveur ne monte pas en charge, il est probable que vous votre code doit être plus asynchrone. Écrire du code asynchrone implique en général l'installation d'un rappel (également appelé suite) pour exprimer la logique qui se produit après que l'opération asynchrone s'est terminée. Cela complique la structure du code asynchrone par rapport au code synchrone.
Vous pouvez maintenant appeler des méthodes asynchrones sans utiliser de rappels, et sans fractionner votre code entre plusieurs méthodes ou expressions lambda.
Le modificateur async spécifie qu'une méthode est asynchrone. Lors de l’appel d’une méthode async, une tâche est retournée. Lorsque l’opérateur await est appliqué à une tâche, la méthode actuelle se termine immédiatement. Lorsque la tâche se termine, l’exécution reprend dans la même méthode.
Note
Microsoft. Data.SqlClient ne prend pas en charge Context Connection=true. Lorsque vous construisez SqlConnection ou affectez sa ConnectionString propriété avec ce paramètre, le pilote lève InvalidOperationException avec le message Connecting to the context connection using Microsoft.Data.SqlClient is not supported. L'exception se produit avant d'appeler Open ou OpenAsync; le paramètre n'est pas ignoré.
L'appel d'une méthode async n'alloue aucun thread supplémentaire. Elle peut brièvement utiliser le thread de complétion d'entrée/sortie existant à la fin.
Les méthodes suivantes de la classe de base et de Microsoft.Data.SqlClient prennent en charge la programmation asynchrone :
- DbConnection.OpenAsync
- DbCommand.ExecuteDbDataReaderAsync
- DbCommand.ExecuteNonQueryAsync
- DbCommand.ExecuteReaderAsync
- DbCommand.ExecuteScalarAsync
- GetFieldValueAsync
- IsDBNullAsync
- DbDataReader.NextResultAsync
- DbDataReader.ReadAsync
- SqlConnection.OpenAsync
- SqlCommand.ExecuteNonQueryAsync
- SqlCommand.ExecuteReaderAsync
- SqlCommand.ExecuteScalarAsync
- SqlCommand.ExecuteXmlReaderAsync
- SqlDataReader.NextResultAsync
- SqlDataReader.ReadAsync
- SqlBulkCopy.WriteToServerAsync
D’autres membres asynchrones ont été ajoutés pour assurer la Prise en charge de la diffusion en continu pour SqlClient.
Conseil
Les méthodes asynchrones basées sur des tâches ne nécessitent pas d'option de chaîne de connexion pour activer les opérations asynchrones.
Ouverture de connexion synchrone à asynchrone
Vous pouvez mettre à niveau une application plus ancienne afin d'utiliser la fonctionnalité asynchrone plus récente. Par exemple, supposons qu'une application possède un algorithme de connexion synchrone et bloque le thread d'interface utilisateur chaque fois qu'elle se connecte à la base de données et, une fois connectée, l'application appelle une procédure stockée qui signale d'autres utilisateurs que celui qui vient de se connecter.
using SqlConnection conn = new SqlConnection("...");
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("StoredProcedure_Logon", conn))
{
cmd.ExecuteNonQuery();
}
}
Une fois converti pour utiliser la fonctionnalité asynchrone plus récente, le programme a l'apparence suivante :
using System;
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class A {
static async Task<int> Method(SqlConnection conn, SqlCommand cmd) {
await conn.OpenAsync();
await cmd.ExecuteNonQueryAsync();
return 1;
}
public static void Main() {
using (SqlConnection conn = new SqlConnection("...")) {
SqlCommand command = new SqlCommand("select top 2 * from orders", conn);
int result = A.Method(conn, command).Result;
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
Console.WriteLine(reader[0]);
}
}
}
Ajout des nouvelles fonctionnalités asynchrones dans une appli existante (combinant les modèles anciens et nouveaux)
Il est également possible d'ajouter la nouvelle fonction asynchrone (SqlConnection::OpenAsync) sans modifier la logique asynchrone existante. Par exemple, si d'une application utilise actuellement :
AsyncCallback productList = new AsyncCallback(ProductList);
SqlConnection conn = new SqlConnection("...");
conn.Open();
SqlCommand cmd = new SqlCommand("SELECT * FROM [Current Product List]", conn);
IAsyncResult ia = cmd.BeginExecuteReader(productList, cmd);
Vous pouvez commencer à utiliser le nouveau modèle asynchrone sans modifier considérablement l'algorithme existant.
Le modèle suivant omet intentionnellement le corps du rappel ProductList. Dans votre fonction de rappel, appelez EndExecuteReader, traitez et libérez le lecteur, puis libérez la commande et la connexion. Conservez l’application active jusqu’à ce que la fonction de rappel se termine. Le modèle n’est pas une application console autonome.
using System;
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class A {
static void ProductList(IAsyncResult result) { }
public static void Main()
{
AsyncCallback productList = new AsyncCallback(ProductList);
SqlConnection conn = new SqlConnection("...");
conn.OpenAsync().ContinueWith((task) => {
SqlCommand cmd = new SqlCommand("select top 2 * from orders", conn);
IAsyncResult ia = cmd.BeginExecuteReader(productList, cmd);
}, TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
Utilisation du modèle de fournisseur de base et de la nouvelle fonctionnalité asynchrone
Vous devrez peut-être créer un outil qui peut se connecter à différentes bases de données et exécuter des requêtes. Vous pouvez utiliser le modèle de fournisseur de base et la nouvelle fonctionnalité asynchrone.
Microsoft Distributed Transaction Controller (MSDTC) doit être activé sur le serveur pour utiliser des transactions distribuées. Pour plus d’informations sur l’activation de MSDTC, consultez Procédure : activer MSDTC sur un serveur web.
Avant d’exécuter l’exemple de fabrique, enregistrez Microsoft.Data.SqlClient comme décrit dans Obtention d’une dbProviderFactory. Utilisez la configuration d’application sur .NET Framework ou DbProviderFactories.RegisterFactory sur des .NET modernes.
using System;
using System.Data.Common;
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class A {
static async Task PerformDBOperationsUsingProviderModel(string connectionString, string providerName) {
DbProviderFactory factory = DbProviderFactories.GetFactory(providerName);
using (DbConnection connection = factory.CreateConnection()) {
connection.ConnectionString = connectionString;
await connection.OpenAsync();
DbCommand command = connection.CreateCommand();
command.CommandText = "SELECT * FROM AUTHORS";
using (DbDataReader reader = await command.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) {
for (int i = 0; i < reader.FieldCount; i++) {
// Process each column as appropriate
object obj = await reader.GetFieldValueAsync<object>(i);
Console.WriteLine(obj);
}
}
}
}
}
public static void Main()
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
// replace these with your own values
builder.DataSource = "your_server";
builder.InitialCatalog = "pubs";
builder.IntegratedSecurity = true;
string provider = "Microsoft.Data.SqlClient";
Task task = PerformDBOperationsUsingProviderModel(builder.ConnectionString, provider);
task.Wait();
}
}
Importante
Microsoft vous recommande d’utiliser le flux d’authentification le plus sécurisé disponible. Si vous vous connectez à Azure SQL, les identités managées pour les ressources Azure sont la méthode d'authentification recommandée.
Utiliser des transactions SQL et la nouvelle fonctionnalité asynchrone
using System;
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class Program {
static void Main() {
string connectionString = "...";
Task task = ExecuteSqlTransaction(connectionString);
task.Wait();
}
static async Task ExecuteSqlTransaction(string connectionString) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
await connection.OpenAsync();
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction = null;
// Start a local transaction.
transaction = await Task.Run<SqlTransaction>(
() => connection.BeginTransaction("SampleTransaction")
);
// Must assign both transaction object and connection
// to Command object for a pending local transaction
command.Connection = connection;
command.Transaction = transaction;
try {
command.CommandText =
"Insert into Region (RegionID, RegionDescription) VALUES (555, 'Description')";
await command.ExecuteNonQueryAsync();
command.CommandText =
"Insert into Region (RegionID, RegionDescription) VALUES (556, 'Description')";
await command.ExecuteNonQueryAsync();
// Attempt to commit the transaction.
await Task.Run(() => transaction.Commit());
Console.WriteLine("Both records are written to database.");
}
catch (Exception ex) {
Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
Console.WriteLine(" Message: {0}", ex.Message);
// Attempt to roll back the transaction.
try {
transaction.Rollback();
}
catch (Exception ex2) {
// This catch block will handle any errors that may have occurred
// on the server that would cause the rollback to fail, such as
// a closed connection.
Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
Console.WriteLine(" Message: {0}", ex2.Message);
}
}
}
}
}
Utiliser des transactions SQL et la nouvelle fonctionnalité asynchrone
Dans une application d'entreprise, vous devrez peut-être ajouter des transactions distribuées dans certains scénarios, pour activer les transactions entre plusieurs serveurs de base de données. Vous pouvez utiliser l'espace de noms System.Transactions et enregistrer une transaction distribuée, comme suit :
Sur les .NET modernes, les transactions distribuées nécessitent Windows et .NET 7 et versions ultérieures. Configurez l’accès réseau au Microsoft Distributed Transaction Coordinator (MSDTC) sur les ordinateurs participants. Avant d’exécuter l’exemple suivant sur les .NET modernes, activez la promotion des transactions au démarrage de l’application, avant de créer des transactions :
TransactionManager.ImplicitDistributedTransactions = true;
La propriété ImplicitDistributedTransactions est définie par défaut sur false. Sans cette activation, la promotion vers une transaction distribuée lève NotSupportedException. Sur .NET Framework, omettez cette affectation, car la propriété n'est pas disponible ; La configuration MSDTC est toujours requise.
using System;
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
using System.Transactions;
class Program {
public static void Main()
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
// replace these with your own values
builder.DataSource = "your_server";
builder.InitialCatalog = "your_data_source";
builder.IntegratedSecurity = true;
Task task = ExecuteDistributedTransaction(builder.ConnectionString, builder.ConnectionString);
task.Wait();
}
static async Task ExecuteDistributedTransaction(string connectionString1, string connectionString2) {
using (SqlConnection connection1 = new SqlConnection(connectionString1))
using (SqlConnection connection2 = new SqlConnection(connectionString2)) {
using (CommittableTransaction transaction = new CommittableTransaction()) {
await connection1.OpenAsync();
connection1.EnlistTransaction(transaction);
await connection2.OpenAsync();
connection2.EnlistTransaction(transaction);
try {
SqlCommand command1 = connection1.CreateCommand();
command1.CommandText = "Insert into RegionTable1 (RegionID, RegionDescription) VALUES (100, 'Description')";
await command1.ExecuteNonQueryAsync();
SqlCommand command2 = connection2.CreateCommand();
command2.CommandText = "Insert into RegionTable2 (RegionID, RegionDescription) VALUES (100, 'Description')";
await command2.ExecuteNonQueryAsync();
transaction.Commit();
}
catch (Exception ex) {
Console.WriteLine("Exception Type: {0}", ex.GetType());
Console.WriteLine(" Message: {0}", ex.Message);
try {
transaction.Rollback();
}
catch (Exception ex2) {
Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
Console.WriteLine(" Message: {0}", ex2.Message);
}
}
}
}
}
}
Importante
Microsoft vous recommande d’utiliser le flux d’authentification le plus sécurisé disponible. Si vous vous connectez à Azure SQL, les identités managées pour les ressources Azure sont la méthode d'authentification recommandée.
Annuler une opération asynchrone
Vous pouvez annuler une requête asynchrone à l'aide de CancellationToken.
using System;
using Microsoft.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
namespace Samples {
class CancellationSample {
public static void Main(string[] args) {
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(2000); // give up after 2 seconds
try {
Task result = CancellingAsynchronousOperations(source.Token);
result.Wait();
}
catch (AggregateException exception) {
if (source.IsCancellationRequested &&
(exception.InnerException is OperationCanceledException ||
exception.InnerException is SqlException)) {
Console.WriteLine("Operation canceled");
}
else {
throw;
}
}
}
static async Task CancellingAsynchronousOperations(CancellationToken cancellationToken) {
using (SqlConnection connection = new SqlConnection("...")) {
await connection.OpenAsync(cancellationToken);
SqlCommand command = new SqlCommand("WAITFOR DELAY '00:10:00'", connection);
await command.ExecuteNonQueryAsync(cancellationToken);
}
}
}
}
Opérations asynchrones avec SqlBulkCopy
Utiliser SqlBulkCopy.WriteToServerAsync pour la copie en bloc asynchrone avec Microsoft.Data.SqlClient.SqlBulkCopy.
Utilisez des chaînes de connexion distinctes pour SqlClient et ODBC. Remplacez <server> par votre instance de SQL Server et installez ODBC Driver 18 pour SQL Server pour l’exemple de fournisseur mixte. Sur les .NET modernes, faites également référence au package System.Data.Odbc. La chaîne de connexion SqlClient active plusieurs jeux de résultats actifs (MARS) pour l’exemple final.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Odbc;
using Microsoft.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SqlBulkCopyAsyncCodeSample {
class Program {
static string selectStatement = "SELECT * FROM [pubs].[dbo].[titles]";
static string createDestTableStatement =
@"CREATE TABLE {0} (
[title_id] [varchar](6) NOT NULL,
[title] [varchar](80) NOT NULL,
[type] [char](12) NOT NULL,
[pub_id] [char](4) NULL,
[price] [money] NULL,
[advance] [money] NULL,
[royalty] [int] NULL,
[ytd_sales] [int] NULL,
[notes] [varchar](200) NULL,
[pubdate] [datetime] NOT NULL)";
static string connectionString =
"Server=<server>;Database=pubs;Integrated Security=true;Encrypt=true;MultipleActiveResultSets=true;";
static string odbcConnectionString =
"Driver={ODBC Driver 18 for SQL Server};Server=<server>;Database=pubs;Trusted_Connection=yes;Encrypt=yes;";
static void Main(string[] args) {
SynchronousSqlBulkCopy();
AsyncSqlBulkCopy().Wait();
MixSyncAsyncSqlBulkCopy().Wait();
AsyncSqlBulkCopyNotifyAfter().Wait();
AsyncSqlBulkCopyDataRows().Wait();
AsyncSqlBulkCopyMARS().Wait();
}
// 3.1.1 Synchronous bulk copy
private static void SynchronousSqlBulkCopy() {
using (SqlConnection conn = new SqlConnection(connectionString)) {
conn.Open();
DataTable dt = new DataTable();
using (SqlCommand cmd = new SqlCommand(selectStatement, conn)) {
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
adapter.Fill(dt);
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
cmd.CommandText = string.Format(createDestTableStatement, temptable);
cmd.ExecuteNonQuery();
using (SqlBulkCopy bcp = new SqlBulkCopy(conn)) {
bcp.DestinationTableName = temptable;
bcp.WriteToServer(dt);
}
}
}
}
// 3.1.2 Asynchronous bulk copy
private static async Task AsyncSqlBulkCopy() {
using (SqlConnection conn = new SqlConnection(connectionString)) {
await conn.OpenAsync();
DataTable dt = new DataTable();
using (SqlCommand cmd = new SqlCommand(selectStatement, conn)) {
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
adapter.Fill(dt);
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
cmd.CommandText = string.Format(createDestTableStatement, temptable);
await cmd.ExecuteNonQueryAsync();
using (SqlBulkCopy bcp = new SqlBulkCopy(conn)) {
bcp.DestinationTableName = temptable;
await bcp.WriteToServerAsync(dt);
}
}
}
}
// 3.2 Add new Async.NET capabilities in an existing application (Mixing synchronous and asynchronous calls)
private static async Task MixSyncAsyncSqlBulkCopy() {
using (OdbcConnection odbcconn = new OdbcConnection(odbcConnectionString)) {
odbcconn.Open();
using (OdbcCommand odbccmd = new OdbcCommand(selectStatement, odbcconn)) {
using (OdbcDataReader odbcreader = odbccmd.ExecuteReader()) {
using (SqlConnection conn = new SqlConnection(connectionString)) {
await conn.OpenAsync();
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
SqlCommand createCmd = new SqlCommand(string.Format(createDestTableStatement, temptable), conn);
await createCmd.ExecuteNonQueryAsync();
using (SqlBulkCopy bcp = new SqlBulkCopy(conn)) {
bcp.DestinationTableName = temptable;
await bcp.WriteToServerAsync(odbcreader);
}
}
}
}
}
}
// 3.3 Using the NotifyAfter property
private static async Task AsyncSqlBulkCopyNotifyAfter() {
using (SqlConnection conn = new SqlConnection(connectionString)) {
await conn.OpenAsync();
DataTable dt = new DataTable();
using (SqlCommand cmd = new SqlCommand(selectStatement, conn)) {
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
adapter.Fill(dt);
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
cmd.CommandText = string.Format(createDestTableStatement, temptable);
await cmd.ExecuteNonQueryAsync();
using (SqlBulkCopy bcp = new SqlBulkCopy(conn)) {
bcp.DestinationTableName = temptable;
bcp.NotifyAfter = 5;
bcp.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnSqlRowsCopied);
await bcp.WriteToServerAsync(dt);
}
}
}
}
private static void OnSqlRowsCopied(object sender, SqlRowsCopiedEventArgs e) {
Console.WriteLine("Copied {0} so far...", e.RowsCopied);
}
// 3.4 Using the new SqlBulkCopy Async.NET capabilities with DataRow[]
private static async Task AsyncSqlBulkCopyDataRows() {
using (SqlConnection conn = new SqlConnection(connectionString)) {
await conn.OpenAsync();
DataTable dt = new DataTable();
using (SqlCommand cmd = new SqlCommand(selectStatement, conn)) {
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
adapter.Fill(dt);
DataRow[] rows = dt.Select();
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
cmd.CommandText = string.Format(createDestTableStatement, temptable);
await cmd.ExecuteNonQueryAsync();
using (SqlBulkCopy bcp = new SqlBulkCopy(conn)) {
bcp.DestinationTableName = temptable;
await bcp.WriteToServerAsync(rows);
}
}
}
}
// 3.7 Using Async.Net and MARS
private static async Task AsyncSqlBulkCopyMARS() {
using (SqlConnection marsConn = new SqlConnection(connectionString)) {
await marsConn.OpenAsync();
SqlCommand titlesCmd = new SqlCommand("SELECT * FROM [pubs].[dbo].[titles]", marsConn);
SqlCommand authorsCmd = new SqlCommand("SELECT * FROM [pubs].[dbo].[authors]", marsConn);
//With MARS we can have multiple active results sets on the same connection
using (SqlDataReader titlesReader = await titlesCmd.ExecuteReaderAsync())
using (SqlDataReader authorsReader = await authorsCmd.ExecuteReaderAsync()) {
await authorsReader.ReadAsync();
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
using (SqlConnection destConn = new SqlConnection(connectionString)) {
await destConn.OpenAsync();
using (SqlCommand destCmd = new SqlCommand(string.Format(createDestTableStatement, temptable), destConn)) {
await destCmd.ExecuteNonQueryAsync();
using (SqlBulkCopy bcp = new SqlBulkCopy(destConn)) {
bcp.DestinationTableName = temptable;
await bcp.WriteToServerAsync(titlesReader);
}
}
}
}
}
}
}
}
Utiliser de manière asynchrone plusieurs commandes avec MARS
L’exemple ouvre une connexion unique à la AdventureWorks base de données. À l’aide d’un objet SqlCommand, un SqlDataReader est créé. À mesure que le lecteur est utilisé, un deuxième SqlDataReader est ouvert, en utilisant les données du premier SqlDataReader comme entrée de la clause WHERE pour le deuxième lecteur.
Note
L’exemple suivant utilise l’exemple AdventureWorks de base de données inclus avec SQL Server. La chaîne de connexion fournie dans l’exemple de code suppose que la base de données est installée et disponible sur l’ordinateur local. Modifiez la chaîne de connexion en fonction des besoins de votre environnement.
using System;
using System.Data;
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class Class1 {
static void Main()
{
Task task = MultipleCommands();
task.Wait();
}
static async Task MultipleCommands()
{
// By default, MARS is disabled when connecting to a MARS-enabled.
// It must be enabled in the connection string.
string connectionString = GetConnectionString();
int vendorID;
SqlDataReader productReader = null;
string vendorSQL =
"SELECT VendorId, Name FROM Purchasing.Vendor";
string productSQL =
"SELECT Production.Product.Name FROM Production.Product " +
"INNER JOIN Purchasing.ProductVendor " +
"ON Production.Product.ProductID = " +
"Purchasing.ProductVendor.ProductID " +
"WHERE Purchasing.ProductVendor.VendorID = @VendorId";
using (SqlConnection awConnection =
new SqlConnection(connectionString)) {
SqlCommand vendorCmd = new SqlCommand(vendorSQL, awConnection);
SqlCommand productCmd =
new SqlCommand(productSQL, awConnection);
productCmd.Parameters.Add("@VendorId", SqlDbType.Int);
await awConnection.OpenAsync();
using (SqlDataReader vendorReader = await vendorCmd.ExecuteReaderAsync()) {
while (await vendorReader.ReadAsync()) {
Console.WriteLine(vendorReader["Name"]);
vendorID = (int)vendorReader["VendorId"];
productCmd.Parameters["@VendorId"].Value = vendorID;
// The following line of code requires a MARS-enabled connection.
productReader = await productCmd.ExecuteReaderAsync();
using (productReader) {
while (await productReader.ReadAsync()) {
Console.WriteLine(" " +
productReader["Name"].ToString());
}
}
}
}
}
}
}
Lire et mettre à jour des données de façon asynchrone avec MARS
MARS permet l’utilisation d’une connexion pour les opérations de lecture et les opérations DML (Data Manipulation Language) avec plusieurs opérations en attente. Cette fonctionnalité évite à une application de relever les erreurs de connexion en cours. En outre, MARS peut remplacer l’utilisateur des curseurs côté serveur, qui consomme généralement davantage de ressources. Enfin, étant donné que plusieurs opérations peuvent fonctionner sur une seule connexion, elles peuvent partager le même contexte de transaction, éliminant ainsi la nécessité d’utiliser sp_getbindtoken et sp_bindsession de procédures stockées système.
L’application Console suivante montre comment utiliser deux objets SqlDataReader avec trois objets SqlCommand et un objet SqlConnection unique avec MARS activé. Le premier objet de commande récupère une liste de fournisseurs dont le niveau de solvabilité est 5. Le deuxième objet de commande utilise l’ID de fournisseur fournie par un SqlDataReader pour charger le deuxième SqlDataReader avec tous les produits du fournisseur en question. Chaque enregistrement de produit est visité par le deuxième SqlDataReader. Un calcul est effectué pour déterminer ce que doit être le nouveau OnOrderQty . Le troisième objet de commande est ensuite utilisé pour mettre à jour la ProductVendor table avec la nouvelle valeur. Tout le processus se déroule au sein d'une seule transaction, qui est annulée à la fin.
Note
L’exemple suivant utilise l’exemple AdventureWorks de base de données inclus avec SQL Server. La chaîne de connexion fournie dans l’exemple de code suppose que la base de données est installée et disponible sur l’ordinateur local. Modifiez la chaîne de connexion en fonction des besoins de votre environnement.
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class Program {
static void Main() {
Task task = ReadingAndUpdatingData();
task.Wait();
}
static async Task ReadingAndUpdatingData() {
// By default, MARS is disabled when connecting to a MARS-enabled host.
// It must be enabled in the connection string.
string connectionString = GetConnectionString();
SqlTransaction updateTx = null;
SqlCommand vendorCmd = null;
SqlCommand prodVendCmd = null;
SqlCommand updateCmd = null;
SqlDataReader prodVendReader = null;
int vendorID = 0;
int productID = 0;
int minOrderQty = 0;
int maxOrderQty = 0;
int onOrderQty = 0;
int recordsUpdated = 0;
int totalRecordsUpdated = 0;
string vendorSQL =
"SELECT VendorID, Name FROM Purchasing.Vendor " +
"WHERE CreditRating = 5";
string prodVendSQL =
"SELECT ProductID, MaxOrderQty, MinOrderQty, OnOrderQty " +
"FROM Purchasing.ProductVendor " +
"WHERE VendorID = @VendorID";
string updateSQL =
"UPDATE Purchasing.ProductVendor " +
"SET OnOrderQty = @OrderQty " +
"WHERE ProductID = @ProductID AND VendorID = @VendorID";
using (SqlConnection awConnection =
new SqlConnection(connectionString)) {
await awConnection.OpenAsync();
updateTx = await Task.Run(() => awConnection.BeginTransaction());
vendorCmd = new SqlCommand(vendorSQL, awConnection);
vendorCmd.Transaction = updateTx;
prodVendCmd = new SqlCommand(prodVendSQL, awConnection);
prodVendCmd.Transaction = updateTx;
prodVendCmd.Parameters.Add("@VendorId", SqlDbType.Int);
updateCmd = new SqlCommand(updateSQL, awConnection);
updateCmd.Transaction = updateTx;
updateCmd.Parameters.Add("@OrderQty", SqlDbType.Int);
updateCmd.Parameters.Add("@ProductID", SqlDbType.Int);
updateCmd.Parameters.Add("@VendorID", SqlDbType.Int);
using (SqlDataReader vendorReader = await vendorCmd.ExecuteReaderAsync()) {
while (await vendorReader.ReadAsync()) {
Console.WriteLine(vendorReader["Name"]);
vendorID = (int)vendorReader["VendorID"];
prodVendCmd.Parameters["@VendorID"].Value = vendorID;
prodVendReader = await prodVendCmd.ExecuteReaderAsync();
using (prodVendReader) {
while (await prodVendReader.ReadAsync()) {
productID = (int)prodVendReader["ProductID"];
if (prodVendReader["OnOrderQty"] == DBNull.Value) {
minOrderQty = (int)prodVendReader["MinOrderQty"];
onOrderQty = minOrderQty;
}
else {
maxOrderQty = (int)prodVendReader["MaxOrderQty"];
onOrderQty = (int)(maxOrderQty / 2);
}
updateCmd.Parameters["@OrderQty"].Value = onOrderQty;
updateCmd.Parameters["@ProductID"].Value = productID;
updateCmd.Parameters["@VendorID"].Value = vendorID;
recordsUpdated = await updateCmd.ExecuteNonQueryAsync();
totalRecordsUpdated += recordsUpdated;
}
}
}
}
Console.WriteLine("Total Records Updated: {0}", totalRecordsUpdated);
await Task.Run(() => updateTx.Rollback());
Console.WriteLine("Transaction Rolled Back");
}
}
}