Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek popisuje asynchronní programování s Microsoft.Data.SqlClient. Nainstalujte balíček NuGet a naimportujte obor názvů Microsoft.Data.SqlClient pro příklady SQL. Microsoft. Data.SqlClient podporuje rozhraní API založená na úlohách v rozhraní .NET Framework a moderní .NET.
Starší asynchronní programování
Před .NET Framework 4.5 se asynchronní programování pomocí System.Data.SqlClient provádělo s následujícími metodami a Asynchronous Processing=true vlastností připojení:
Tato funkce zůstala v SqlClient v rozhraní .NET Framework 4.5.
Návod
Microsoft.Data.SqlClient také poskytuje BeginExecuteNonQuery, BeginExecuteReader a BeginExecuteXmlReader kvůli kompatibilitě. Při migraci na Microsoft.Data.SqlClient odeberte Asynchronous Processing z připojovacích řetězců. Při použití true nebo false klíčové slovo způsobí ArgumentException se zprávou Keyword not supported: 'asynchronous processing'. při vytvoření SqlConnection nebo přiřazení hodnoty její vlastnosti ConnectionString před voláním Open nebo OpenAsync. Klíčové slovo není ignorováno a není vyžadováno pro asynchronní volání.
Asynchronní programovací funkce přidané v rozhraní .NET Framework 4.5
Asynchronní programovací funkce poskytuje jednoduchou techniku, která umožňuje, aby byl kód asynchronní. Další informace o asynchronní programovací funkci, která byla zavedena v rozhraní .NET Framework 4.5, najdete v tématu:
- Asynchronní programování v jazyce C#
- Asynchronní programování pomocí Async a Await (Visual Basic)
- Použití nových asynchronních metod SqlDataReader v rozhraní .NET Framework 4.5 (část 1)
- Použití nových asynchronních metod SqlDataReader v rozhraní .NET Framework 4.5 (část 2)
Pokud vaše uživatelské rozhraní nereaguje nebo se váš server nes škáluje, pravděpodobně potřebujete, aby byl váš kód asynchronní. Psaní asynchronního kódu tradičně zahrnovalo instalaci zpětného volání (označovaného také jako pokračování) k vyjádření logiky, ke které dochází po dokončení asynchronní operace. To komplikuje strukturu asynchronního kódu ve srovnání s synchronním kódem.
Nyní můžete volat asynchronní metody bez použití zpětného volání a bez rozdělení kódu mezi více metod nebo výrazy lambda.
async Modifikátor určuje, že metoda je asynchronní. Při volání async metody se vrátí úkol. Když se await operátor použije na úlohu, aktuální metoda okamžitě ukončí. Po dokončení úlohy se provádění obnoví ve stejné metodě.
Poznámka:
Microsoft. Data.SqlClient nepodporuje Context Connection=true. Když vytvoříte SqlConnection nebo nastavíte jeho vlastnost ConnectionString na tuto hodnotu, ovladač vyvolá InvalidOperationException se zprávou Connecting to the context connection using Microsoft.Data.SqlClient is not supported. Výjimka nastane před voláním Open nebo OpenAsync; nastavení není ignorováno.
Volání metody async nepřiděluje žádná další vlákna. Na konci může krátce použít stávající vlákno pro dokončení vstupně-výstupních operací.
Následující metody základní třídy a knihovny Microsoft.Data.SqlClient podporují asynchronní programování:
- 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
Další asynchronní členy byly přidány k podpoře streamování SqlClient.
Návod
Asynchronní metody založené na úlohách nevyžadují volbu připojovacího řetězce pro povolení asynchronních operací.
Přechod ze synchronního na asynchronní připojení je otevřený
Starší aplikaci můžete upgradovat tak, aby používala novější asynchronní funkci. Předpokládejme například, že aplikace má synchronní algoritmus připojení a blokuje vlákno uživatelského rozhraní při každém připojení k databázi a po připojení aplikace volá uloženou proceduru, která signalizuje ostatní uživatele, kteří se právě přihlásili.
using SqlConnection conn = new SqlConnection("...");
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("StoredProcedure_Logon", conn))
{
cmd.ExecuteNonQuery();
}
}
Při převodu na použití novější asynchronní funkce by program vypadal takto:
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]);
}
}
}
Přidání nové asynchronní funkce do existující aplikace (kombinace starých a nových vzorů)
Je také možné přidat novou asynchronní funkci (SqlConnection::OpenAsync) beze změny stávající asynchronní logiky. Pokud například aplikace aktuálně používá:
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);
Můžete začít používat nový asynchronní vzor, aniž byste podstatně změnili stávající algoritmus.
Následující šablona záměrně neobsahuje ProductList tělo zpětného volání. Ve zpětném volání volejte EndExecuteReader, zpracujte a uvolněte objekt reader a uvolněte příkaz a připojení. Nechte aplikaci aktivní, dokud se zpětné volání neskončí. Šablona není samostatná konzolová aplikace.
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);
}
}
Použijte model základního zprostředkovatele a novou asynchronní funkci
Možná budete muset vytvořit nástroj, který se může připojit k různým databázím a spouštět dotazy. Můžete použít model základního zprostředkovatele a novou asynchronní funkci.
Aby bylo možné používat distribuované transakce, musí být na serveru povolen program Microsoft Distributed Transaction Controller (MSDTC). Informace o tom, jak povolit MSDTC, naleznete v tématu Jak povolit MSDTC na webovém serveru.
Před spuštěním příkladu továrny zaregistrujte Microsoft.Data.SqlClient , jak je popsáno v části Získání DbProviderFactory. Použijte konfiguraci aplikace v .NET Frameworku nebo konfiguraci DbProviderFactories.RegisterFactory v moderním .NETu.
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();
}
}
Důležité
Microsoft doporučuje používat nejbezpečnější dostupný tok ověřování. Pokud se připojujete k Azure SQL, Spravované identity pro prostředky Azure jsou doporučenou metodou ověřování.
Použití transakcí SQL a nové asynchronní funkce
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);
}
}
}
}
}
Použití transakcí SQL a nové asynchronní funkce
V podnikové aplikaci může být potřeba přidat distribuované transakce v některých scénářích, aby bylo možné transakce mezi několika databázovými servery. Můžete použít System.Transactions obor názvů a zařadit distribuovanou transakci následujícím způsobem:
V moderním .NET vyžadují distribuované transakce Windows a .NET 7 a novějších verzích. Nakonfigurujte přístup k síti služba MS DTC (Microsoft Distributed Transaction Coordinator) (MSDTC) na zúčastněných počítačích. Před spuštěním následujícího příkladu v moderních .NET povolte povýšení transakcí při spuštění aplikace před vytvořením jakýchkoli transakcí:
TransactionManager.ImplicitDistributedTransactions = true;
Vlastnost ImplicitDistributedTransactions je ve výchozím nastavení false. Bez tohoto explicitního povolení vyvolá povýšení na distribuovanou transakci výjimku NotSupportedException. V .NET Framework toto přiřazení vynecháte, protože vlastnost není k dispozici; Konfigurace MSDTC je stále nutná.
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);
}
}
}
}
}
}
Důležité
Microsoft doporučuje používat nejbezpečnější dostupný tok ověřování. Pokud se připojujete k Azure SQL, Spravované identity pro prostředky Azure jsou doporučenou metodou ověřování.
Zrušení asynchronní operace
Asynchronní požadavek můžete zrušit pomocí metody 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);
}
}
}
}
Asynchronní operace s sqlBulkCopy
Používá se SqlBulkCopy.WriteToServerAsync pro asynchronní hromadné kopírování s Microsoft.Data.SqlClient.SqlBulkCopy.
Pro SqlClient a ODBC použijte samostatné připojovací řetězce. Nahraďte <server> instancí SQL Serveru a nainstalujte ovladač ODBC Driver 18 for SQL Server pro ukázku se smíšenými poskytovateli. V moderních .NET také přidejte odkaz na balíček System.Data.Odbc. SqlClient připojovací řetězec umožňuje v posledním příkladu několik aktivních sad výsledků (MARS).
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);
}
}
}
}
}
}
}
}
Asynchronní použití více příkazů s MARS
V příkladu AdventureWorks se otevře jedno připojení k databázi. Pomocí objektu SqlCommand se vytvoří SqlDataReader. Při použití čtečky je otevřena druhá SqlDataReader s využitím dat z první SqlDataReader jako vstupu do klauzule WHERE pro druhou čtečku.
Poznámka:
Následující příklad používá ukázkovou AdventureWorks databázi, která je součástí SQL Serveru. Připojovací řetězec poskytnuté v ukázkovém kódu předpokládá, že je databáze nainstalovaná a dostupná v místním počítači. Upravte připojovací řetězec podle potřeby pro vaše prostředí.
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());
}
}
}
}
}
}
}
Asynchronní čtení a aktualizace dat pomocí MARS
MARS umožňuje používat připojení pro operace čtení i operace jazyka DML (Data Manipulat Language) s více než jednou čekající operací. Tato funkce eliminuje potřebu aplikace řešit chyby, když je připojení zaneprázdněné. Kromě toho může MARS nahradit uživatele kurzorů na straně serveru, které obecně spotřebovávají více prostředků. A konečně, protože na jednom připojení může fungovat více operací, můžou sdílet stejný kontext transakce, což eliminuje potřebu používat sp_getbindtoken a sp_bindsession systémové uložené procedury.
Následující konzolová aplikace ukazuje, jak používat dva SqlDataReader objekty se třemi SqlCommand objekty a jeden SqlConnection objekt s povoleným MARS. První objekt příkazu načte seznam dodavatelů, jejichž rating je 5. Druhý objekt příkazu používá ID dodavatele poskytnutého z objektu SqlDataReader k načtení druhého SqlDataReader se všemi produkty pro konkrétního dodavatele. Každý záznam o produktu je navštíven druhým SqlDataReader. Provede se výpočet, který určí, co má být nové OnOrderQty . Třetí objekt příkazu se pak použije k aktualizaci ProductVendor tabulky novou hodnotou. Celý proces probíhá v rámci jedné transakce, která se vrátí zpět na konec.
Poznámka:
Následující příklad používá ukázkovou AdventureWorks databázi, která je součástí SQL Serveru. Připojovací řetězec poskytnuté v ukázkovém kódu předpokládá, že je databáze nainstalovaná a dostupná v místním počítači. Upravte připojovací řetězec podle potřeby pro vaše prostředí.
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");
}
}
}