Compartilhar via


Programação assíncrona

Aplica-se a: .NET Framework .NET .NET Standard

Baixar ADO.NET

Este artigo aborda o suporte à programação assíncrona no Provedor de Dados do Microsoft SqlClient para o SQL Server (SqlClient).

Programação assíncrona herdada

O Provedor de Dados do Microsoft SqlClient para o SQL Server inclui métodos de System.Data.SqlClient para manter a compatibilidade com versões anteriores em aplicativos que migram para o Microsoft.Data.SqlClient. Não é recomendável usar os seguintes métodos herdados de programação assíncrona para um novo desenvolvimento:

Dica

No Provedor de Dados do Microsoft SqlClient para o SQL Server, esses métodos herdados não exigem mais Asynchronous Processing=true na cadeia de conexão.

Aviso

A Asynchronous Processing propriedade de conexão não tem mais suporte no Microsoft SqlClient Data Provider v4.0 em diante.

Recursos de programação assíncrona

Esses recursos de programação assíncrona fornecem uma técnica simples para tornar o código assíncrono.

Para obter mais informações sobre a programação assíncrona no .NET, confira:

Quando a interface do usuário não está respondendo ou o servidor não é dimensionado, é provável que você precise que o seu código seja mais assíncrono. Escrever código assíncrono tradicionalmente envolve instalar um retorno de chamada (também chamado de continuação) para expressar a lógica que ocorre depois que a operação assíncrona é concluída. Esse estilo complica a estrutura do código assíncrono em comparação com o código síncrono.

Você pode chamar métodos assíncronos sem usar retornos de chamada e sem dividir o código em vários métodos ou expressões lambda.

O modificador async especifica que um método é assíncrono. Ao chamar um método async, uma tarefa é retornada. Quando o operador await é aplicado a uma tarefa, o método atual é encerrado imediatamente. Quando a tarefa é concluída, a execução é retomada no mesmo método.

Dica

No Provedor de Dados do Microsoft SqlClient para o SQL Server, as chamadas assíncronas não são necessárias para definir a palavra-chave da cadeia de conexão Context Connection.

Chamar um método async não cria threads extras. Ele pode usar o thread de E/S de conclusão existente brevemente na extremidade.

Os seguintes métodos do Provedor de Dados do Microsoft SqlClient para o SQL Server dão suporte à programação assíncrona:

Outros membros assíncronos dão suporte ao suporte a streaming do SqlClient.

Dica

Os métodos assíncronos não exigem Asynchronous Processing=true na cadeia de conexão. Além disso, essa propriedade é obsoleta no Provedor de Dados do Microsoft SqlClient para o SQL Server.

Conexão síncrona para assíncrona aberta

Você pode atualizar um aplicativo existente para usar o recurso assíncrono. Por exemplo, suponha que um aplicativo tenha um algoritmo de conexão síncrona e bloqueie o thread da interface do usuário toda vez que ele se conectar ao banco de dados. Uma vez conectado, o aplicativo chama um procedimento armazenado que sinaliza aos demais usuários sobre o que acabou de entrar.

using System;
using System.Data;
using Microsoft.Data.SqlClient;

namespace SqlCommandCS
{
    class Program
    {
        static void Main()
        {
            string str = "Data Source=(local);Initial Catalog=Northwind;"
                + "Integrated Security=SSPI";
            string qs = "SELECT OrderID, CustomerID FROM dbo.Orders;";
            CreateCommand(qs, str);
        }
        private static void CreateCommand(string queryString,
            string connectionString)
        {
            using (SqlConnection connection = new SqlConnection(
                       connectionString))
            {
                SqlCommand command = new SqlCommand(queryString, connection);
                command.Connection.Open();
                command.ExecuteNonQuery();
            }
        }
    }
}

Quando convertido para usar a funcionalidade assíncrona, o programa terá a seguinte aparência:

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

