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

Scarica ADO.NET

In questo argomento viene fornito un esempio di codice C# che illustra una logica di ripetizione dei tentativi personalizzata. La logica di ripetizione dei tentativi garantisce affidabilità. È progettata per elaborare correttamente gli errori o guasti temporanei che tendono a risolversi se il programma attende qualche secondo ed effettua un nuovo tentativo.

L'origine di un errore temporaneo può essere:

  • Un errore di breve durata della rete che supporta Internet.
  • Il bilanciamento del carico delle risorse di un sistema cloud al momento dell'invio della query.

Le classi ADO.NET per la connessione all'istanza locale di Microsoft SQL Server possono anche connettersi al database SQL di Azure. Tuttavia, le classi ADO.NET non forniscono autonomamente tutta la solidità e l’affidabilità necessarie per l’uso in un ambiente di produzione. Il programma client può incorrere in guasti temporanei da cui deve poter eseguire un ripristino automatico e continuare a funzionare.

Passaggio 1: identificare gli errori temporanei

Il programma deve distinguere gli errori temporanei dagli errori persistenti. Gli errori temporanei sono condizioni di errore che possono scomparire entro un breve periodo di tempo, ad esempio problemi momentanei della rete. Un esempio di errore persistente può essere l'uso da parte del programma di un nome scritto in modo errato per il database di destinazione: in questo caso l'errore "Database non trovato" è persistente e non è possibile cancellarlo entro un breve periodo di tempo.

L'elenco dei numeri di errore classificati come errori temporanei è disponibile nell'elenco dei messaggi di errore per le applicazioni client del database SQL

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. L'esempio di codice C# è costituito da un file denominato 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#.
    • File > Nuovo > Progetto > Installati > Modelli > Visual C# > Windows > Desktop classico > Applicazione console
    • Assegnare al progetto il nome RetryAdo2.
  2. Aprire il riquadro Esplora soluzioni.
    • Visualizzare il nome del progetto.
    • Nel progetto aggiungere una dipendenza NuGet nel pacchetto Microsoft.Data.SqlClient.
    • Visualizzare il nome del file Program.cs.
  3. Aprire il file Program.cs.
  4. Sostituire completamente il contenuto del file Program.cs con il codice nel blocco di codice seguente.
  5. Fare clic sul menu Compilazione > Compila soluzione.

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

Incollare questo codice nel file Program.cs .

È necessario modificare le stringhe per nome del server, password e così via. È possibile trovare queste stringhe nel metodo denominato GetSqlConnectionString.

NOTA: la stringa di connessione per il nome del server è pensata per il database SQL di Azure, perché include il prefisso di quattro caratteri di tcp:. ma è possibile modificare la stringa del server per connettersi a Microsoft 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);
                succeeded = false;
                break;
            }
            catch (TestSqlException sqlExc) {
                if (TransientErrorNumbers.Contains(sqlExc.Number))
                {
                    Console.WriteLine("{0}: transient occurred. (TESTING.)", sqlExc.Number);
                    continue;
                }

                Console.WriteLine(sqlExc);
                succeeded = false;
                break;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                succeeded = false;
                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>
    /// You must edit the four 'my' string values.
    /// </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             = "MyPassword",
            // 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 invia parametri. Per eseguire il file EXE:

  1. Aprire una finestra della console in cui è stato compilato il file binario RetryAdo2.exe.
  2. Eseguire 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

Per testare la logica di ripetizione dei tentativi è possibile simulare un errore temporaneo in diversi modi.

Passaggio 3.a: generare un'eccezione di test

L'esempio di codice include:

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

Se si rimuove il commento dall'istruzione throw e si ricompila, l'esecuzione successiva di RetryAdo2.exe restituisce un output 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 verificare se il codice gestisce correttamente gli errori persistenti, rieseguire il test precedente facendo attenzione a non usare il numero di un errore temporaneo effettivo, come 4060. Usare invece il numero fittizio 7654321. Il programma deve trattarlo come un errore persistente e ignorare qualsiasi ripetizione dei tentativi.

Passaggio 3.c: disconnettersi dalla rete

  1. Disconnettere il computer client dalla rete.
    • In un computer desktop scollegare il cavo di rete.
    • In un computer portatile premere la combinazione di tasti funzione che disattiva la scheda di rete.
  2. Avviare RetryAdo2.exe e attendere che venga visualizzato il primo errore temporaneo, probabilmente il numero 11001.
  3. Riconnettersi alla rete mentre RetryAdo2.exe è ancora in esecuzione.
  4. La console segnalerà l'esito positivo di un tentativo successivo.

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

  1. Aggiungere temporaneamente il numero di errore 40615 a TransientErrorNumberse ricompilare.
  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.
    • Consentire l'esecuzione del programma e tornare al punto di interruzione.
    • Si verifica l'errore 40615.
  4. Correggere l'errore di ortografia.
  5. Consentire l'esecuzione e il completamento del programma.
  6. Rimuovere il numero 40615 e ricompilare.

Passaggi successivi

Per esplorare altre procedure consigliate e linee guida di progettazione, consultare Connessione al database SQL: collegamenti, migliori pratiche e linee guida di progettazione