Pemrograman asinkron

Artikel ini membahas pemrograman asinkron dengan Microsoft.Data.SqlClient. Instal paket NuGet dan impor namespace Microsoft.Data.SqlClient untuk contoh SQL. Microsoft. Data.SqlClient mendukung API berbasis tugas pada .NET Framework dan .NET modern.

Pemrograman asinkron warisan

Sebelum .NET Framework 4.5, pemrograman asinkron dengan System.Data.SqlClient dilakukan dengan metode berikut dan properti koneksi Asynchronous Processing=true:

Fungsionalitas ini tetap berada di SqlClient di .NET Framework 4.5.

Petunjuk / Saran

Microsoft. Data.SqlClient juga menyediakan BeginExecuteNonQuery, BeginExecuteReader, dan BeginExecuteXmlReader untuk kompatibilitas. Hapus Asynchronous Processing dari string koneksi saat Anda bermigrasi ke Microsoft.Data.SqlClient. Dengan true atau false, kata kunci menyebabkan ArgumentException dengan pesan Keyword not supported: 'asynchronous processing'. saat Anda membuat SqlConnection atau menetapkan properti ConnectionString-nya, sebelum Anda memanggil Open atau OpenAsync. Kata kunci tidak diabaikan dan tidak wajib untuk panggilan asinkron.

Fitur pemrograman asinkron ditambahkan dalam .NET Framework 4.5

Fitur pemrograman asinkron menyediakan teknik sederhana untuk membuat kode asinkron. Untuk informasi selengkapnya tentang fitur pemrograman asinkron yang diperkenalkan di .NET Framework 4.5, lihat:

Ketika antarmuka pengguna Anda tidak responsif atau server Anda tidak menskalakan, kemungkinan Anda memerlukan kode Anda untuk lebih asinkron. Menulis kode asinkron secara tradisional melibatkan pemasangan panggilan balik (juga disebut kelanjutan) untuk mengekspresikan logika yang terjadi setelah operasi asinkron selesai. Ini mempersulit struktur kode asinkron dibandingkan dengan kode sinkron.

Anda sekarang dapat memanggil metode asinkron tanpa menggunakan panggilan balik, dan tanpa membagi kode Anda di beberapa metode atau ekspresi lambda.

Pengubah async menetapkan bahwa suatu metode tidak sinkron. Saat memanggil metode async, tugas dikembalikan. Saat operator await diterapkan ke tugas, metode saat ini segera keluar. Ketika tugas selesai, eksekusi dilanjutkan dengan metode yang sama.

Catatan

Microsoft. Data.SqlClient tidak mendukung Context Connection=true. Saat Anda membuat SqlConnection atau menetapkan propertinya ConnectionString dengan pengaturan tersebut, driver melempar InvalidOperationException dengan pesan Connecting to the context connection using Microsoft.Data.SqlClient is not supported. Pengecualian terjadi sebelum Anda memanggil Open atau OpenAsync; pengaturan tidak diabaikan.

Memanggil metode async tidak akan mengalokasikan utas tambahan apa pun. Di akhir, mungkin akan menggunakan utas penyelesaian I/O yang ada secara singkat.

Metode base-class berikut dan Microsoft.Data.SqlClient mendukung pemrograman asinkron:

Anggota asinkron lainnya ditambahkan untuk mendukung Dukungan Streaming SqlClient.

Petunjuk / Saran

Metode asinkron berbasis tugas tidak memerlukan opsi string koneksi untuk mengaktifkan operasi asinkron.

Pembukaan koneksi dari sinkron ke asinkron

Anda dapat meningkatkan aplikasi lama untuk menggunakan fitur asinkron yang lebih baru. Misalnya, asumsikan aplikasi memiliki algoritma koneksi sinkron dan memblokir utas UI setiap kali terhubung ke database dan, setelah terhubung, aplikasi memanggil prosedur tersimpan yang memberi sinyal kepada pengguna lain dari yang baru saja masuk.

using SqlConnection conn = new SqlConnection("...");
{
   conn.Open();
   using (SqlCommand cmd = new SqlCommand("StoredProcedure_Logon", conn))
   {
      cmd.ExecuteNonQuery();
   }
}

Ketika dikonversi untuk menggunakan fungsionalitas asinkron yang lebih baru, program akan terlihat seperti:

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]);
      }
   }
}

Tambahkan fitur asinkron baru di aplikasi yang ada (campur pola lama dan baru)

Anda juga dapat menambahkan kemampuan asinkron baru (SqlConnection::OpenAsync) tanpa mengubah logika asinkron yang ada. Misalnya, jika aplikasi saat ini menggunakan:

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);

Anda dapat mulai menggunakan pola asinkron baru tanpa mengubah algoritma yang ada secara substansial.

Templat berikut ini sengaja menghilangkan isi fungsi callback ProductList. Dalam callback Anda, panggil EndExecuteReader, proses dan lepaskan pembaca, dan lepaskan perintah dan koneksi. Biarkan aplikasi tetap hidup sampai callback selesai. Templat bukan aplikasi konsol mandiri.

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);
   }
}

Gunakan model penyedia dasar dan fitur asinkron baru

Anda mungkin perlu membuat alat yang dapat terhubung ke database yang berbeda dan menjalankan kueri. Anda dapat menggunakan model penyedia dasar dan fitur asinkron baru.

Microsoft Distributed Transaction Controller (MSDTC) harus diaktifkan di server untuk menggunakan transaksi terdistribusi. Untuk informasi tentang cara mengaktifkan MSDTC, lihat Cara Mengaktifkan MSDTC di Server Web.