class A {
   public static void Main() 
   {
      using (SqlConnection conn = new SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI"))
      {
         SqlCommand command = new SqlCommand("SELECT TOP 2 * FROM dbo.Orders", conn);

         int result = A.Method(conn, command).Result;

         SqlDataReader reader = command.ExecuteReader();
         while (reader.Read())
            Console.WriteLine(reader[0]);
      }
   }

   static async Task<int> Method(SqlConnection conn, SqlCommand cmd) {
      await conn.OpenAsync();
      await cmd.ExecuteNonQueryAsync();
      return 1;
   }
}

Adicionar o recurso assíncrono em um aplicativo existente (combinando padrões antigos e novos)

Também é possível adicionar a funcionalidade assíncrona (SqlConnection::OpenAsync) sem alterar a lógica assíncrona existente. Por exemplo, se um aplicativo no momento usa:

AsyncCallback productList = new AsyncCallback(ProductList);
SqlConnection conn = new SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI");
conn.Open();
SqlCommand cmd = new SqlCommand("select top 2 * from orders", conn);
IAsyncResult ia = cmd.BeginExecuteReader(productList, cmd);

Você pode começar a usar o padrão assíncrono sem alterar substancialmente o algoritmo existente.

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

class A 
{
   static void ProductList(IAsyncResult result) { }

   public static void Main() 
   {
      // AsyncCallback productList = new AsyncCallback(ProductList);
      // SqlConnection conn = new SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI");
      // conn.Open();
      // SqlCommand cmd = new SqlCommand("select top 2 * from orders", conn);
      // IAsyncResult ia = cmd.BeginExecuteReader(productList, cmd);

      AsyncCallback productList = new AsyncCallback(ProductList);
      SqlConnection conn = new SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI");
      conn.OpenAsync().ContinueWith((task) => {
         SqlCommand cmd = new SqlCommand("select top 2 * from orders", conn);
         IAsyncResult ia = cmd.BeginExecuteReader(productList, cmd);
      }, TaskContinuationOptions.OnlyOnRanToCompletion);
   }
}

Usar o modelo de provedor base e o recurso assíncrono

Talvez seja necessário criar uma ferramenta que possa se conectar a diferentes bancos de dados e executar consultas. Use o modelo de provedor base e o recurso assíncrono.

O controlador MSDTC deve ser habilitado no servidor para usar transações distribuídas. Para obter informações sobre como habilitar o MSDTC, confira Como habilitar o MSDTC em um servidor Web.

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

class program
{
   static async Task PerformDBOperationsUsingProviderModel(string connectionString)
   {
      using (DbConnection connection = SqlClientFactory.Instance.CreateConnection())
      {
            connection.ConnectionString = connectionString;
            await connection.OpenAsync();

            DbCommand command = connection.CreateCommand();
            command.CommandText = "SELECT * FROM AUTHORS";

            using (DbDataReader reader = await command.ExecuteReaderAsync())
            {
               while (await reader.ReadAsync())
               {
                  for (int i = 0; i < reader.FieldCount; i++)
                  {
                        // Process each column as appropriate
                        object obj = await reader.GetFieldValueAsync<object>(i);
                        Console.WriteLine(obj);
                  }
               }
            }
      }
   }

   public static void Main()
   {
      SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
      // replace these with your own values
      builder.DataSource = "localhost";
      builder.InitialCatalog = "pubs";
      builder.IntegratedSecurity = true;

      Task task = PerformDBOperationsUsingProviderModel(builder.ConnectionString);
      task.Wait();
   }
}

Usar transações SQL e o novo recurso assíncrono

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

class Program 
{
   static void Main()
   {
      string connectionString =
          "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)";
      Task task = ExecuteSqlTransaction(connectionString);
      task.Wait();
   }

