Etapa 4: Conectar-se de forma resiliente ao SQL com ADO.NET

Baixar ADO.NET

Este artigo apresenta um exemplo de código em C# que demonstra uma lógica de repetição implementada manualmente. A lógica de repetição lida com erros temporários, ou falhas transitórias, que podem ser resolvidos se o programa aguardar e tentar realizar a operação novamente.

Importante

Para novos aplicativos que usam o Microsoft.Data.SqlClient 3.0 e versões posteriores, prefira a lógica de repetição configurável do driver em vez de implementar um loop de repetição. Comece com Configurar a lógica de nova tentativa no SqlClient.

As fontes de falhas transitórias incluem:

  • Uma breve falha da rede que suporta a Internet.
  • Um sistema em nuvem pode estar balanceando seus recursos quando sua consulta chega.

As classes ADO.NET para conexão ao SQL Server também podem se conectar ao Banco de Dados SQL do Azure. Seu programa cliente pode enfrentar falhas transitórias. A recuperação deve ocorrer utilizando uma política de nova tentativa limitada, adequada à operação.

Etapa 1: Identificar erros transitórios

Seu programa deve distinguir entre erros transitórios e erros persistentes. Erros transitórios são condições de erro que podem se resolver em pouco tempo, como problemas de rede transitória. Um exemplo de erro persistente é uma grafia errada no nome do banco de dados de destino. Nesse caso, o erro "Nenhum banco de dados encontrado desse tipo" persiste e não desaparece rapidamente.

A lista de números de erro categorizados como falhas transitórias está disponível em Mensagens de erro para aplicações cliente de banco de dados SQL.

Etapa 2: Criar e executar o aplicativo de exemplo

Este exemplo pressupõe que o .NET Framework 4.6.2 ou posterior esteja instalado. O exemplo de código C# consiste em um arquivo chamado Program.cs. Seu código é fornecido na próxima seção.

Etapa 2.a: Capturar e compilar o exemplo de código

Você pode compilar o exemplo com as seguintes etapas:

  1. Na edição gratuita do Visual Studio Community, crie um novo projeto no modelo de Aplicativo do Console do C#.

    • Selecione Arquivo>Novo>>>Modelos>Visual C#>Windows>Área de Trabalho Clássica>Aplicativo de Console.

    • Dê ao projeto o nome de RetryAdo2.

  2. Abra o painel do Gerenciador de Soluções.

  3. Abra o arquivo Program.cs .

  4. Substitua todo o conteúdo do Program.cs arquivo pelo código do bloco de código a seguir.

  5. Selecione o menu Build>Build Solution.

Etapa 2.b: Copiar e colar código de exemplo

Cole esse código no seu Program.cs arquivo.

Depois, edite as strings do nome do servidor e senha. Você pode encontrar essas cadeias no método chamado GetSqlConnectionString.

Observação

A cadeia de conexão é voltada para o Banco de Dados SQL do Azure, pois inclui o prefixo tcp:de quatro caracteres . Mas você pode ajustar a string do servidor para conectar à sua instância do SQL Server.

using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Threading;

namespace RetryAdo2;

public class Program
{
    public static int Main(string[] args)
    {
        bool succeeded = false;
        const int totalNumberOfTimesToTry = 4;
        int retryIntervalSeconds    = 10;

        for (int tries = 1; tries <= totalNumberOfTimesToTry; tries++)
        {
            try
            {
                if (tries > 1)
                {
                    Console.WriteLine(
                        "Transient error encountered. Will begin attempt number {0} of {1} max...",
                        tries,
                        totalNumberOfTimesToTry
                    );
                    Thread.Sleep(1000 * retryIntervalSeconds);
                    retryIntervalSeconds = Convert.ToInt32(retryIntervalSeconds * 1.5);
                }
                AccessDatabase();
                succeeded = true;
                break;
            }
            catch (SqlException sqlExc) {
                if (TransientErrorNumbers.Contains(sqlExc.Number))
                {
                    Console.WriteLine("{0}: transient occurred.", sqlExc.Number);
                    continue;
                }

                Console.WriteLine(sqlExc);
                break;
            }
            catch (TestSqlException sqlExc) {
                if (TransientErrorNumbers.Contains(sqlExc.Number))
                {
                    Console.WriteLine("{0}: transient occurred. (TESTING.)", sqlExc.Number);
                    continue;
                }

                Console.WriteLine(sqlExc);
                break;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                break;
            }
        }

        if (!succeeded) {
            Console.WriteLine("ERROR: Unable to access the database!");
            return 1;
        }

        return 0;
    }

    /// <summary>
    /// Connects to the database, reads,
    /// prints results to the console.
    /// </summary>
    static void AccessDatabase() {
        //throw new TestSqlException(4060); //(7654321);  // Uncomment for testing.

        using var sqlConnection = new SqlConnection(GetSqlConnectionString());

        using var dbCommand = sqlConnection.CreateCommand();

        dbCommand.CommandText = @"
        SELECT TOP 3
            ob.name,
            CAST(ob.object_id as NVARCHAR(32)) AS [object_id]
        FROM sys.objects AS ob
        WHERE ob.type='IT'
        ORDER BY ob.name;";
        
        sqlConnection.Open();
        var dataReader = dbCommand.ExecuteReader();

        while (dataReader.Read())
        {
            Console.WriteLine(
                "{0}\t{1}",
                dataReader.GetString(0),
                dataReader.GetString(1)
            );
        }
    }

