Connessione resilienza e logica di ripetizione dei tentativi

Nota

Solo EF6 e versioni successive: funzionalità, API e altri argomenti discussi in questa pagina sono stati introdotti in Entity Framework 6. Se si usa una versione precedente, le informazioni qui riportate, o parte di esse, non sono applicabili.

Le applicazioni che si connettono a un server di database sono sempre state vulnerabili alle interruzioni di connessione a causa di errori back-end e instabilità della rete. Tuttavia, in un ambiente basato su LAN che opera su server di database dedicati questi errori sono abbastanza rari che la logica aggiuntiva per gestire tali errori non è spesso necessaria. Con l'aumento dei server di database basati sul cloud, ad esempio Windows database SQL di Azure e connessioni su reti meno affidabili, è ora più comune che si verifichino interruzioni di connessione. Ciò potrebbe essere dovuto a tecniche difensive usate dai database cloud per garantire l'equità del servizio, ad esempio la limitazione delle connessioni o l'instabilità nella rete che causa timeout intermittenti e altri errori temporanei.

Connessione resilienza si riferisce alla possibilità per Entity Framework di ritentare automaticamente tutti i comandi che hanno esito negativo a causa di queste interruzioni di connessione.

Strategie di esecuzione

Connessione la ripetizione dei tentativi viene presa in considerazione da un'implementazione dell'interfaccia IDbExecutionStrategy. Le implementazioni di IDbExecutionStrategy saranno responsabili dell'accettazione di un'operazione e, se si verifica un'eccezione, determinare se un nuovo tentativo è appropriato e riprovare. Esistono quattro strategie di esecuzione fornite con Entity Framework:

  1. DefaultExecutionStrategy: questa strategia di esecuzione non ritenta alcuna operazione, è l'impostazione predefinita per i database diversi da sql server.
  2. DefaultSqlExecutionStrategy: si tratta di una strategia di esecuzione interna usata per impostazione predefinita. Questa strategia non riprova, tuttavia, eseguirà il wrapping di eventuali eccezioni che potrebbero essere temporanee per informare gli utenti che potrebbero voler abilitare la resilienza della connessione.
  3. DbExecutionStrategy: questa classe è adatta come classe di base per altre strategie di esecuzione, incluse quelle personalizzate. Implementa un criterio di ripetizione esponenziale dei tentativi, in cui il nuovo tentativo iniziale si verifica con zero ritardo e il ritardo aumenta in modo esponenziale fino a quando non viene raggiunto il numero massimo di tentativi. Questa classe ha un metodo ShouldRetryOn astratto che può essere implementato nelle strategie di esecuzione derivate per controllare quali eccezioni devono essere ritentate.
  4. SqlAzureExecutionStrategy: questa strategia di esecuzione eredita da DbExecutionStrategy e ritenta le eccezioni che potrebbero essere temporanee quando si lavora con database SQL di Azure.

Nota

Le strategie di esecuzione 2 e 4 sono incluse nel provider Sql Server fornito con ENTITY Framework, che si trova nell'assembly EntityFramework.SqlServer e sono progettate per l'uso con SQL Server.

Abilitazione di una strategia di esecuzione

Il modo più semplice per indicare a EF di usare una strategia di esecuzione consiste nel metodo SetExecutionStrategy della classe DbConfiguration :

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
    }
}

Questo codice indica a EF di usare SqlAzureExecutionStrategy durante la connessione a SQL Server.

Configurazione della strategia di esecuzione

Il costruttore di SqlAzureExecutionStrategy può accettare due parametri, MaxRetryCount e MaxDelay. MaxRetry count è il numero massimo di tentativi di ripetizione della strategia. MaxDelay è un timeSpan che rappresenta il ritardo massimo tra i tentativi che verrà usata dalla strategia di esecuzione.

Per impostare il numero massimo di tentativi su 1 e il ritardo massimo su 30 secondi, eseguire quanto segue:

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetExecutionStrategy(
            "System.Data.SqlClient",
            () => new SqlAzureExecutionStrategy(1, TimeSpan.FromSeconds(30)));
    }
}

SqlAzureExecutionStrategy ritenta immediatamente la prima volta che si verifica un errore temporaneo, ma ritarderà più tempo tra ogni tentativo finché non viene superato il limite massimo di tentativi o il tempo totale raggiunge il ritardo massimo.

Le strategie di esecuzione ritentano solo un numero limitato di eccezioni che sono in genere temporanee, sarà comunque necessario gestire altri errori, nonché intercettare l'eccezione RetryLimitExceeded per il caso in cui un errore non sia temporaneo o richieda troppo tempo per risolverlo.