   static async Task ExecuteSqlTransaction(string connectionString)
   {
      using (SqlConnection connection = new SqlConnection(connectionString))
      {
         await connection.OpenAsync();

         SqlCommand command = connection.CreateCommand();
         SqlTransaction transaction = null;

         // Start a local transaction.
         transaction = await Task.Run<SqlTransaction>(
             () => connection.BeginTransaction("SampleTransaction")
             );

         // Must assign both transaction object and connection
         // to Command object for a pending local transaction
         command.Connection = connection;
         command.Transaction = transaction;

         try {
            command.CommandText =
                "Insert into Region (RegionID, RegionDescription) VALUES (555, 'Description')";
            await command.ExecuteNonQueryAsync();

            command.CommandText =
                "Insert into Region (RegionID, RegionDescription) VALUES (556, 'Description')";
            await command.ExecuteNonQueryAsync();

            // Attempt to commit the transaction.
            await Task.Run(() => transaction.Commit());
            Console.WriteLine("Both records are written to database.");
         }
         catch (Exception ex)
         {
            Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
            Console.WriteLine("  Message: {0}", ex.Message);

            // Attempt to roll back the transaction.
            try
            {
               transaction.Rollback();
            }
            catch (Exception ex2)
            {
               // This catch block will handle any errors that may have occurred
               // on the server that would cause the rollback to fail, such as
               // a closed connection.
               Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
               Console.WriteLine("  Message: {0}", ex2.Message);
            }
         }
      }
   }
}

Usar transações distribuídas e o novo recurso assíncrono

Em um aplicativo empresarial, talvez seja necessário adicionar transações distribuídas em alguns cenários, a fim de habilitar transações entre vários servidores de banco de dados. Você pode usar o namespace System.Transactions e inscrever uma transação distribuída, da seguinte maneira:

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

class Program
{
   public static void Main()
   {
      SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
      // replace these with your own values
      // create two tables RegionTable1 and RegionTable2
      // and add a constraint in one of these tables 
      // to avoid duplicate RegionID
      builder.DataSource = "localhost";
      builder.InitialCatalog = "Northwind";
      builder.IntegratedSecurity = true;

      Task task = ExecuteDistributedTransaction(builder.ConnectionString, builder.ConnectionString);
      task.Wait();
   }

   static async Task ExecuteDistributedTransaction(string connectionString1, string connectionString2)
   {
      using (SqlConnection connection1 = new SqlConnection(connectionString1))
      using (SqlConnection connection2 = new SqlConnection(connectionString2))
      {
            using (CommittableTransaction transaction = new CommittableTransaction())
            {
               await connection1.OpenAsync();
               connection1.EnlistTransaction(transaction);

               await connection2.OpenAsync();
               connection2.EnlistTransaction(transaction);

               try
               {
                  SqlCommand command1 = connection1.CreateCommand();
                  command1.CommandText = "Insert into RegionTable1 (RegionID, RegionDescription) VALUES (100, 'Description')";
                  await command1.ExecuteNonQueryAsync();

                  SqlCommand command2 = connection2.CreateCommand();
                  command2.CommandText = "Insert into RegionTable2 (RegionID, RegionDescription) VALUES (100, 'Description')";
                  await command2.ExecuteNonQueryAsync();

                  transaction.Commit();
               }
               catch (Exception ex)
               {
                  Console.WriteLine("Exception Type: {0}", ex.GetType());
                  Console.WriteLine("  Message: {0}", ex.Message);

                  try
                  {
                        transaction.Rollback();
                  }
                  catch (Exception ex2)
                  {
                        Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
                        Console.WriteLine("  Message: {0}", ex2.Message);
                  }
               }
            }
      }
   }
}

Cancelar uma operação assíncrona

Você pode cancelar uma solicitação assíncrona usando o CancellationToken.

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

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

