Pemrograman asinkron
Berlaku untuk: .NET Framework .NET .NET Standard
Artikel ini membahas dukungan untuk pemrograman asinkron di Penyedia Data Microsoft SqlClient untuk SQL Server (SqlClient).
Pemrograman asinkron warisan
Penyedia Data Microsoft SqlClient untuk SQL Server menyertakan metode dari System.Data.SqlClient untuk mempertahankan kompatibilitas mundur untuk aplikasi yang bermigrasi ke Microsoft.Data.SqlClient. Tidak disarankan untuk menggunakan metode pemrograman asinkron warisan berikut untuk pengembangan baru:
Tip
Di Penyedia Data Microsoft SqlClient untuk SQL Server, metode warisan ini tidak lagi diperlukan Asynchronous Processing=true
dalam string koneksi.
Fitur pemrograman asinkron
Fitur pemrograman asinkron ini menyediakan teknik sederhana untuk membuat kode asinkron.
Untuk informasi selengkapnya tentang pemrograman asinkron di .NET, 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. Gaya ini mempersulit struktur kode asinkron dibandingkan dengan kode sinkron.
Anda 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.
Tip
Di Penyedia Data Microsoft SqlClient untuk SQL Server, panggilan asinkron tidak diperlukan untuk mengatur Context Connection
kata kunci string koneksi.
Memanggil async
metode tidak membuat utas tambahan. Ini dapat menggunakan utas penyelesaian I/O yang ada secara singkat di akhir.
Metode berikut di Penyedia Data Microsoft SqlClient untuk SQL Server mendukung pemrograman asinkron:
Anggota asinkron lainnya mendukung dukungan streaming SqlClient.
Tip
Metode asinkron tidak diperlukan Asynchronous Processing=true
dalam string koneksi. Dan properti ini usang di Penyedia Data Microsoft SqlClient untuk SQL Server.
Sinkron ke koneksi asinkron terbuka
Anda dapat meningkatkan aplikasi yang ada untuk menggunakan fitur asinkron. Misalnya, asumsikan aplikasi memiliki algoritma koneksi sinkron dan memblokir utas UI setiap kali terhubung ke database. Setelah tersambung, aplikasi memanggil prosedur tersimpan yang memberi sinyal kepada pengguna lain dari yang baru saja masuk.
using System;
using System.Data;
using Microsoft.Data.SqlClient;
namespace SqlCommandCS
{
class Program
{
static void Main()
{
string str = "Data Source=(local);Initial Catalog=Northwind;"
+ "Integrated Security=SSPI";
string qs = "SELECT OrderID, CustomerID FROM dbo.Orders;";
CreateCommand(qs, str);
}
private static void CreateCommand(string queryString,
string connectionString)
{
using (SqlConnection connection = new SqlConnection(
connectionString))
{
SqlCommand command = new SqlCommand(queryString, connection);
command.Connection.Open();
command.ExecuteNonQuery();
}
}
}
}
Ketika dikonversi untuk menggunakan fungsionalitas asinkron, program akan terlihat seperti:
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class A {
public static void Main()
{
using (SqlConnection conn = new SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI"))
{
SqlCommand command = new SqlCommand("SELECT TOP 2 * FROM dbo.Orders", conn);
int result = A.Method(conn, command).Result;
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
Console.WriteLine(reader[0]);
}
}
static async Task<int> Method(SqlConnection conn, SqlCommand cmd) {
await conn.OpenAsync();
await cmd.ExecuteNonQueryAsync();
return 1;
}
}
Tambahkan fitur asinkron dalam aplikasi yang ada (mencampur pola lama dan baru)
Anda juga dapat menambahkan kemampuan asinkron (SqlConnection::OpenAsync) tanpa mengubah logika asinkron yang ada. Misalnya, jika aplikasi saat ini menggunakan:
AsyncCallback productList = new AsyncCallback(ProductList);
SqlConnection conn = new SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI");
conn.Open();
SqlCommand cmd = new SqlCommand("select top 2 * from orders", conn);
IAsyncResult ia = cmd.BeginExecuteReader(productList, cmd);
Anda dapat mulai menggunakan pola asinkron tanpa mengubah algoritma yang ada secara substansial.
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("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI");
// conn.Open();
// SqlCommand cmd = new SqlCommand("select top 2 * from orders", conn);
// IAsyncResult ia = cmd.BeginExecuteReader(productList, cmd);
AsyncCallback productList = new AsyncCallback(ProductList);
SqlConnection conn = new SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI");
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
Anda mungkin perlu membuat alat yang dapat terhubung ke database yang berbeda dan menjalankan kueri. Anda dapat menggunakan model penyedia dasar dan fitur asinkron.
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.
using System.Data.Common;
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class program
{
static async Task PerformDBOperationsUsingProviderModel(string connectionString)
{
using (DbConnection connection = SqlClientFactory.Instance.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 = "localhost";
builder.InitialCatalog = "pubs";
builder.IntegratedSecurity = true;
Task task = PerformDBOperationsUsingProviderModel(builder.ConnectionString);
task.Wait();
}
}
Menggunakan transaksi SQL dan fitur asinkron baru
using Microsoft.Data.SqlClient;
using System.Threading.Tasks;
class Program
{
static void Main()
{
string connectionString =
"Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)";
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);
}
}
}
}
}
Gunakan transaksi terdistribusi 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 namespace layanan System.Transactions dan mendaftarkan transaksi terdistribusi, sebagai berikut:
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
// create two tables RegionTable1 and RegionTable2
// and add a constraint in one of these tables
// to avoid duplicate RegionID
builder.DataSource = "localhost";
builder.InitialCatalog = "Northwind";
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);
}
}
}
}
}
}
Membatalkan operasi asinkron
Anda dapat membatalkan permintaan asinkron dengan menggunakan CancellationToken.
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 (exception.InnerException is SqlException)
{
Console.WriteLine("Operation canceled");
}
else
{
throw;
}
}
}
static async Task CancellingAsynchronousOperations(CancellationToken cancellationToken)
{
using (SqlConnection connection = new SqlConnection("Server=(local);Integrated Security=true"))
{
await connection.OpenAsync(cancellationToken);
SqlCommand command = new SqlCommand("WAITFOR DELAY '00:10:00'", connection);
await command.ExecuteNonQueryAsync(cancellationToken);
}
}
}
}
Operasi asinkron dengan SqlBulkCopy
Kemampuan asinkron juga ada di Microsoft.Data.SqlClient.SqlBulkCopy dengan SqlBulkCopy.WriteToServerAsync.
using System.Data;
using Microsoft.Data.SqlClient;
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)";
// Replace the connection string if needed, for instance to connect to SQL Express: @"Server=(local)\SQLEXPRESS;Database=Demo;Integrated Security=true"
// static string connectionString = @"Server=(localdb)\V11.0;Database=Demo";
static string connectionString = @"Server=(local);Database=Demo;Integrated Security=true";
// static string marsConnectionString = @"Server=(localdb)\V11.0;Database=Demo;MultipleActiveResultSets=true;";
static string marsConnectionString = @"Server=(local);Database=Demo;MultipleActiveResultSets=true;Integrated Security=true";
// Replace the Server name with your actual sql azure server name and User ID/Password
static string azureConnectionString = @"Server=SqlAzure;User ID=<myUserID>;Password=<myPassword>;Database=Demo";
static void Main(string[] args)
{
SynchronousSqlBulkCopy();
AsyncSqlBulkCopy().Wait();
MixSyncAsyncSqlBulkCopy().Wait();
AsyncSqlBulkCopyNotifyAfter().Wait();
AsyncSqlBulkCopyDataRows().Wait();
AsyncSqlBulkCopySqlServerToSqlAzure().Wait();
AsyncSqlBulkCopyCancel().Wait();
AsyncSqlBulkCopyMARS().Wait();
}
// 3.1.1 Synchronous bulk copy in .NET 4.5
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 in .NET 4.5
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 (SqlConnection conn1 = new SqlConnection(connectionString))
{
conn1.Open();
using (SqlCommand cmd = new SqlCommand(selectStatement, conn1))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
using (SqlConnection conn2 = new SqlConnection(connectionString))
{
await conn2.OpenAsync();
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
SqlCommand createCmd = new SqlCommand(string.Format(createDestTableStatement, temptable), conn2);
await createCmd.ExecuteNonQueryAsync();
using (SqlBulkCopy bcp = new SqlBulkCopy(conn2))
{
bcp.DestinationTableName = temptable;
await bcp.WriteToServerAsync(reader);
}
}
}
}
}
}
// 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.5 Copying data from SQL Server to SQL Azure in .NET 4.5
private static async Task AsyncSqlBulkCopySqlServerToSqlAzure()
{
using (SqlConnection srcConn = new SqlConnection(connectionString))
using (SqlConnection destConn = new SqlConnection(azureConnectionString))
{
await srcConn.OpenAsync();
await destConn.OpenAsync();
using (SqlCommand srcCmd = new SqlCommand(selectStatement, srcConn))
{
using (SqlDataReader reader = await srcCmd.ExecuteReaderAsync())
{
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
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(reader);
}
}
}
}
}
}
// 3.6 Cancelling an Asynchronous Operation to SQL Azure
private static async Task AsyncSqlBulkCopyCancel()
{
CancellationTokenSource cts = new CancellationTokenSource();
using (SqlConnection srcConn = new SqlConnection(connectionString))
using (SqlConnection destConn = new SqlConnection(azureConnectionString))
{
await srcConn.OpenAsync(cts.Token);
await destConn.OpenAsync(cts.Token);
using (SqlCommand srcCmd = new SqlCommand(selectStatement, srcConn))
{
using (SqlDataReader reader = await srcCmd.ExecuteReaderAsync(cts.Token))
{
string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
using (SqlCommand destCmd = new SqlCommand(string.Format(createDestTableStatement, temptable), destConn))
{
await destCmd.ExecuteNonQueryAsync(cts.Token);
using (SqlBulkCopy bcp = new SqlBulkCopy(destConn))
{
bcp.DestinationTableName = temptable;
await bcp.WriteToServerAsync(reader, cts.Token);
//Cancel Async SqlBulCopy Operation after 200 ms
cts.CancelAfter(200);
}
}
}
}
}
}
// 3.7 Using Async.Net and MARS
private static async Task AsyncSqlBulkCopyMARS()
{
using (SqlConnection marsConn = new SqlConnection(marsConnectionString))
{
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 database AdventureWorks. Menggunakan SqlCommandobjek , sebuah SqlDataReader dibuat. Saat pembaca digunakan, satu detik SqlDataReader dibuka, menggunakan data dari SqlDataReader yang pertama sebagai input untuk klausul WHERE untuk pembaca kedua.
Catatan
Contoh berikut menggunakan sampel database AdventureWorks. String koneksi yang disediakan dalam kode sampel mengasumsikan database dipasang dan tersedia di komputer lokal. Ubah string koneksi seperlunya untuk lingkungan Anda.
using System.Data.Common;
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 BusinessEntityID, 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.BusinessEntityID = @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["BusinessEntityID"];
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());
}
}
}
}
}
}
private static string GetConnectionString()
{
// To avoid storing the connection string in your code, you can retrieve it from a configuration file.
return "Data Source=(local);Integrated Security=SSPI;Initial Catalog=AdventureWorks;MultipleActiveResultSets=True";
}
}
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 akan penanganan kesalahan koneksi-sibuk. Selain itu, MARS dapat menggantikan penggunaan kursor sisi server, yang umumnya menggunakan lebih banyak sumber daya. Akhirnya, karena beberapa operasi dapat dijalankan pada satu koneksi, maka dapat berbagi konteks transaksi yang sama, yang menghilangkan kebutuhan untuk menggunakan prosedur tersimpan sistem sp_getbindtoken dan sp_bindsession.
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 untuk vendor tertentu. Setiap rekaman produk dikunjungi oleh SqlDataReader yang kedua. Penghitungan dilakukan untuk menentukan OnOrderQty baru yang seharusnya. Objek perintah ketiga kemudian digunakan untuk memperbarui tabel ProductVendor dengan nilai baru. Seluruh proses ini terjadi dalam satu transaksi, yang akhirnya digulung balik.
Catatan
Contoh berikut menggunakan sampel database AdventureWorks. String koneksi yang disediakan dalam kode sampel mengasumsikan database dipasang dan tersedia di komputer lokal. Ubah string koneksi seperlunya untuk lingkungan Anda.
using System.Data.Common;
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 BusinessEntityID, Name FROM Purchasing.Vendor " +
"WHERE CreditRating = 5";
string prodVendSQL =
"SELECT ProductID, MaxOrderQty, MinOrderQty, OnOrderQty " +
"FROM Purchasing.ProductVendor " +
"WHERE BusinessEntityID = @VendorID";
string updateSQL =
"UPDATE Purchasing.ProductVendor " +
"SET OnOrderQty = @OrderQty " +
"WHERE ProductID = @ProductID AND BusinessEntityID = @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["BusinessEntityID"];
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: ", totalRecordsUpdated.ToString());
await Task.Run(() => updateTx.Rollback());
Console.WriteLine("Transaction Rolled Back");
}
}
private static string GetConnectionString()
{
// To avoid storing the connection string in your code, you can retrieve it from a configuration file.
return "Data Source=(local);Integrated Security=SSPI;Initial Catalog=AdventureWorks;MultipleActiveResultSets=True";
}
}