Asynchroon programmeren

In dit artikel wordt asynchrone programmering besproken met Microsoft.Data.SqlClient. Installeer het NuGet-pakket en importeer de Microsoft.Data.SqlClient naamruimte voor de SQL-voorbeelden. Microsoft. Data.SqlClient ondersteunt taakgebaseerde API's op .NET Framework en moderne .NET.

Verouderde asynchrone programmering

Vóór .NET Framework 4.5 werd asynchrone programmering met System.Data.SqlClient uitgevoerd met de volgende methoden en de Asynchronous Processing=true verbindingseigenschap:

Deze functionaliteit bleef in SqlClient in .NET Framework 4.5.

Aanbeveling

Microsoft. Data.SqlClient biedt BeginExecuteNonQueryook , BeginExecuteReaderen BeginExecuteXmlReader voor compatibiliteit. Verwijder Asynchronous Processing uit verbindingsreeksen wanneer u migreert naar Microsoft.Data.SqlClient. Met false of true leidt het trefwoord tot ArgumentException met het bericht Keyword not supported: 'asynchronous processing'. wanneer u SqlConnection construeert of de eigenschap ConnectionString ervan instelt, voordat u Open of OpenAsync aanroept. Het trefwoord wordt niet genegeerd en is niet vereist voor asynchrone aanroepen.

Asynchrone programmeerfuncties toegevoegd in .NET Framework 4.5

De asynchrone programmeerfunctie biedt een eenvoudige techniek om code asynchroon te maken. Zie voor meer informatie over de asynchrone programmeerfunctie die is geïntroduceerd in .NET Framework 4.5:

Wanneer uw gebruikersinterface niet reageert of uw server niet schaalt, is het waarschijnlijk dat uw code asynchroon moet zijn. Het schrijven van asynchrone code heeft traditioneel betrekking op het installeren van een callback (ook wel vervolg genoemd) om de logica uit te drukken die optreedt nadat de asynchrone bewerking is voltooid. Dit bemoeilijkt de structuur van asynchrone code in vergelijking met synchrone code.

U kunt nu aanroepen naar asynchrone methoden zonder callbacks te gebruiken en zonder dat u uw code over meerdere methoden of lambda-expressies splitst.

De async wijzigingsfunctie geeft aan dat een methode asynchroon is. Wanneer u een async methode aanroept, wordt een taak geretourneerd. Wanneer de await operator wordt toegepast op een taak, wordt de huidige methode onmiddellijk afgesloten. Wanneer de taak is voltooid, wordt de uitvoering hervat in dezelfde methode.

Notitie

Microsoft.Data.SqlClient biedt geen ondersteuning voor Context Connection=true. Wanneer u ConnectionString maakt of de eigenschap SqlConnection ervan op deze instelling instelt, genereert het stuurprogramma InvalidOperationException met het bericht Connecting to the context connection using Microsoft.Data.SqlClient is not supported.. De uitzondering treedt op voordat u Open of OpenAsync aanroept; de instelling wordt niet genegeerd.

Het aanroepen van een async methode wijst geen extra threads toe. Mogelijk wordt de bestaande I/O-voltooiingsthread kort aan het einde gebruikt.

De volgende basisklasse en Microsoft. Data.SqlClient-methoden ondersteunen asynchrone programmering:

Andere asynchrone leden zijn toegevoegd ter ondersteuning van sqlClient Streaming Support.

Aanbeveling

Taakgebaseerde asynchrone methoden vereisen geen verbindingsreeks-optie om asynchrone bewerkingen in te schakelen.

Overgang van synchrone naar asynchrone verbinding geopend

U kunt een oudere toepassing upgraden om de nieuwere asynchrone functie te gebruiken. Stel dat een toepassing een synchrone verbindingsalgoritmen heeft en de UI-thread blokkeert telkens wanneer deze verbinding maakt met de database en, zodra de toepassing is verbonden, een opgeslagen procedure aanroept die andere gebruikers van degene aangeeft die zojuist zijn aangemeld.

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