Esistono alcune limitazioni note quando si usa una strategia di ripetizione dell'esecuzione:

Le query di streaming non sono supportate

Per impostazione predefinita, EF6 e versione successiva memorizzano nel buffer i risultati delle query anziché trasmetterli. Per ottenere risultati trasmessi, è possibile usare il metodo AsStreaming per modificare una query LINQ to Entities in streaming.

using (var db = new BloggingContext())
{
    var query = (from b in db.Blogs
                orderby b.Url
                select b).AsStreaming();
    }
}

Lo streaming non è supportato quando viene registrata una strategia di ripetizione dell'esecuzione. Questa limitazione esiste perché la connessione potrebbe eliminare parte dei risultati restituiti. In questo caso, Entity Framework deve eseguire nuovamente l'intera query, ma non ha un modo affidabile di sapere quali risultati sono già stati restituiti (i dati potrebbero essere stati modificati dopo l'invio della query iniziale, i risultati potrebbero tornare in un ordine diverso, i risultati potrebbero non avere un identificatore univoco e così via).

Le transazioni avviate dall'utente non sono supportate

Dopo aver configurato una strategia di esecuzione che comporta nuovi tentativi, esistono alcune limitazioni relative all'uso delle transazioni.

Per impostazione predefinita, Entity Framework eseguirà tutti gli aggiornamenti del database all'interno di una transazione. Non è necessario eseguire alcuna operazione per abilitare questa operazione, EF esegue sempre questa operazione automaticamente.

Ad esempio, nel codice seguente SaveChanges viene eseguito automaticamente all'interno di una transazione. Se SaveChanges ha esito negativo dopo l'inserimento di uno dei nuovi siti, verrà eseguito il rollback della transazione e non verranno applicate modifiche al database. Il contesto viene lasciato anche in uno stato che consente di chiamare nuovamente SaveChanges per riprovare ad applicare le modifiche.

using (var db = new BloggingContext())
{
    db.Blogs.Add(new Site { Url = "http://msdn.com/data/ef" });
    db.Blogs.Add(new Site { Url = "http://blogs.msdn.com/adonet" });
    db.SaveChanges();
}

Quando non si usa una strategia di ripetizione dei tentativi di esecuzione, è possibile eseguire il wrapping di più operazioni in una singola transazione. Ad esempio, il codice seguente esegue il wrapping di due chiamate SaveChanges in una singola transazione. Se una qualsiasi parte di un'operazione ha esito negativo, non viene applicata alcuna modifica.

using (var db = new BloggingContext())
{
    using (var trn = db.Database.BeginTransaction())
    {
        db.Blogs.Add(new Site { Url = "http://msdn.com/data/ef" });
        db.Blogs.Add(new Site { Url = "http://blogs.msdn.com/adonet" });
        db.SaveChanges();

        db.Blogs.Add(new Site { Url = "http://twitter.com/efmagicunicorns" });
        db.SaveChanges();

        trn.Commit();
    }
}

Ciò non è supportato quando si usa una strategia di ripetizione dell'esecuzione perché EF non è a conoscenza delle operazioni precedenti e come riprovare. Ad esempio, se il secondo SaveChanges non è riuscito, EF non dispone più delle informazioni necessarie per ritentare la prima chiamata a SaveChanges.

Soluzione: Chiamare manualmente la strategia di esecuzione

La soluzione consiste nell'usare manualmente la strategia di esecuzione e assegnargli l'intero set di logica da eseguire, in modo che possa ritentare tutto se una delle operazioni ha esito negativo. Quando viene eseguita una strategia di esecuzione derivata da DbExecutionStrategy, sospende la strategia di esecuzione implicita usata in SaveChanges.

Si noti che tutti i contesti devono essere costruiti all'interno del blocco di codice da ritentare. In questo modo si garantisce che si inizi con uno stato pulito per ogni tentativo.

var executionStrategy = new SqlAzureExecutionStrategy();

executionStrategy.Execute(
    () =>
    {
        using (var db = new BloggingContext())
        {
            using (var trn = db.Database.BeginTransaction())
            {
                db.Blogs.Add(new Blog { Url = "http://msdn.com/data/ef" });
                db.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
                db.SaveChanges();

                db.Blogs.Add(new Blog { Url = "http://twitter.com/efmagicunicorns" });
                db.SaveChanges();

                trn.Commit();
            }
        }
    });