    /// <summary>
    /// Edit the four string values in accordance with your environment.
    /// </summary>
    /// <returns>An ADO.NET connection string.</returns>
    static private string GetSqlConnectionString()
    {
        // Prepare the connection string to Azure SQL Database.
        var sqlConnectionSB = new SqlConnectionStringBuilder
        {
            // Change these values to your values.
            DataSource           = "tcp:myazuresqldbserver.database.windows.net,1433", //["Server"]
            InitialCatalog       = "MyDatabase",                                       //["Database"]
            UserID               = "MyLogin",                                          // "@yourservername"  as suffix sometimes.
            Password             = "<password>",
            // Adjust these values if you like. (ADO.NET 4.5.1 or later.)
            ConnectRetryCount    = 3,
            ConnectRetryInterval = 10, // Seconds.
            // Leave these values as they are.
            IntegratedSecurity = false,
            Encrypt            = true,
            ConnectTimeout     = 30
        };

        return sqlConnectionSB.ToString();
    }

    static List<int> TransientErrorNumbers = new()
    {
        4060, 40197, 40501, 40613, 49918, 49919, 49920, 11001
    };
}

/// <summary>
/// For testing retry logic, you can have method
/// AccessDatabase start by throwing a new
/// TestSqlException with a Number that does
/// or does not match a transient error number
/// present in TransientErrorNumbers.
/// </summary>
internal class TestSqlException : ApplicationException
{
    internal TestSqlException(int testErrorNumber)
    {
        Number = testErrorNumber;
    }

    internal int Number { get; set; }
}

Etapa 2.c: Execute o programa

O RetryAdo2.exe executável não recebe parâmetros. Para executar o .exe:

  1. Abra uma janela de console na pasta onde você compilou o RetryAdo2.exe binário.
  2. Execute RetryAdo2.exe sem parâmetros de entrada.
database_firewall_rules_table   245575913
filestream_tombstone_2073058421 2073058421
filetable_updates_2105058535    2105058535

Etapa 3: Formas de testar sua lógica de nova tentativa

Você pode simular um erro transitório para testar sua lógica de nova tentativa de várias maneiras.

Etapa 3.a: Gerar uma exceção de teste

O exemplo de código inclui:

  • Uma pequena segunda classe nomeada TestSqlException com uma propriedade chamada Number.
  • //throw new TestSqlException(4060); , que você pode remover o comentário.

Se você descomentar a instrução throw e recompilar, a próxima execução de RetryAdo2.exe produzirá algo semelhante ao seguinte.

[C:\VS15\RetryAdo2\RetryAdo2\bin\Debug\]
>> RetryAdo2.exe
4060: transient occurred. (TESTING.)
Transient error encountered. Will begin attempt number 2 of 4 max...
4060: transient occurred. (TESTING.)
Transient error encountered. Will begin attempt number 3 of 4 max...
4060: transient occurred. (TESTING.)
Transient error encountered. Will begin attempt number 4 of 4 max...
4060: transient occurred. (TESTING.)
ERROR: Unable to access the database!

[C:\VS15\RetryAdo2\RetryAdo2\bin\Debug\]
>>

Etapa 3.b: Teste novamente com um erro persistente

Para provar que o código lida corretamente com erros persistentes, execute novamente o teste anterior. Em vez de usar um número de erro transitório real como 4060, use o número fictício 7654321. O programa trata esse número como um erro persistente e ignora qualquer retentativa.

Etapa 3.c: Desconectar da rede

  1. Desconecte o computador cliente da rede.
    • Em um computador desktop, desconecte o cabo de rede.
    • Para um laptop, pressione a combinação de função das teclas para desativar o adaptador de rede.
  2. Inicie RetryAdo2.exee espere o console exibir o primeiro erro transitório, provavelmente 11001.
  3. Reconecte-se à rede, enquanto RetryAdo2.exe continua funcionando.
  4. Observe o console relatar o sucesso em uma nova tentativa subsequente.

Etapa 3.d: temporariamente digitar incorretamente o nome do servidor

  1. Adicione temporariamente 40615 como outro número de erro em TransientErrorNumbers, e recompile.
  2. Defina um ponto de interrupção na linha: new QC.SqlConnectionStringBuilder().
  3. Use o recurso Editar e Continuar para propositalmente digitar o nome do servidor de maneira incorreta algumas linhas abaixo.
    • Deixe o programa continuar em execução até voltar ao ponto de interrupção.
    • O erro 40615 ocorre.
  4. Corrija o erro de ortografia.
  5. Deixe o programa ser executado e concluir com êxito.
  6. Remova o 40615 e recompile.

Próxima etapa