Asynkron programmering

I den här artikeln beskrivs asynkron programmering med Microsoft.Data.SqlClient. Installera NuGet-paketet och importera Microsoft.Data.SqlClient namnområdet för SQL-exemplen. Microsoft. Data.SqlClient stöder uppgiftsbaserade API:er för .NET Framework och moderna .NET.

Äldre asynkron programmering

Före .NET Framework 4.5 gjordes asynkron programmering med System.Data.SqlClient med följande metoder och Asynchronous Processing=trueanslutningsegenskapen:

Den här funktionen finns kvar i SqlClient i .NET Framework 4.5.

Tips

Microsoft.Data.SqlClient tillhandahåller även BeginExecuteNonQuery, BeginExecuteReader och BeginExecuteXmlReader av kompatibilitetsskäl. Ta bort Asynchronous Processing från anslutningssträngar när du migrerar till Microsoft.Data.SqlClient. Med antingen true eller false orsakar nyckelordet ArgumentException med meddelandet Keyword not supported: 'asynchronous processing'. när du skapar SqlConnection eller tilldelar dess ConnectionString egenskap innan du anropar Open eller OpenAsync. Nyckelordet ignoreras inte och krävs inte för asynkrona anrop.

Asynkrona programmeringsfunktioner har lagts till i .NET Framework 4.5

Den asynkrona programmeringsfunktionen ger en enkel teknik för att göra koden asynkron. Mer information om den asynkrona programmeringsfunktionen som introducerades i .NET Framework 4.5 finns i:

När användargränssnittet inte svarar eller om servern inte skalas är det troligt att du behöver koden för att vara mer asynkron. Att skriva asynkron kod har traditionellt inneburet att installera ett återanrop (kallas även fortsättning) för att uttrycka logiken som inträffar när den asynkrona åtgärden har slutförts. Detta komplicerar strukturen för asynkron kod jämfört med synkron kod.

Nu kan du anropa till asynkrona metoder utan att använda motringningar och utan att dela upp koden mellan flera metoder eller lambda-uttryck.

Modifieraren async anger att en metod är asynkron. När du anropar en async metod returneras en uppgift. När operatorn await tillämpas på en aktivitet avslutas den aktuella metoden omedelbart. När aktiviteten är klar återupptas körningen med samma metod.

Kommentar

Microsoft.Data.SqlClient stöder Context Connection=true inte. När du skapar SqlConnection eller tilldelar dess ConnectionString egenskap med den inställningen, genererar drivrutinen InvalidOperationException med meddelandet Connecting to the context connection using Microsoft.Data.SqlClient is not supported. Undantaget inträffar innan du anropar Open eller OpenAsync. Inställningen ignoreras inte.

Om du anropar en async metod allokeras inga ytterligare trådar. Den kan använda den befintliga I/O-slutförandetråden en kort stund i slutet.

Följande basklass och Microsoft. Data.SqlClient-metoder stöder asynkron programmering:

Andra asynkrona medlemmar har lagts till för att stödja SqlClient Streaming Support.

Tips

Uppgiftsbaserade asynkrona metoder kräver inte ett alternativ för anslutningssträng för att aktivera asynkrona operationer.

Synkron till asynkron anslutning öppen

Du kan uppgradera ett äldre program för att använda den nyare asynkrona funktionen. Anta till exempel att ett program har en synkron anslutningsalgoritm och blockerar användargränssnittstråden varje gång det ansluter till databasen, och när det är anslutet anropar programmet en lagrad procedur som signalerar andra användare av den som just loggade in.

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

När programmet konverteras för att använda de nyare asynkrona funktionerna skulle det se ut så här:

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

Lägg till den nya asynkrona funktionen i en befintlig app (blanda gamla och nya mönster)

Det går också att lägga till nya asynkrona funktioner (SqlConnection::OpenAsync) utan att ändra den befintliga asynkrona logiken. Om ett program till exempel för närvarande använder:

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

Du kan börja använda det nya asynkrona mönstret utan att väsentligen ändra den befintliga algoritmen.

Följande mall utelämnar avsiktligt innehållet i callback-funktionen ProductList. Anropa EndExecuteReader, bearbeta och frigör läsaren i ditt återanrop, och frigör kommandot och anslutningen. Håll programmet vid liv tills återanropet har slutförts. Mallen är inte ett fristående konsolprogram.

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

Använd basprovidermodellen och den nya asynkrona funktionen

Du kan behöva skapa ett verktyg som kan ansluta till olika databaser och köra frågor. Du kan använda basprovidermodellen och den nya asynkrona funktionen.

Microsoft Distributed Transaction Controller (MSDTC) måste vara aktiverat på servern för att använda distribuerade transaktioner. Information om hur du aktiverar MSDTC finns i Aktivera MSDTC på en webbserver.