      static async Task CancellingAsynchronousOperations(CancellationToken cancellationToken)
      {
            using (SqlConnection connection = new SqlConnection("Server=(local);Integrated Security=true"))
            {
               await connection.OpenAsync(cancellationToken);

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

Operações assíncronas com SqlBulkCopy

As funcionalidades assíncronas também estão em Microsoft.Data.SqlClient.SqlBulkCopy com SqlBulkCopy.WriteToServerAsync.

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

namespace SqlBulkCopyAsyncCodeSample
{
    class Program
    {
        static string selectStatement = "SELECT * FROM [pubs].[dbo].[titles]";
        static string createDestTableStatement =
            @"CREATE TABLE {0} (
            [title_id] [varchar](6) NOT NULL,
            [title] [varchar](80) NOT NULL,
            [type] [char](12) NOT NULL,
            [pub_id] [char](4) NULL,
            [price] [money] NULL,
            [advance] [money] NULL,
            [royalty] [int] NULL,
            [ytd_sales] [int] NULL,
            [notes] [varchar](200) NULL,
            [pubdate] [datetime] NOT NULL)";

        // Replace the connection string if needed, for instance to connect to SQL Express: @"Server=(local)\SQLEXPRESS;Database=Demo;Integrated Security=true"
        // static string connectionString = @"Server=(localdb)\V11.0;Database=Demo";
        static string connectionString = @"Server=(local);Database=Demo;Integrated Security=true";

        // static string marsConnectionString = @"Server=(localdb)\V11.0;Database=Demo;MultipleActiveResultSets=true;";
        static string marsConnectionString = @"Server=(local);Database=Demo;MultipleActiveResultSets=true;Integrated Security=true";

        // Replace the Server name with your actual sql azure server name and User ID/Password
        static string azureConnectionString = @"Server=SqlAzure;User ID=<myUserID>;Password=<myPassword>;Database=Demo";

        static void Main(string[] args)
        {
            SynchronousSqlBulkCopy();
            AsyncSqlBulkCopy().Wait();
            MixSyncAsyncSqlBulkCopy().Wait();
            AsyncSqlBulkCopyNotifyAfter().Wait();
            AsyncSqlBulkCopyDataRows().Wait();
            AsyncSqlBulkCopySqlServerToSqlAzure().Wait();
            AsyncSqlBulkCopyCancel().Wait();
            AsyncSqlBulkCopyMARS().Wait();
        }

        // 3.1.1 Synchronous bulk copy in .NET 4.5
        private static void SynchronousSqlBulkCopy()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                DataTable dt = new DataTable();
                using (SqlCommand cmd = new SqlCommand(selectStatement, conn))
                {
                    SqlDataAdapter adapter = new SqlDataAdapter(cmd);
                    adapter.Fill(dt);

                    string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
                    cmd.CommandText = string.Format(createDestTableStatement, temptable);
                    cmd.ExecuteNonQuery();

                    using (SqlBulkCopy bcp = new SqlBulkCopy(conn))
                    {
                        bcp.DestinationTableName = temptable;
                        bcp.WriteToServer(dt);
                    }
                }
            }

        }

        // 3.1.2 Asynchronous bulk copy in .NET 4.5
        private static async Task AsyncSqlBulkCopy()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                await conn.OpenAsync();
                DataTable dt = new DataTable();
                using (SqlCommand cmd = new SqlCommand(selectStatement, conn))
                {
                    SqlDataAdapter adapter = new SqlDataAdapter(cmd);
                    adapter.Fill(dt);

                    string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
                    cmd.CommandText = string.Format(createDestTableStatement, temptable);
                    await cmd.ExecuteNonQueryAsync();

                    using (SqlBulkCopy bcp = new SqlBulkCopy(conn))
                    {
                        bcp.DestinationTableName = temptable;
                        await bcp.WriteToServerAsync(dt);
                    }
                }
            }
        }

        // 3.2 Add new Async.NET capabilities in an existing application (Mixing synchronous and asynchronous calls)
        private static async Task MixSyncAsyncSqlBulkCopy()
        {
            using (SqlConnection conn1 = new SqlConnection(connectionString))
            {
                conn1.Open();
                using (SqlCommand cmd = new SqlCommand(selectStatement, conn1))
                {
                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        using (SqlConnection conn2 = new SqlConnection(connectionString))
                        {
                            await conn2.OpenAsync();
                            string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
                            SqlCommand createCmd = new SqlCommand(string.Format(createDestTableStatement, temptable), conn2);
                            await createCmd.ExecuteNonQueryAsync();
                            using (SqlBulkCopy bcp = new SqlBulkCopy(conn2))
                            {
                                bcp.DestinationTableName = temptable;
                                await bcp.WriteToServerAsync(reader);
                            }
                        }
                    }
                }
            }
        }

        // 3.3 Using the NotifyAfter property
        private static async Task AsyncSqlBulkCopyNotifyAfter()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                await conn.OpenAsync();
                DataTable dt = new DataTable();
                using (SqlCommand cmd = new SqlCommand(selectStatement, conn))
                {
                    SqlDataAdapter adapter = new SqlDataAdapter(cmd);
                    adapter.Fill(dt);

                    string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
                    cmd.CommandText = string.Format(createDestTableStatement, temptable);
                    await cmd.ExecuteNonQueryAsync();

                    using (SqlBulkCopy bcp = new SqlBulkCopy(conn))
                    {
                        bcp.DestinationTableName = temptable;
                        bcp.NotifyAfter = 5;
                        bcp.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnSqlRowsCopied);
                        await bcp.WriteToServerAsync(dt);
                    }
                }
            }
        }

        private static void OnSqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
        {
            Console.WriteLine("Copied {0} so far...", e.RowsCopied);
        }

        // 3.4 Using the new SqlBulkCopy Async.NET capabilities with DataRow[]
        private static async Task AsyncSqlBulkCopyDataRows()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                await conn.OpenAsync();
                DataTable dt = new DataTable();
                using (SqlCommand cmd = new SqlCommand(selectStatement, conn))
                {
                    SqlDataAdapter adapter = new SqlDataAdapter(cmd);
                    adapter.Fill(dt);
                    DataRow[] rows = dt.Select();

                    string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
                    cmd.CommandText = string.Format(createDestTableStatement, temptable);
                    await cmd.ExecuteNonQueryAsync();

                    using (SqlBulkCopy bcp = new SqlBulkCopy(conn))
                    {
                        bcp.DestinationTableName = temptable;
                        await bcp.WriteToServerAsync(rows);
                    }
                }
            }
        }

        // 3.5 Copying data from SQL Server to SQL Azure in .NET 4.5
        private static async Task AsyncSqlBulkCopySqlServerToSqlAzure()
        {
            using (SqlConnection srcConn = new SqlConnection(connectionString))
            using (SqlConnection destConn = new SqlConnection(azureConnectionString))
            {
                await srcConn.OpenAsync();
                await destConn.OpenAsync();
                using (SqlCommand srcCmd = new SqlCommand(selectStatement, srcConn))
                {
                    using (SqlDataReader reader = await srcCmd.ExecuteReaderAsync())
                    {
                        string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
                        using (SqlCommand destCmd = new SqlCommand(string.Format(createDestTableStatement, temptable), destConn))
                        {
                            await destCmd.ExecuteNonQueryAsync();
                            using (SqlBulkCopy bcp = new SqlBulkCopy(destConn))
                            {
                                bcp.DestinationTableName = temptable;
                                await bcp.WriteToServerAsync(reader);
                            }
                        }
                    }
                }
            }
        }

        // 3.6 Cancelling an Asynchronous Operation to SQL Azure
        private static async Task AsyncSqlBulkCopyCancel()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            using (SqlConnection srcConn = new SqlConnection(connectionString))
            using (SqlConnection destConn = new SqlConnection(azureConnectionString))
            {
                await srcConn.OpenAsync(cts.Token);
                await destConn.OpenAsync(cts.Token);
                using (SqlCommand srcCmd = new SqlCommand(selectStatement, srcConn))
                {
                    using (SqlDataReader reader = await srcCmd.ExecuteReaderAsync(cts.Token))
                    {
                        string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
                        using (SqlCommand destCmd = new SqlCommand(string.Format(createDestTableStatement, temptable), destConn))
                        {
                            await destCmd.ExecuteNonQueryAsync(cts.Token);
                            using (SqlBulkCopy bcp = new SqlBulkCopy(destConn))
                            {
                                bcp.DestinationTableName = temptable;
                                await bcp.WriteToServerAsync(reader, cts.Token);
                                //Cancel Async SqlBulCopy Operation after 200 ms
                                cts.CancelAfter(200);
                            }
                        }
                    }
                }
            }
        }

        // 3.7 Using Async.Net and MARS
        private static async Task AsyncSqlBulkCopyMARS()
        {
            using (SqlConnection marsConn = new SqlConnection(marsConnectionString))
            {
                await marsConn.OpenAsync();

                SqlCommand titlesCmd = new SqlCommand("SELECT * FROM [pubs].[dbo].[titles]", marsConn);
                SqlCommand authorsCmd = new SqlCommand("SELECT * FROM [pubs].[dbo].[authors]", marsConn);
                //With MARS we can have multiple active results sets on the same connection
                using (SqlDataReader titlesReader = await titlesCmd.ExecuteReaderAsync())
                using (SqlDataReader authorsReader = await authorsCmd.ExecuteReaderAsync())
                {
                    await authorsReader.ReadAsync();

                    string temptable = "[#" + Guid.NewGuid().ToString("N") + "]";
                    using (SqlConnection destConn = new SqlConnection(connectionString))
                    {
                        await destConn.OpenAsync();
                        using (SqlCommand destCmd = new SqlCommand(string.Format(createDestTableStatement, temptable), destConn))
                        {
                            await destCmd.ExecuteNonQueryAsync();
                            using (SqlBulkCopy bcp = new SqlBulkCopy(destConn))
                            {
                                bcp.DestinationTableName = temptable;
                                await bcp.WriteToServerAsync(titlesReader);
                            }
                        }
                    }
                }
            }
        }
    }
}

