Programowanie asynchroniczne

W tym artykule omówiono programowanie asynchroniczne przy użyciu Microsoft.Data.SqlClient. Zainstaluj pakiet NuGet i zaimportuj Microsoft.Data.SqlClient przestrzeń nazw dla przykładów SQL. Microsoft.Data.SqlClient obsługuje asynchroniczne interfejsy API oparte na zadaniach na platformie .NET Framework i w nowoczesnym środowisku .NET.

Starsze programowanie asynchroniczne

Przed .NET Framework 4.5 programowanie asynchroniczne przy użyciu System.Data.SqlClient odbywało się przy użyciu następujących metod i właściwości połączenia Asynchronous Processing=true:

Ta funkcja pozostała w programie SqlClient w programie .NET Framework 4.5.

Wskazówka

Microsoft.Data.SqlClient zapewnia również BeginExecuteNonQuery, BeginExecuteReader i BeginExecuteXmlReader dla zgodności. Usuń Asynchronous Processing z ciągów połączenia podczas migracji do Microsoft.Data.SqlClient. W przypadku true lub false słowo kluczowe powoduje ArgumentException z komunikatem Keyword not supported: 'asynchronous processing'. podczas konstruowania SqlConnection lub przypisywania wartości do właściwości ConnectionString przed wywołaniem metody Open lub OpenAsync. Słowo kluczowe nie jest ignorowane i nie jest wymagane w przypadku wywołań asynchronicznych.

Funkcje programowania asynchronicznego dodane w programie .NET Framework 4.5

Funkcja programowania asynchronicznego oferuje prostą metodę, aby uczynić kod asynchronicznym. Aby uzyskać więcej informacji na temat funkcji programowania asynchronicznego wprowadzonej w programie .NET Framework 4.5, zobacz:

Jeśli interfejs użytkownika nie odpowiada lub serwer nie jest skalowany, prawdopodobnie kod będzie bardziej asynchroniczny. Pisanie kodu asynchronicznego tradycyjnie polegało na zainstalowaniu wywołania zwrotnego (nazywanego również kontynuacją) w celu wyrażenia logiki, która występuje po zakończeniu operacji asynchronicznej. Komplikuje to strukturę kodu asynchronicznego w porównaniu z kodem synchronicznym.

Teraz można wywołać metody asynchroniczne bez użycia wywołań zwrotnych i bez dzielenia kodu między wiele metod lub wyrażeń lambda.

Modyfikator async określa, że metoda jest asynchroniczna. Podczas wywoływania async metody zwracane jest zadanie. Gdy operator await zostanie zastosowany do zadania, aktualna metoda kończy się natychmiast. Po zakończeniu zadania wykonywanie zostanie wznowione w tej samej metodzie.

Uwaga

Microsoft.Data.SqlClient nie obsługuje Context Connection=true. Podczas konstruowania SqlConnection lub przypisywania jej ConnectionString właściwości za pomocą tego ustawienia sterownik zgłasza InvalidOperationException z komunikatem Connecting to the context connection using Microsoft.Data.SqlClient is not supported. Wyjątek występuje przed wywołaniem Open lub OpenAsync; ustawienie nie jest ignorowane.

async Wywołanie metody nie przydziela żadnych dodatkowych wątków. Może korzystać z istniejącego wątku uzupełniania we/wy na krótko na końcu.

Następujące metody klasy bazowej i Microsoft.Data.SqlClient obsługują programowanie asynchroniczne:

Dodano inne asynchroniczne elementy członkowskie, aby obsługiwać SqlClient Streaming Support.

Wskazówka

Metody asynchroniczne oparte na zadaniach nie wymagają opcji parametry połączenia do włączania operacji asynchronicznych.

Otwarcie połączenia z synchronicznego na asynchroniczne