Innan du kör fabriksexemplet registrerar du Microsoft.Data.SqlClient enligt beskrivningen i Hämta en DbProviderFactory. Använd applikationskonfiguration på .NET Framework eller DbProviderFactories.RegisterFactory på moderna .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();
   }
}

Viktigt!

Microsoft rekommenderar att du använder det säkraste tillgängliga autentiseringsflödet. Om du ansluter till Azure SQL är hanterade identiteter för Azure-resurser den rekommenderade autentiseringsmetoden.

Använda SQL-transaktioner och den nya asynkrona funktionen

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

Använda SQL-transaktioner och den nya asynkrona funktionen

I ett företagsprogram kan du behöva lägga till distribuerade transaktioner i vissa scenarier för att aktivera transaktioner mellan flera databasservrar. Du kan använda System.Transactions namnområdet och registrera en distribuerad transaktion enligt följande:

I modern .NET kräver distribuerade transaktioner Windows och .NET 7 och senare versioner. Konfigurera Microsoft Distributed Transaction Coordinator (MSDTC) nätverksåtkomst på de deltagande datorerna. Innan du kör följande exempel på moderna .NET aktiverar du transaction promotion vid programstart innan du skapar några transaktioner:

TransactionManager.ImplicitDistributedTransactions = true;

Egenskapen ImplicitDistributedTransactions är falsesom standard . Utan detta val utlöser befordran till en distribuerad transaktion NotSupportedException. På .NET Framework utelämna den här tilldelningen eftersom egenskapen inte är tillgänglig. MSDTC-konfiguration krävs fortfarande.

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

Viktigt!

Microsoft rekommenderar att du använder det säkraste tillgängliga autentiseringsflödet. Om du ansluter till Azure SQL är hanterade identiteter för Azure-resurser den rekommenderade autentiseringsmetoden.

Avbryt en asynkron åtgärd

Du kan avbryta en asynkron begäran med hjälp av 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);
         }
      }
   }
}

Asynkrona åtgärder med SqlBulkCopy

Använd SqlBulkCopy.WriteToServerAsync för asynkron masskopiering med Microsoft.Data.SqlClient.SqlBulkCopy.

Använd separata anslutningssträngar för SqlClient och ODBC. Ersätt <server> med din SQL Server-instans och installera ODBC Driver 18 för SQL Server för exemplet med mixad provider. På moderna .NET lägger du även till en referens till paketet System.Data.Odbc. SqlClient-anslutningssträngen möjliggör flera aktiva resultatuppsättningar (MARS) för det sista exemplet.

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

Asynkront använda flera kommandon med MARS

Exemplet öppnar en enda anslutning till AdventureWorks databasen. Med hjälp av ett SqlCommand objekt skapas en SqlDataReader . När läsaren används, öppnas en andra SqlDataReader, och data från den första SqlDataReader används som indata till WHERE-villkoret för den andra läsaren.

Kommentar

I följande exempel används exempeldatabasen AdventureWorks som ingår i SQL Server. Den anslutningssträng som anges i exempelkoden förutsätter att databasen är installerad och tillgänglig på den lokala datorn. Ändra anslutningssträng efter behov för din miljö.

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

Asynkront läsa och uppdatera data med MARS

MARS tillåter att en anslutning används för både läsåtgärder och DML-åtgärder (datamanipuleringsspråk) med mer än en väntande åtgärd. Den här funktionen eliminerar behovet för ett program att hantera fel av typen "anslutningsupptagen". Dessutom kan MARS ersätta användaren av markörer på serversidan, vilket i allmänhet förbrukar mer resurser. Slutligen, eftersom flera åtgärder kan köras på en enda anslutning, kan de dela samma transaktionskontext, vilket eliminerar behovet av att använda sp_getbindtoken och sp_bindsession system lagrade procedurer.

Följande konsolprogram visar hur du använder två SqlDataReader objekt med tre SqlCommand objekt och ett enda SqlConnection objekt med MARS aktiverat. Det första kommandoobjektet hämtar en lista över leverantörer vars kreditvärdighet är 5. Det andra kommandoobjektet använder det leverantörs-ID som tillhandahålls från en SqlDataReader för att ladda det andra SqlDataReader med alla produkter hos den specifika leverantören. Varje produktpost granskas av den andra SqlDataReader. En beräkning utförs för att avgöra vad det nya OnOrderQty ska vara. Det tredje kommandoobjektet används sedan för att uppdatera ProductVendor tabellen med det nya värdet. Hela den här processen sker inom en enda transaktion, som återställs i slutet.

Kommentar

I följande exempel används exempeldatabasen AdventureWorks som ingår i SQL Server. Den anslutningssträng som anges i exempelkoden förutsätter att databasen är installerad och tillgänglig på den lokala datorn. Ändra anslutningssträng efter behov för din miljö.

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

Se även