Usar vários comandos de modo assíncrono com o MARS

O exemplo abre uma conexão com o banco de dados AdventureWorks. Usando um objeto SqlCommand, um SqlDataReader é criado. Conforme o leitor é usado, um segundo SqlDataReader é aberto, usando dados do primeiro SqlDataReader como entrada para a cláusula WHERE para o segundo leitor.

Observação

O exemplo a seguir usa o banco de dados de exemplo AdventureWorks. A cadeia de conexão fornecida no código de exemplo pressupõe que o banco de dados está instalado e disponível no computador local. Modifique a cadeia de conexão conforme necessário para o seu ambiente.

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

class Class1
{
    static void Main()
    {
        Task task = MultipleCommands();
        task.Wait();
    }

    static async Task MultipleCommands()
    {
        // By default, MARS is disabled when connecting to a MARS-enabled.
        // It must be enabled in the connection string.
        string connectionString = GetConnectionString();

        int vendorID;
        SqlDataReader productReader = null;
        string vendorSQL =
            "SELECT BusinessEntityID, Name FROM Purchasing.Vendor";
        string productSQL =
            "SELECT Production.Product.Name FROM Production.Product " +
            "INNER JOIN Purchasing.ProductVendor " +
            "ON Production.Product.ProductID = " +
            "Purchasing.ProductVendor.ProductID " +
            "WHERE Purchasing.ProductVendor.BusinessEntityID = @VendorId";

        using (SqlConnection awConnection =
            new SqlConnection(connectionString))
        {
            SqlCommand vendorCmd = new SqlCommand(vendorSQL, awConnection);
            SqlCommand productCmd =
                new SqlCommand(productSQL, awConnection);

            productCmd.Parameters.Add("@VendorId", SqlDbType.Int);

            await awConnection.OpenAsync();
            using (SqlDataReader vendorReader = await vendorCmd.ExecuteReaderAsync())
            {
                while (await vendorReader.ReadAsync())
                {
                    Console.WriteLine(vendorReader["Name"]);

                    vendorID = (int)vendorReader["BusinessEntityID"];

                    productCmd.Parameters["@VendorId"].Value = vendorID;
                    // The following line of code requires a MARS-enabled connection.
                    productReader = await productCmd.ExecuteReaderAsync();
                    using (productReader)
                    {
                        while (await productReader.ReadAsync())
                        {
                            Console.WriteLine("  " +
                                productReader["Name"].ToString());
                        }
                    }
                }
            }
        }
    }