Wanneer het programma wordt geconverteerd om de nieuwere asynchrone functionaliteit te gebruiken, ziet het programma er als volgt uit:

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

De nieuwe asynchrone functie toevoegen in een bestaande app (oude en nieuwe patronen combineren)

Het is ook mogelijk om nieuwe asynchrone mogelijkheid (SqlConnection::OpenAsync) toe te voegen zonder de bestaande asynchrone logica te wijzigen. Als een toepassing momenteel bijvoorbeeld gebruikmaakt van:

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

U kunt het nieuwe asynchrone patroon gaan gebruiken zonder het bestaande algoritme aanzienlijk te wijzigen.

In de volgende sjabloon wordt de hoofdtekst van de ProductList callback opzettelijk weggelaten. Roep in uw callback EndExecuteReader aan, verwerk en disponeer de reader, en disponeer de opdracht en de verbinding. Houd de toepassing actief totdat de callback is voltooid. De sjabloon is geen zelfstandige consoletoepassing.

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

Het basisprovidermodel en de nieuwe asynchrone functie gebruiken

Mogelijk moet u een hulpprogramma maken dat verbinding kan maken met verschillende databases en query's kan uitvoeren. U kunt het basisprovidermodel en de nieuwe asynchrone functie gebruiken.

De Microsoft Distributed Transaction Controller (MSDTC) moet zijn ingeschakeld op de server om gedistribueerde transacties te kunnen gebruiken. Zie MSDTC inschakelen op een webserver voor meer informatie over het inschakelen van MSDTC.

Voordat u het factory-voorbeeld uitvoert, moet u Microsoft.Data.SqlClient registreren zoals beschreven in Het verkrijgen van een DbProviderFactory. Gebruik toepassingsconfiguratie in .NET Framework of DbProviderFactories.RegisterFactory op moderne .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();
   }
}

Belangrijk

Microsoft raadt u aan de veiligste verificatiestroom te gebruiken die beschikbaar is. Als u verbinding maakt met Azure SQL, is Managed Identities voor Azure-resources de aanbevolen verificatiemethode.

SQL-transacties en de nieuwe asynchrone functie gebruiken

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

SQL-transacties en de nieuwe asynchrone functie gebruiken

In een bedrijfstoepassing moet u in sommige scenario's mogelijk gedistribueerde transacties toevoegen om transacties tussen meerdere databaseservers mogelijk te maken. U kunt de System.Transactions naamruimte als volgt gebruiken en een gedistribueerde transactie inschakelen:

Op moderne .NET worden gedistribueerde transacties alleen ondersteund op Windows en met .NET 7 en latere versies. Configureer Microsoft Distributed Transaction Coordinator netwerktoegang (MSDTC) op de deelnemende computers. Voordat u het volgende voorbeeld uitvoert op moderne .NET, schakelt u transactiepromotie in bij het opstarten van de toepassing voordat u transacties maakt:

TransactionManager.ImplicitDistributedTransactions = true;

De ImplicitDistributedTransactions eigenschap wordt standaard ingesteld op false. Zonder deze opt-in leidt promotie naar een gedistribueerde transactie tot NotSupportedException. Laat in .NET Framework deze toewijzing weg omdat de eigenschap niet beschikbaar is; MSDTC-configuratie is nog steeds vereist.

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

Belangrijk

Microsoft raadt u aan de veiligste verificatiestroom te gebruiken die beschikbaar is. Als u verbinding maakt met Azure SQL, is Managed Identities voor Azure-resources de aanbevolen verificatiemethode.

Een asynchrone bewerking annuleren

U kunt een asynchrone aanvraag annuleren met behulp van de CancellationToken.

using System;
using Microsoft.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;

namespace Samples {
   class CancellationSample {
      public static void Main(string[] args) {
         CancellationTokenSource source = new CancellationTokenSource();
         source.CancelAfter(2000); // give up after 2 seconds
         try {
            Task result = CancellingAsynchronousOperations(source.Token);
            result.Wait();
         }
         catch (AggregateException exception) {
            if (source.IsCancellationRequested &&
                (exception.InnerException is OperationCanceledException ||
                 exception.InnerException is SqlException)) {
               Console.WriteLine("Operation canceled");
            }
            else {
               throw;
            }
         }
      }