Możesz uaktualnić starszą aplikację, aby użyć nowszej funkcji asynchronicznej. Załóżmy na przykład, że aplikacja ma algorytm połączenia synchronicznego i blokuje wątek interfejsu użytkownika za każdym razem, gdy łączy się z bazą danych, a po nawiązaniu połączenia aplikacja wywołuje procedurę składowaną, która sygnalizuje innym użytkownikom o osobie, która właśnie się zalogowała.

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

Po przekonwertowaniu na korzystanie z nowszych funkcji asynchronicznych program będzie wyglądać następująco:

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

Dodaj nową funkcję asynchroniczną w istniejącej aplikacji (mieszaj stare i nowe wzorce)

Istnieje również możliwość dodania nowej możliwości asynchronicznej (SqlConnection::OpenAsync) bez zmiany istniejącej logiki asynchronicznej. Jeśli na przykład aplikacja aktualnie używa:

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

Możesz zacząć używać nowego wzorca asynchronicznego bez istotnej zmiany istniejącego algorytmu.

Poniższy szablon celowo pomija treść callback ProductList. W wywołaniu zwrotnym wywołaj EndExecuteReader, przetwórz i usuń czytnik, a następnie usuń polecenie i połączenie. Utrzymuj aplikację uruchomioną do momentu zakończenia callbacku. Szablon nie jest autonomiczną aplikacją konsolową.

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

Używanie podstawowego modelu dostawcy i nowej funkcji asynchronicznej

Może być konieczne utworzenie narzędzia, które może łączyć się z różnymi bazami danych i wykonywać zapytania. Możesz użyć podstawowego modelu dostawcy i nowej funkcji asynchronicznej.

Kontroler transakcji rozproszonych firmy Microsoft (MSDTC) musi być włączony na serwerze w celu korzystania z transakcji rozproszonych. Aby uzyskać informacje na temat włączania usługi MSDTC, zobacz Jak włączyć usługę MSDTC na serwerze sieci Web.

Przed uruchomieniem przykładu fabryki zarejestruj Microsoft.Data.SqlClient zgodnie z opisem w temacie Uzyskiwanie elementu DbProviderFactory. Użyj konfiguracji aplikacji na platformie .NET Framework lub DbProviderFactories.RegisterFactory w nowoczesnym .NET.

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

Ważne

Firma Microsoft zaleca korzystanie z najbezpieczniejszego dostępnego przepływu uwierzytelniania. Jeśli łączysz się z usługą Azure SQL, tożsamości zarządzane dla zasobów platformy Azure to zalecana metoda uwierzytelniania.

Używanie transakcji SQL i nowej funkcji asynchronicznej

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

Używanie transakcji SQL i nowej funkcji asynchronicznej

W aplikacji dla przedsiębiorstw może być konieczne dodanie transakcji rozproszonych w niektórych scenariuszach, aby umożliwić transakcje między wieloma serwerami baz danych. Możesz użyć przestrzeni nazw System.Transactions i włączyć transakcję rozproszoną w następujący sposób:

We współczesnym środowisku .NET transakcje rozproszone wymagają systemu Windows oraz platformy .NET 7 i nowszych wersji. Skonfiguruj dostęp do sieci Microsoft Distributed Transaction Coordinator (MSDTC) na uczestniczących komputerach. Przed uruchomieniem następującego przykładu w nowoczesnym środowisku .NET włącz promowanie transakcji podczas uruchamiania aplikacji, przed utworzeniem jakichkolwiek transakcji:

TransactionManager.ImplicitDistributedTransactions = true;

Właściwość ImplicitDistributedTransactions ma domyślną wartość false. Bez tej zgody promowanie do transakcji rozproszonej zgłasza wyjątek NotSupportedException. W .NET Framework pomiń to przypisanie, ponieważ właściwość jest niedostępna; Konfiguracja MSDTC jest nadal wymagana.

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

Ważne