    private static string GetConnectionString()
    {
        // To avoid storing the connection string in your code, you can retrieve it from a configuration file.
        return "Data Source=(local);Integrated Security=SSPI;Initial Catalog=AdventureWorks;MultipleActiveResultSets=True";
    }
}

Ler e atualizar dados de modo assíncrono com o MARS

O MARS permite que uma conexão seja usada para operações de leitura e de DML (linguagem de manipulação de dados) com mais de uma operação pendente. Esse recurso elimina a necessidade de um aplicativo lidar com erros de conexão ocupada. Além disso, o MARS pode substituir o uso de cursores do servidor, que geralmente consomem mais recursos. Finalmente, como as várias operações podem funcionar em uma única conexão, elas podem compartilhar o mesmo contexto de transação, eliminando a necessidade de usar procedimentos armazenados do sistema sp_getbindtoken e sp_bindsession.

O aplicativo de console a seguir demonstra como usar dois objetos SqlDataReader com três objetos SqlCommand e um objeto SqlConnection com o MARS habilitado. O primeiro objeto de comando recupera uma lista de fornecedores cuja classificação de crédito é 5. O segundo objeto de comando usa a ID do fornecedor especificada de um SqlDataReader para carregar o segundo SqlDataReader com todos os produtos para o fornecedor específico. Cada registro de produto é visitado pelo segundo SqlDataReader. Um cálculo é executado para determinar o que o novo OnOrderQty deve ser. O terceiro objeto de comando é usado para atualizar a tabela ProductVendor com o novo valor. Todo esse processo ocorre em uma transação, que é revertida no final.

Observação