      static async Task CancellingAsynchronousOperations(CancellationToken cancellationToken) {
         using (SqlConnection connection = new SqlConnection("...")) {
            await connection.OpenAsync(cancellationToken);

            SqlCommand command = new SqlCommand("WAITFOR DELAY '00:10:00'", connection);
            await command.ExecuteNonQueryAsync(cancellationToken);
         }
      }
   }
}

Asynchrone bewerkingen met SqlBulkCopy

Gebruik SqlBulkCopy.WriteToServerAsync voor asynchrone bulkkopie met Microsoft.Data.SqlClient.SqlBulkCopy.

Gebruik afzonderlijke verbindingsreeksen voor SqlClient en ODBC. Vervang <server> door uw SQL Server-exemplaar en installeer ODBC-stuurprogramma 18 voor SQL Server voor het voorbeeld van de gemengde provider. In moderne .NET neemt u ook een verwijzing op naar het System.Data.Odbc-pakket. De SqlClient-verbindingsreeks maakt meerdere actieve resultatensets (MARS) mogelijk voor het laatste voorbeeld.

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

Asynchroon meerdere opdrachten gebruiken met MARS

In het voorbeeld wordt één verbinding met de AdventureWorks database geopend. Er wordt een SqlCommand object gemaakt met behulp van een SqlDataReader. Terwijl de lezer wordt gebruikt, wordt een tweede SqlDataReader geopend, waarbij gegevens uit de eerste SqlDataReader worden gebruikt als invoer voor de WHERE-component voor de tweede lezer.

Notitie

In het volgende voorbeeld wordt de voorbeelddatabase AdventureWorks gebruikt die is opgenomen in SQL Server. Bij het verbindingsreeks in de voorbeeldcode wordt ervan uitgegaan dat de database is geïnstalleerd en beschikbaar is op de lokale computer. Wijzig de verbindingsreeks indien nodig voor uw omgeving.

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

Asynchroon gegevens lezen en bijwerken met MARS

MET MARS kan een verbinding worden gebruikt voor zowel leesbewerkingen als DML-bewerkingen (Data Manipulat Language) met meer dan één in behandeling zijnde bewerking. Deze functie elimineert de noodzaak voor een applicatie om verbindingsbezetfouten af te handelen. Bovendien kan MARS de gebruiker van cursors aan de serverzijde vervangen, die over het algemeen meer resources verbruiken. Ten slotte, omdat meerdere bewerkingen op één verbinding kunnen worden uitgevoerd, kunnen ze dezelfde transactiecontext delen, waardoor het gebruik sp_getbindtoken en sp_bindsession de opgeslagen procedures van het systeem niet meer nodig zijn.

In de volgende consoletoepassing ziet u hoe u twee SqlDataReader objecten gebruikt met drie SqlCommand objecten en één SqlConnection object waarvoor MARS is ingeschakeld. Met het eerste opdrachtobject wordt een lijst opgehaald met leveranciers waarvan de waardering 5 is. Het tweede opdrachtobject gebruikt de leverancier-id van een SqlDataReader om de tweede SqlDataReader te laden met alle producten voor de specifieke leverancier. Elke productrecord wordt door de tweede SqlDataReaderbezocht. Er wordt een berekening uitgevoerd om te bepalen wat de nieuwe OnOrderQty moet zijn. Het derde opdrachtobject wordt vervolgens gebruikt om de ProductVendor tabel bij te werken met de nieuwe waarde. Dit hele proces vindt plaats binnen één transactie, die aan het einde wordt teruggedraaid.

Notitie

In het volgende voorbeeld wordt de voorbeelddatabase AdventureWorks gebruikt die is opgenomen in SQL Server. Bij het verbindingsreeks in de voorbeeldcode wordt ervan uitgegaan dat de database is geïnstalleerd en beschikbaar is op de lokale computer. Wijzig de verbindingsreeks indien nodig voor uw omgeving.

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

Zie ook