Resiliência da conexãoConnection Resiliency

Resiliência de Conexão repete automaticamente os comandos de banco de dados com falha.Connection resiliency automatically retries failed database commands. O recurso pode ser usado com qualquer banco de dados, fornecendo uma "estratégia de execução," que encapsula a lógica necessária para detectar falhas e tente novamente comandos.The feature can be used with any database by supplying an "execution strategy", which encapsulates the logic necessary to detect failures and retry commands. Provedores de núcleo EF podem fornecer estratégias de execução personalizadas para suas condições de falha do banco de dados específico e políticas de repetição ideal.EF Core providers can supply execution strategies tailored to their specific database failure conditions and optimal retry policies.

Por exemplo, o provedor SQL Server inclui uma estratégia de execução foi especificamente projetada para o SQL Server (incluindo o SQL Azure).As an example, the SQL Server provider includes an execution strategy that is specifically tailored to SQL Server (including SQL Azure). Ele está ciente dos tipos de exceção que podem ser repetidos e tem a sensatos padrões para o número máximo de tentativas, atraso entre repetições, etc.It is aware of the exception types that can be retried and has sensible defaults for maximum retries, delay between retries, etc.

Uma estratégia de execução for especificada ao configurar as opções para o contexto.An execution strategy is specified when configuring the options for your context. Isso é normalmente no OnConfiguring método de seu contexto derivado ou no Startup.cs para um aplicativo 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());
}

Estratégia de execução personalizadoCustom execution strategy

Há um mecanismo para registrar uma estratégia personalizada de execução de sua preferência, se você quiser alterar qualquer um dos padrões.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(...));
}

Transações e estratégias de execuçãoExecution strategies and transactions

Uma estratégia de execução repete automaticamente em caso de falha deve ser capaz de executar cada operação em um bloco de repetição que falha.An execution strategy that automatically retries on failures needs to be able to play back each operation in an retry block that fails. Quando novas tentativas são habilitadas, cada operação executar via EF Core torna-se sua própria operação repetível, ou seja, cada consulta e cada chamada para SaveChanges() será repetida como uma unidade se ocorrer uma falha temporária.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.

No entanto, se seu código inicia uma transação usando BeginTransaction() você está definindo seu próprio grupo de operações que precisam ser tratados como uma unidade, ou seja, tudo dentro da transação deve ser reproduzida deverá ocorrer uma falha.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. Se você tentar fazer isso ao usar uma estratégia de execução, você receberá uma exceção semelhante à seguinte.You will receive an exception like the following if you attempt to do this when using an execution strategy.

InvalidOperationException: A estratégia de execução configurada 'SqlServerRetryingExecutionStrategy' não oferece suporte a transações iniciadas pelo usuário.InvalidOperationException: The configured execution strategy 'SqlServerRetryingExecutionStrategy' does not support user initiated transactions. Use a estratégia de execução retornada por 'DbContext.Database.CreateExecutionStrategy()' para executar todas as operações na transação como uma unidade repetível.Use the execution strategy returned by 'DbContext.Database.CreateExecutionStrategy()' to execute all the operations in the transaction as a retriable unit.

A solução é chamar manualmente a estratégia de execução com um delegado que representa tudo o que precisa ser executada.The solution is to manually invoke the execution strategy with a delegate representing everything that needs to be executed. Se ocorrer uma falha transitória, a estratégia de execução invocará o representante novamente.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();
            }
        }
    });
}

Falha de confirmação de transação e o problema de idempotênciaTransaction commit failure and the idempotency issue

Em geral, quando há uma falha de conexão a transação atual será revertida.In general, when there is a connection failure the current transaction is rolled back. No entanto, se a conexão for descartada enquanto a transação está sendo confirmada resultante estado da transação é desconhecido.However, if the connection is dropped while the transaction is being committed the resulting state of the transaction is unknown. Consulte este postagem de blog para obter mais detalhes.See this blog post for more details.

Por padrão, a estratégia de execução tentará repetir a operação como se a transação foi revertida, mas se não for o caso isso resultará em uma exceção se o novo estado do banco de dados é incompatível ou pode levar a corrupção de dados se a operação não se baseia em um estado específico, por exemplo, ao inserir uma nova linha com valores de chave gerada automaticamente.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.

Há várias maneiras de lidar com isso.There are several ways to deal with this.

Opção 1 - fazer nada (quase)Option 1 - Do (almost) nothing

A probabilidade de uma falha de conexão durante a confirmação da transação é baixa, portanto, pode ser aceitável para o aplicativo falhar apenas se essa condição ocorrer, na verdade.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.

No entanto, você precisa evitar o uso de chaves geradas pelo repositório para garantir que uma exceção será lançada em vez de adicionar uma linha duplicada.However, you need to avoid using store-generated keys in order to ensure that an exception is thrown instead of adding a duplicate row. Considere usar um valor GUID gerado pelo cliente ou um gerador do valor do lado do cliente.Consider using a client-generated GUID value or a client-side value generator.

Opção 2 - reconstruir o estado do aplicativoOption 2 - Rebuild application state

  1. Descartar atual DbContext.Discard the current DbContext.
  2. Criar um novo DbContext e restaurar o estado do seu aplicativo do banco de dados.Create a new DbContext and restore the state of your application from the database.
  3. Informe ao usuário que a última operação pode não ter sido concluída com êxito.Inform the user that the last operation might not have been completed successfully.

Opção 3 - adicionar estado de verificaçãoOption 3 - Add state verification

Para a maioria das operações que alteram o estado do banco de dados, é possível adicionar o código que verifica se ela foi bem-sucedida.For most of the operations that change the database state it is possible to add code that checks whether it succeeded. EF fornece um método de extensão para facilitar essa tarefa - IExecutionStrategy.ExecuteInTransaction.EF provides an extension method to make this easier - IExecutionStrategy.ExecuteInTransaction.

Esse método inicia e confirma uma transação e também aceita uma função no verifySucceeded parâmetro que é invocado quando ocorre um erro temporário durante a confirmação da transação.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();
}

Observação

Aqui SaveChanges é invocado com acceptAllChangesOnSuccess definida como false para evitar alterar o estado do Blog entidade Unchanged se SaveChanges for bem-sucedida.Here SaveChanges is invoked with acceptAllChangesOnSuccess set to false to avoid changing the state of the Blog entity to Unchanged if SaveChanges succeeds. Isso permite que repita a operação mesmo se houver uma falha e a transação será revertida.This allows to retry the same operation if the commit fails and the transaction is rolled back.

Opção 4 - controlar manualmente a transaçãoOption 4 - Manually track the transaction

Se você precisar usar chaves geradas pelo repositório ou precisam de um modo genérico de tratamento de falhas de confirmação que não depende da operação executada cada transação pôde ser atribuída a uma ID que é verificada quando a confirmação falhará.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. Adicione uma tabela no banco de dados usado para acompanhar o status das transações.Add a table to the database used to track the status of the transactions.
  2. Inserir uma linha na tabela no início de cada transação.Insert a row into the table at the beginning of each transaction.
  3. Se a conexão falhar durante a confirmação, verifique a presença da linha correspondente no banco de dados.If the connection fails during the commit, check for the presence of the corresponding row in the database.
  4. Se a confirmação for bem-sucedido, exclua a linha correspondente para evitar o crescimento da tabela.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();
}

Observação

Certifique-se de que o contexto usado para a verificação tiver uma estratégia de execução definida como a conexão é provavelmente falharão novamente durante a verificação se ela teve falha durante a confirmação da transação.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.