Résilience des connexionsConnection Resiliency

Résilience des connexions tente automatiquement les commandes de base de données a échoué.Connection resiliency automatically retries failed database commands. La fonctionnalité peut être utilisée avec une base de données, vous devez fournir une « stratégie d’exécution », qui encapsule la logique nécessaire pour détecter les erreurs, puis réessayez de commandes.The feature can be used with any database by supplying an "execution strategy", which encapsulates the logic necessary to detect failures and retry commands. Les fournisseurs de base EF peuvent fournir des stratégies d’exécution adaptées à leurs conditions d’échec spécifique de la base de données et les stratégies de nouvelle tentative optimal.EF Core providers can supply execution strategies tailored to their specific database failure conditions and optimal retry policies.

Par exemple, le fournisseur SQL Server inclut une stratégie d’exécution qui est conçu spécialement pour SQL Server (y compris SQL Azure).As an example, the SQL Server provider includes an execution strategy that is specifically tailored to SQL Server (including SQL Azure). Il connaît les types d’exception qui peuvent être retentées et a des valeurs par défaut pour le nombre maximal de tentatives, délai entre les nouvelles tentatives, etc.It is aware of the exception types that can be retried and has sensible defaults for maximum retries, delay between retries, etc.

Stratégie d’exécution est spécifiée lorsque vous configurez les options de votre contexte.An execution strategy is specified when configuring the options for your context. Il s’agit généralement dans le OnConfiguring méthode de votre contexte dérivée ou dans Startup.cs pour une application ASP.NET Core.This is typically in the OnConfiguring method of your derived context, or in Startup.cs for an ASP.NET Core application.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(
            @"Server=(localdb)\mssqllocaldb;Database=EFMiscellanous.ConnectionResiliency;Trusted_Connection=True;ConnectRetryCount=0",
            options => options.EnableRetryOnFailure());
}

Stratégie d’exécution personnalisésCustom execution strategy

Il existe un mécanisme d’enregistrement d’une stratégie d’exécution personnalisée de votre choix si vous souhaitez modifier les valeurs par défaut.There is a mechanism to register a custom execution strategy of your own if you wish to change any of the defaults.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseMyProvider(
            "<connection string>",
            options => options.ExecutionStrategy(...));
}

Les transactions et les stratégies d’exécutionExecution strategies and transactions

Stratégie d’exécution tente automatiquement de défaillances doit être en mesure de lire chaque opération dans un bloc de nouvelle tentative échoue.An execution strategy that automatically retries on failures needs to be able to play back each operation in an retry block that fails. Lorsque de nouvelles tentatives sont activées, chaque opération que vous effectuez via EF Core devient son propre opération reproductible, autrement dit, chaque requête et chaque appel à SaveChanges() sera tentée à nouveau en tant qu’unité si une défaillance temporaire se produit.When retries are enabled, each operation you perform via EF Core becomes its own retriable operation, i.e. each query and each call to SaveChanges() will be retried as a unit if a transient failure occurs.

Toutefois, si votre code initie une transaction à l’aide de BeginTransaction() vous définissez votre propre groupe d’opérations qui doivent être traités en tant qu’unité, autrement dit, tous les éléments à l’intérieur de la transaction devra être lu une défaillance intervient.However, if your code initiates a transaction using BeginTransaction() you are defining your own group of operations that need to be treated as a unit, i.e. everything inside the transaction would need to be played back shall a failure occur. Vous recevez une exception semblable à ce qui suit si vous essayez d’effectuer lors de l’utilisation d’une stratégie d’exécution.You will receive an exception like the following if you attempt to do this when using an execution strategy.

InvalidOperationException : La stratégie d’exécution configurée 'SqlServerRetryingExecutionStrategy' ne prend pas en charge les transactions démarrées par l’utilisateur.InvalidOperationException: The configured execution strategy 'SqlServerRetryingExecutionStrategy' does not support user initiated transactions. Utilisez la stratégie d’exécution retournée par 'DbContext.Database.CreateExecutionStrategy()' pour exécuter toutes les opérations dans la transaction comme une unité reproductible.Use the execution strategy returned by 'DbContext.Database.CreateExecutionStrategy()' to execute all the operations in the transaction as a retriable unit.

La solution consiste à appeler manuellement la stratégie d’exécution avec un délégué qui représente tous les éléments qui doit être exécutée.The solution is to manually invoke the execution strategy with a delegate representing everything that needs to be executed. Si une défaillance temporaire se produit, la stratégie d’exécution appelle le délégué à nouveau.If a transient failure occurs, the execution strategy will invoke the delegate again.

using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    strategy.Execute(() =>
    {
        using (var context = new BloggingContext())
        {
            using (var transaction = context.Database.BeginTransaction())
            {
                context.Blogs.Add(new Blog {Url = "http://blogs.msdn.com/dotnet"});
                context.SaveChanges();

                context.Blogs.Add(new Blog {Url = "http://blogs.msdn.com/visualstudio"});
                context.SaveChanges();

                transaction.Commit();
            }
        }
    });
}

Échec de validation de transaction et le problème idempotenceTransaction commit failure and the idempotency issue

En général, lorsqu’un échec de connexion la transaction en cours est annulée.In general, when there is a connection failure the current transaction is rolled back. Toutefois, si la connexion est perdue pendant la transaction étant validées résultant de la transaction de l’état est inconnu.However, if the connection is dropped while the transaction is being committed the resulting state of the transaction is unknown. Consultez ce billet de blog pour plus d’informations.See this blog post for more details.