Sebelum Anda menjalankan contoh pabrik, daftarkan Microsoft.Data.SqlClient seperti yang dijelaskan dalam Mendapatkan DbProviderFactory. Gunakan konfigurasi aplikasi pada .NET Framework atau DbProviderFactories.RegisterFactory pada .NET modern.

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();
   }
}

Penting

Microsoft menyarankan agar Anda menggunakan alur autentikasi paling aman yang tersedia. Jika Anda menyambungkan ke Azure SQL, Identitas Terkelola untuk sumber daya Azure adalah metode autentikasi yang direkomendasikan.

Menggunakan transaksi SQL dan fitur asinkron baru

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);
            }
         }
      }
   }
}

Menggunakan transaksi SQL dan fitur asinkron baru

Dalam aplikasi perusahaan, Anda mungkin perlu menambahkan transaksi terdistribusi dalam beberapa skenario, untuk mengaktifkan transaksi antara beberapa server database. Anda dapat menggunakan System.Transactions namespace dan mendaftarkan transaksi terdistribusi, dengan cara berikut:

Pada .NET modern, transaksi terdistribusi memerlukan Windows dan .NET 7 dan versi yang lebih baru. Konfigurasikan akses jaringan Koordinator Transaksi Terdistribusi Microsoft (MSDTC) pada komputer yang berpartisipasi. Sebelum Anda menjalankan contoh berikut pada .NET modern, aktifkan promosi transaksi di startup aplikasi, sebelum Anda membuat transaksi apa pun:

TransactionManager.ImplicitDistributedTransactions = true;

Properti ImplicitDistributedTransactions memiliki nilai default false. Tanpa persetujuan ini, promosi ke transaksi terdistribusi melemparkan NotSupportedException. Pada .NET Framework, hilangkan penugasan ini karena properti tidak tersedia; Konfigurasi MSDTC masih diperlukan.

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);
               }
            }
         }
      }
   }
}

Penting

Microsoft menyarankan agar Anda menggunakan alur autentikasi paling aman yang tersedia. Jika Anda menyambungkan ke Azure SQL, Identitas Terkelola untuk sumber daya Azure adalah metode autentikasi yang direkomendasikan.

Membatalkan operasi asinkron

Anda dapat membatalkan permintaan asinkron dengan menggunakan 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);
         }
      }
   }
}

Operasi asinkron dengan SqlBulkCopy

Gunakan SqlBulkCopy.WriteToServerAsync untuk salinan massal asinkron dengan Microsoft.Data.SqlClient.SqlBulkCopy.

Gunakan string koneksi terpisah untuk SqlClient dan ODBC. Ganti <server> dengan instans SQL Server Anda, dan instal ODBC Driver 18 untuk SQL Server untuk contoh penyedia campuran. Pada .NET modern, referensikan juga paket System.Data.Odbc. SqlClient string koneksi memungkinkan beberapa kumpulan hasil aktif (MARS) untuk contoh akhir.

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);
                     }
                  }
               }
            }
         }
      }
   }
}

Secara asinkron menggunakan beberapa perintah dengan MARS

Contoh membuka satu koneksi ke basis data AdventureWorks. Menggunakan objekSqlCommand, sebuah SqlDataReader dibuat. Saat pembaca digunakan, pembaca kedua SqlDataReader dibuka, menggunakan data dari SqlDataReader pertama sebagai input untuk klausul WHERE.

Catatan

Contoh berikut menggunakan database sampel AdventureWorks yang disertakan dengan SQL Server. String koneksi yang disediakan dalam kode sampel mengasumsikan database dipasang dan tersedia di komputer lokal. Ubah string koneksi seperlunya untuk lingkungan Anda.

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());
                  }
               }
            }
         }
      }
   }
}

Membaca dan memperbarui data secara asinkron dengan MARS

MARS memungkinkan koneksi digunakan untuk operasi pembacaan dan operasi bahasa manipulasi data (DML) dengan lebih dari satu operasi yang tertunda. Fitur ini menghilangkan kebutuhan aplikasi untuk mengatasi kesalahan saat koneksi sibuk. Selain itu, MARS dapat menggantikan pengguna kursor sisi server, yang umumnya menghabiskan lebih banyak sumber daya. Akhirnya, karena beberapa operasi dapat beroperasi pada satu koneksi, mereka dapat berbagi konteks transaksi yang sama, menghilangkan kebutuhan untuk menggunakan sp_getbindtoken dan sp_bindsession prosedur tersimpan sistem.

Aplikasi Konsol berikut menunjukkan cara menggunakan dua objek SqlDataReader dengan tiga objek SqlCommand dan satu objek SqlConnection dengan MARS diaktifkan. Objek perintah pertama mengambil daftar vendor yang nilai kreditnya adalah 5. Objek perintah kedua menggunakan ID vendor yang disediakan dari SqlDataReader untuk memuat SqlDataReader yang kedua dengan semua produk dari vendor tertentu. Setiap rekaman produk dikunjungi untuk kedua kalinya oleh SqlDataReader. Penghitungan dilakukan untuk menentukan apa yang baru OnOrderQty seharusnya. Objek perintah ketiga kemudian digunakan untuk memperbarui ProductVendor tabel dengan nilai baru. Seluruh proses ini terjadi dalam satu transaksi, yang akhirnya digulung balik.

Catatan

Contoh berikut menggunakan database sampel AdventureWorks yang disertakan dengan SQL Server. String koneksi yang disediakan dalam kode sampel mengasumsikan database dipasang dan tersedia di komputer lokal. Ubah string koneksi seperlunya untuk lingkungan Anda.

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");
      }
   }
}

Lihat juga