O exemplo a seguir usa o banco de dados de exemplo AdventureWorks. A cadeia de conexão fornecida no código de exemplo pressupõe que o banco de dados está instalado e disponível no computador local. Modifique a cadeia de conexão conforme necessário para o seu ambiente.

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

class Program
{
    static void Main()
    {
        Task task = ReadingAndUpdatingData();
        task.Wait();
    }

    static async Task ReadingAndUpdatingData()
    {
        // By default, MARS is disabled when connecting to a MARS-enabled host.
        // It must be enabled in the connection string.
        string connectionString = GetConnectionString();

        SqlTransaction updateTx = null;
        SqlCommand vendorCmd = null;
        SqlCommand prodVendCmd = null;
        SqlCommand updateCmd = null;

        SqlDataReader prodVendReader = null;

        int vendorID = 0;
        int productID = 0;
        int minOrderQty = 0;
        int maxOrderQty = 0;
        int onOrderQty = 0;
        int recordsUpdated = 0;
        int totalRecordsUpdated = 0;

        string vendorSQL =
            "SELECT BusinessEntityID, Name FROM Purchasing.Vendor " +
            "WHERE CreditRating = 5";
        string prodVendSQL =
            "SELECT ProductID, MaxOrderQty, MinOrderQty, OnOrderQty " +
            "FROM Purchasing.ProductVendor " +
            "WHERE BusinessEntityID = @VendorID";
        string updateSQL =
            "UPDATE Purchasing.ProductVendor " +
            "SET OnOrderQty = @OrderQty " +
            "WHERE ProductID = @ProductID AND BusinessEntityID = @VendorID";

        using (SqlConnection awConnection =
            new SqlConnection(connectionString))
        {
            await awConnection.OpenAsync();
            updateTx = await Task.Run(() => awConnection.BeginTransaction());

            vendorCmd = new SqlCommand(vendorSQL, awConnection);
            vendorCmd.Transaction = updateTx;

            prodVendCmd = new SqlCommand(prodVendSQL, awConnection);
            prodVendCmd.Transaction = updateTx;
            prodVendCmd.Parameters.Add("@VendorId", SqlDbType.Int);

            updateCmd = new SqlCommand(updateSQL, awConnection);
            updateCmd.Transaction = updateTx;
            updateCmd.Parameters.Add("@OrderQty", SqlDbType.Int);
            updateCmd.Parameters.Add("@ProductID", SqlDbType.Int);
            updateCmd.Parameters.Add("@VendorID", SqlDbType.Int);

            using (SqlDataReader vendorReader = await vendorCmd.ExecuteReaderAsync())
            {
                while (await vendorReader.ReadAsync())
                {
                    Console.WriteLine(vendorReader["Name"]);

                    vendorID = (int)vendorReader["BusinessEntityID"];
                    prodVendCmd.Parameters["@VendorID"].Value = vendorID;
                    prodVendReader = await prodVendCmd.ExecuteReaderAsync();

                    using (prodVendReader)
                    {
                        while (await prodVendReader.ReadAsync())
                        {
                            productID = (int)prodVendReader["ProductID"];

                            if (prodVendReader["OnOrderQty"] == DBNull.Value)
                            {
                                minOrderQty = (int)prodVendReader["MinOrderQty"];
                                onOrderQty = minOrderQty;
                            }
                            else
                            {
                                maxOrderQty = (int)prodVendReader["MaxOrderQty"];
                                onOrderQty = (int)(maxOrderQty / 2);
                            }

                            updateCmd.Parameters["@OrderQty"].Value = onOrderQty;
                            updateCmd.Parameters["@ProductID"].Value = productID;
                            updateCmd.Parameters["@VendorID"].Value = vendorID;

                            recordsUpdated = await updateCmd.ExecuteNonQueryAsync();
                            totalRecordsUpdated += recordsUpdated;
                        }
                    }
                }
            }
            Console.WriteLine("Total Records Updated: ", totalRecordsUpdated.ToString());
            await Task.Run(() => updateTx.Rollback());
            Console.WriteLine("Transaction Rolled Back");
        }
    }

    private static string GetConnectionString()
    {
        // To avoid storing the connection string in your code, you can retrieve it from a configuration file.
        return "Data Source=(local);Integrated Security=SSPI;Initial Catalog=AdventureWorks;MultipleActiveResultSets=True";
    }
}

Confira também