Résilience des connexions et logique de nouvelle tentative

Remarque

EF6 et versions ultérieures uniquement : Les fonctionnalités, les API, etc. décrites dans cette page ont été introduites dans Entity Framework 6. Si vous utilisez une version antérieure, certaines ou toutes les informations ne s’appliquent pas.

Les applications qui se connectent à un serveur de base de données ont toujours été vulnérables aux interruptions de connexion en raison de défaillances de back-end et d’instabilité du réseau. Toutefois, dans un environnement basé sur un réseau local travaillant sur des serveurs de base de données dédiés, ces erreurs sont suffisamment rares pour que la logique supplémentaire afin de gérer ces défaillances ne soit pas souvent nécessaire. Avec l’essor des serveurs de base de données cloud tels que Windows Azure SQL Database et les connexions sur des réseaux moins fiables, il est désormais plus courant que des interruptions de connexion se produisent. Cela peut être dû à des techniques défensives que les bases de données cloud utilisent pour garantir l’impartialité du service, comme la limitation de connexion ou l’instabilité dans le réseau provoquant des délais d’attente intermittents et d’autres erreurs temporaires.

La résilience des connexions fait référence à la possibilité pour EF de réessayer automatiquement toutes les commandes qui échouent en raison de ces interruptions de connexion.

Stratégies d’exécution

La nouvelle tentative de connexion est prise en charge par une implémentation de l’interface IDbExecutionStrategy. Les implémentations de IDbExecutionStrategy seront responsables de l’acceptation d’une opération et, si une exception se produit, détermineront si une nouvelle tentative est appropriée pour la réessayer si c’est le cas. Il existent quatre stratégies d’exécution qui sont fournies avec EF :

  1. DefaultExecutionStrategy : cette stratégie d’exécution ne réessaye aucune opération. Il s’agit de la valeur par défaut pour les bases de données autres que SQL Server.
  2. DefaultSqlExecutionStrategy : il s’agit d’une stratégie d’exécution interne utilisée par défaut. Cette stratégie n’exécute aucune nouvelle tentative, cependant, elle enveloppe toutes les exceptions qui peuvent être temporaires afin de conseiller aux utilisateurs d’activer la résilience des connexions.
  3. DbExecutionStrategy : cette classe est adaptée en tant que classe de base pour d’autres stratégies d’exécution, y compris vos propres stratégies personnalisées. Elle implémente une stratégie de nouvelles tentatives exponentielle, où la nouvelle tentative initiale se produit avec un délai nul et le délai augmente de manière exponentielle jusqu’à ce que le nombre maximal de nouvelles tentatives soit atteint. Cette classe a une méthode ShouldRetryOn abstraite qui peut être implémentée dans des stratégies d’exécution dérivées afin de contrôler quelles exceptions doivent être retentées.
  4. SqlAzureExecutionStrategy : cette stratégie d’exécution hérite de DbExecutionStrategy et réalise de nouvelles tentatives sur les exceptions connues pour être éventuellement temporaires lors de l’utilisation d’Azure SQL Database.

Remarque

Les stratégies d’exécution 2 et 4 sont incluses dans le fournisseur SQL Server fourni avec EF, qui se trouve dans l’assembly EntityFramework.SqlServer et sont conçues pour fonctionner avec SQL Server.

Activer une stratégie d’exécution

Le moyen le plus simple d’indiquer à EF d’utiliser une stratégie d’exécution consiste à utiliser la méthode SetExecutionStrategy de la classe DbConfiguration :

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

Ce code indique à EF d’utiliser SqlAzureExecutionStrategy lors de la connexion à SQL Server.

Configuration de la stratégie d’exécution

Le constructeur de SqlAzureExecutionStrategy peut accepter deux paramètres, MaxRetryCount et MaxDelay. Le nombre de MaxRetry correspond au nombre maximal de nouvelles tentatives de la stratégie. MaxDelay est un intervalle TimeSpan qui représente le délai maximal entre les nouvelles tentatives que la stratégie d’exécution réalisera.

