Passaggio 4: Connettersi in modo resiliente a SQL con ADO.NET

Scarica ADO.NET

Questo articolo fornisce un esempio di codice C# che dimostra la logica di ritenti implementata manualmente. La logica di retry gestisce errori temporanei, o guasti transitori, che potrebbero risolversi se il programma aspetta e tenta di nuovo l'operazione.

Importante

Per le nuove applicazioni che usano Microsoft.Data.SqlClient 3.0 e versioni successive, è preferibile usare la logica configurabile di ripetizione dei tentativi del driver invece di implementare un ciclo di ripetizione dei tentativi. Inizia con Configura la logica dei ritenti in SqlClient.

L'origine di un errore temporaneo può essere:

  • Un breve guasto della rete che supporta Internet.
  • Un sistema cloud potrebbe stare bilanciando le sue risorse quando arriva la tua richiesta.

Le classi ADO.NET per la connessione a SQL Server possono anche collegarsi a database SQL di Azure. Il tuo programma client può incontrare guasti transitori. Dovrebbe recuperare utilizzando una politica di ritenti limitati appropriata per l'operazione.

Passaggio 1: identificare gli errori temporanei

Il tuo programma deve distinguere tra errori transitori e errori persistenti. Gli errori transitori sono condizioni di errore che possono risolversi in poco tempo, come i problemi di rete transitorie. Un esempio di errore persistente è un errore di ortografia del nome del database di destinazione. In tal caso, l'errore "Nessun database di questo tipo trovato" persiste e non si risolve rapidamente.

L'elenco dei numeri di errore classificati come guasti transitori è disponibile su Messaggi di errore per applicazioni client SQL Database.

Passaggio 2: creare ed eseguire un'applicazione di esempio

In questo esempio si presuppone che sia installato .NET Framework 4.6.2 o versione successiva. Il campione di codice C# consiste in un file chiamato Program.cs. Il relativo codice viene specificato nella sezione successiva.

Passaggio 2.a: acquisire e compilare il codice di esempio

È possibile compilare l'esempio con i passaggi seguenti:

  1. Nell’ edizione gratuita Visual Studio Community, creare un nuovo progetto dal modello di applicazione Console C#.

    • Seleziona File>Nuovo>Progetto>Modelli installati>Modelli>Visual C#>Windows>Desktop classico>Applicazione console.

    • Assegnare al progetto il nome RetryAdo2.

  2. Aprire il riquadro Esplora soluzioni.

    • Vedere il nome del tuo progetto.
    • Nel tuo progetto, aggiungere una dipendenza NuGet al pacchetto Microsoft.Data.SqlClient.
    • Visualizza il nome del Program.cs file.
  3. Aprire il Program.cs file.

  4. Sostituisci l'intero contenuto del Program.cs file con il codice nel blocco di codice successivo.

  5. Seleziona il menu Build>Build Solution.

Passaggio 2.b: copiare e incollare il codice di esempio

Incolla questo codice nel tuo Program.cs file.

Poi, modifica le stringhe per il nome del server e la password. Puoi trovare queste stringhe nel metodo chiamato GetSqlConnectionString.

Nota

La stringa di connessione è orientata a database SQL di Azure, perché include il prefisso tcp:di quattro caratteri . Ma puoi regolare la stringa del server per connetterla alla tua istanza di 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; }
}

Passaggio 2.c: eseguire il programma

L'eseguibile RetryAdo2.exe non prende parametri. Per eseguire il .exe:

  1. Apri una finestra della console nella cartella dove hai compilato il RetryAdo2.exe binario.
  2. Esegui RetryAdo2.exe senza parametri di input.
database_firewall_rules_table   245575913
filestream_tombstone_2073058421 2073058421
filetable_updates_2105058535    2105058535

Passaggio 3: come testare la logica di ripetizione dei tentativi

Puoi simulare un errore transitorio per verificare la logica di nuovo tentativo in diversi modi.

Passaggio 3.a: generare un'eccezione di test

L'esempio di codice include:

  • Una piccola seconda classe chiamata TestSqlException con una proprietà chiamata Number.
  • //throw new TestSqlException(4060); , da cui è possibile rimuovere il commento.

Se elimini il commento dell'istruzione throw e ricompili, la sequenza RetryAdo2.exe successiva produce qualcosa di simile al seguente.

[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\]
>>

Passaggio 3.b: eseguire nuovamente il test con un errore persistente

Per dimostrare che il codice gestisce correttamente gli errori persistenti, si riesegue il test precedente. Invece di usare un vero numero di errore transitorio come 4060, usa il numero fittizio 7654321. Il programma considera questo numero come un errore persistente e bypassa qualsiasi ritentativo.

Passaggio 3.c: disconnettersi dalla rete

  1. Disconnettere il computer client dalla rete.
    • In un computer desktop scollegare il cavo di rete.
    • Per un computer portatile, premere la combinazione di tasti funzione per disattivare la scheda di rete.
  2. Avvia RetryAdo2.exe, e aspetta che la console mostri il primo errore transitorio, probabilmente 11001.
  3. Riconnettiti alla rete mentre RetryAdo2.exe continua a funzionare.
  4. La console segnalerà l'esito positivo di un tentativo successivo.

Passaggio 3.d: Specificare temporaneamente il nome del server in modo errato

  1. Aggiungi temporaneamente 40615 come un altro numero di errore a TransientErrorNumbers, e ricompila.
  2. Impostare un punto di interruzione sulla riga new QC.SqlConnectionStringBuilder().
  3. Usare la funzionalità Modifica e continuazione per specificare intenzionalmente in modo errato il nome del server un paio di righe più in basso.
    • Lascia che il programma venga eseguito e ritorna al tuo punto di interruzione.
    • Si verifica l'errore 40615.
  4. Correggere l'errore di ortografia.
  5. Lasciare che il programma si esegua e si completi con successo.
  6. Rimuovere il numero 40615 e ricompilare.

Passo successivo