Par défaut, la stratégie d’exécution va retenter l’opération comme si la transaction a été restaurée, mais si elle n’est pas le cas cela entraîne une exception si le nouvel état de la base de données n’est pas compatible ou peut entraîner des une altération des données si le opération ne repose pas sur un état particulier, par exemple lors de l’insertion d’une nouvelle ligne avec les valeurs de clés générées automatiquement.By default, the execution strategy will retry the operation as if the transaction was rolled back, but if it's not the case this will result in an exception if the new database state is incompatible or could lead to data corruption if the operation does not rely on a particular state, for example when inserting a new row with auto-generated key values.

Il existe plusieurs façons de gérer cela.There are several ways to deal with this.

Option 1 - (presque) nothingOption 1 - Do (almost) nothing

La probabilité d’un échec de connexion lors de la validation de transaction est faible, il peut être acceptable pour votre application pour simplement de faire échouer si cette condition se produit réellement.The likelihood of a connection failure during transaction commit is low so it may be acceptable for your application to just fail if this condition actually occurs.

Toutefois, vous devez éviter d’utiliser des clés générées par le magasin afin de garantir qu’une exception est levée au lieu d’ajouter une ligne en double.However, you need to avoid using store-generated keys in order to ensure that an exception is thrown instead of adding a duplicate row. Envisagez d’utiliser une valeur GUID générée par le client ou un générateur de valeur du côté client.Consider using a client-generated GUID value or a client-side value generator.

Option 2 - état de l’application reconstructionOption 2 - Rebuild application state

  1. Ignorer l’actuel DbContext.Discard the current DbContext.
  2. Créer un nouveau DbContext et restaurer l’état de votre application à partir de la base de données.Create a new DbContext and restore the state of your application from the database.
  3. Informer l’utilisateur que la dernière opération ne peut-être pas été correctement déroulée.Inform the user that the last operation might not have been completed successfully.

Option 3 - Vérification de l’état de l’ajoutOption 3 - Add state verification

Pour la plupart des opérations qui modifient l’état de la base de données, il est possible d’ajouter du code qui vérifie si elle a réussi.For most of the operations that change the database state it is possible to add code that checks whether it succeeded. EF fournit une méthode d’extension pour simplifier cette utilisation - IExecutionStrategy.ExecuteInTransaction.EF provides an extension method to make this easier - IExecutionStrategy.ExecuteInTransaction.

Cette méthode commence et valide une transaction et accepte également une fonction dans le verifySucceeded paramètre qui est appelé lorsqu’une erreur temporaire se produit pendant la validation de transaction.This method begins and commits a transaction and also accepts a function in the verifySucceeded parameter that is invoked when a transient error occurs during the transaction commit.

using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    var blogToAdd = new Blog {Url = "http://blogs.msdn.com/dotnet"};
    db.Blogs.Add(blogToAdd);

    strategy.ExecuteInTransaction(db,
        operation: context =>
        {
            context.SaveChanges(acceptAllChangesOnSuccess: false);
        },
        verifySucceeded: context => context.Blogs.AsNoTracking().Any(b => b.BlogId == blogToAdd.BlogId));

    db.ChangeTracker.AcceptAllChanges();
}

Note

Ici SaveChanges est appelé avec acceptAllChangesOnSuccess la valeur false pour éviter de modifier l’état de la Blog entité Unchanged si SaveChanges réussit.Here SaveChanges is invoked with acceptAllChangesOnSuccess set to false to avoid changing the state of the Blog entity to Unchanged if SaveChanges succeeds. Cela permet de recommencer l’opération de même si la validation échoue et que la transaction est restaurée.This allows to retry the same operation if the commit fails and the transaction is rolled back.

Option 4 : suivre manuellement la transactionOption 4 - Manually track the transaction

Si vous devez utiliser des clés générées par le magasin ou générique afin de gérer les erreurs de validation ne dépend de l’opération effectuée chaque transaction peut être attribuée à un ID qui est vérifié lors de la validation échoue.If you need to use store-generated keys or need a generic way of handling commit failures that doesn't depend on the operation performed each transaction could be assigned an ID that is checked when the commit fails.

  1. Ajouter une table à la base de données utilisé pour suivre l’état des transactions.Add a table to the database used to track the status of the transactions.
  2. Insérer une ligne dans la table au début de chaque transaction.Insert a row into the table at the beginning of each transaction.
  3. Si la connexion échoue lors de la validation, vérifiez la présence de la ligne correspondante dans la base de données.If the connection fails during the commit, check for the presence of the corresponding row in the database.
  4. Si la validation est réussie, supprimez la ligne correspondante pour éviter la croissance de la table.If the commit is successful, delete the corresponding row to avoid the growth of the table.
using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    db.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });

    var transaction = new TransactionRow {Id = Guid.NewGuid()};
    db.Transactions.Add(transaction);

    strategy.ExecuteInTransaction(db,
        operation: context =>
        {
            context.SaveChanges(acceptAllChangesOnSuccess: false);
        },
        verifySucceeded: context => context.Transactions.AsNoTracking().Any(t => t.Id == transaction.Id));

    db.ChangeTracker.AcceptAllChanges();
    db.Transactions.Remove(transaction);
    db.SaveChanges();
}

Note

Assurez-vous que le contexte utilisé pour la vérification a une stratégie d’exécution définie comme la connexion est susceptible d’échouer lors de la vérification en cas d’échec lors de la validation de la transaction.Make sure that the context used for the verification has an execution strategy defined as the connection is likely to fail again during verification if it failed during transaction commit.