Pour définir le nombre maximal de nouvelles tentatives sur 1 et le délai maximal sur 30 secondes, exécutez la commande suivante :

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

SqlAzureExecutionStrategy exécute de nouvelles tentatives dès la première fois qu’une défaillance temporaire se produit, mais retarde plus longtemps entre chaque nouvelle tentative jusqu’à ce que la limite maximale de nouvelles tentatives soit dépassée ou que la durée totale atteint le délai maximal.

Les stratégies d’exécution ne réessayent qu’un nombre limité d’exceptions qui sont généralement temporaires. Vous devrez toujours gérer les autres erreurs et intercepter l’exception RetryLimitExceededed dans le cas où une erreur n’est pas temporaire ou prend trop de temps pour se résoudre.

Il existe certaines limitations connues lors de l’utilisation d’une stratégie d’exécution de nouvelle tentative :

Les requêtes en diffusion en continu ne sont pas prises en charge

Par défaut, EF6 et les versions ultérieures mettent en mémoire tampon les résultats des requêtes plutôt que de les diffuser en continu. Si vous souhaitez que les résultats soient diffusés en continu, vous pouvez utiliser la méthode AsStreaming pour modifier une requête LINQ to Entities en diffusion en continu.

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

La diffusion en continu n’est pas prise en charge lorsqu’une stratégie d’exécution de nouvelle tentative est enregistrée. Cette limitation existe, car la connexion peut s’annuler en plein pendant que les résultats sont retournés. Lorsque cela se produit, EF doit réexécuter l’intégralité de la requête, mais n’a aucun moyen fiable de savoir quels résultats ont déjà été retournés (les données peuvent avoir changé depuis l’envoi de la requête initiale, les résultats peuvent revenir dans un ordre différent, les résultats peuvent ne pas avoir d’identificateur unique, etc.).

Les transactions initiées par l’utilisateur ne sont pas prises en charge

Lorsque vous avez configuré une stratégie d’exécution qui entraîne des nouvelles tentatives, il existe certaines limitations relatives à l’utilisation des transactions.

Par défaut, EF effectue toutes les mises à jour de base de données au sein d’une transaction. Vous n’avez rien à faire pour activer cela, EF effectue toujours cette opération automatiquement.

Par exemple, dans le code suivant, SaveChanges est automatiquement effectué dans une transaction. Si SaveChanges échoue après l’insertion de l’un des nouveaux sites, la transaction est alors restaurée et aucune modification n’est appliquée à la base de données. Le contexte est également laissé dans un état qui permet à SaveChanges d’être à nouveau appelé pour réessayer d’appliquer les modifications.

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();
}

Lorsque vous n’utilisez pas de stratégie d’exécution de nouvelle tentative, vous pouvez envelopper plusieurs opérations dans une seule transaction. Par exemple, le code suivant enveloppe deux appels SaveChanges dans une seule transaction. Si une partie d’une des opérations échoue, aucune des modifications n’est appliquée.

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();
    }
}

Cela n’est pas pris en charge lors de l’utilisation d’une stratégie d’exécution de nouvelle tentative, car EF ne connaît pas les opérations précédentes et ne sait pas comment les réessayer. Par exemple, si le deuxième SaveChanges échoue, EF n’a plus les informations requises pour réessayer le premier appel SaveChanges.

Solution : appeler manuellement la stratégie d’exécution

La solution consiste à utiliser manuellement la stratégie d’exécution et à lui donner l’ensemble de la logique à exécuter, afin qu’elle puisse réaliser de nouvelles tentatives sur toutes les opérations si l’une d’entre elles échoue. Lorsqu’une stratégie d’exécution dérivée de DbExecutionStrategy est en cours d’exécution, elle suspend la stratégie d’exécution implicite utilisée dans SaveChanges.

Notez que tous les contextes doivent être construits dans le bloc de code afin d’être retentés. Cela garantit chaque nouvelle tentative de pouvoir s’exécuter en repartant de zéro.

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();
            }
        }
    });