Firma Microsoft zaleca korzystanie z najbezpieczniejszego dostępnego przepływu uwierzytelniania. Jeśli łączysz się z usługą Azure SQL, tożsamości zarządzane dla zasobów platformy Azure to zalecana metoda uwierzytelniania.

Anulowanie operacji asynchronicznej

Żądanie asynchroniczne można anulować przy użyciu elementu 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);
         }
      }
   }
}

Operacje asynchroniczne za pomocą narzędzia SqlBulkCopy

Służy SqlBulkCopy.WriteToServerAsync do asynchronicznego kopiowania zbiorczego za pomocą polecenia Microsoft.Data.SqlClient.SqlBulkCopy.

Użyj oddzielnych ciągów połączeń dla programów SqlClient i ODBC. Zastąp <server> swoim wystąpieniem programu SQL Server i zainstaluj sterownik ODBC Driver 18 for SQL Server na potrzeby przykładu z mieszanymi dostawcami. W nowoczesnych .NET należy również odwołać się do pakietu System.Data.Odbc. Connection string SqlClient włącza wiele aktywnych zestawów wyników (MARS) dla ostatniego przykładu.

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

Asynchronicznie używaj wielu poleceń z MARS

W przykładzie zostanie otwarte pojedyncze połączenie z bazą AdventureWorks danych. Przy użyciu obiektu SqlCommand tworzony jest SqlDataReader. Gdy czytnik jest używany, drugi SqlDataReader jest otwierany, używając danych z pierwszego SqlDataReader jako danych wejściowych do klauzuli WHERE dla drugiego czytelnika.

Uwaga

W poniższym przykładzie użyto przykładowej AdventureWorks bazy danych dołączonej do programu SQL Server. Parametry połączenia podany w przykładowym kodzie zakłada, że baza danych jest zainstalowana i dostępna na komputerze lokalnym. Zmodyfikuj łańcuch połączenia zgodnie z potrzebami środowiska.

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

Asynchronicznie odczytywanie i aktualizowanie danych za pomocą usługi MARS

Usługa MARS umożliwia użycie połączenia zarówno dla operacji odczytu, jak i operacji manipulowania danymi (DML) z więcej niż jedną oczekującą operacją. Funkcja ta eliminuje konieczność radzenia sobie z błędami związanymi z zajętymi połączeniami przez aplikację. Ponadto usługa MARS może zastąpić użytkownika kursorów po stronie serwera, co zwykle zużywa więcej zasobów. Na koniec, ponieważ wiele operacji może działać na jednym połączeniu, może współużytkować ten sam kontekst transakcji, eliminując konieczność użycia sp_getbindtoken i sp_bindsession systemowych procedur składowanych.

Poniższa aplikacja konsolowa pokazuje, jak używać dwóch SqlDataReader obiektów z trzema SqlCommand obiektami i jednym SqlConnection obiektem z włączonym usługą MARS. Pierwszy obiekt polecenia pobiera listę dostawców, których ocena kredytowa wynosi 5. Drugi obiekt polecenia używa identyfikatora dostawcy dostarczonego z elementu SqlDataReader , aby załadować drugi SqlDataReader ze wszystkimi produktami dla określonego dostawcy. Każdy rekord produktu jest odwiedzany co sekundę SqlDataReader. Wykonywane jest obliczenie w celu określenia, jaka powinna być nowa wartość OnOrderQty. Trzeci obiekt polecenia jest następnie używany do aktualizowania ProductVendor tabeli przy użyciu nowej wartości. Cały proces odbywa się w ramach jednej transakcji, która zostaje wycofana na końcu.

Uwaga

W poniższym przykładzie użyto przykładowej AdventureWorks bazy danych dołączonej do programu SQL Server. Parametry połączenia podany w przykładowym kodzie zakłada, że baza danych jest zainstalowana i dostępna na komputerze lokalnym. Zmodyfikuj łańcuch połączenia zgodnie z potrzebami środowiska.

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

Zobacz też