Implementar conexões resilientes do Entity Framework Core SQL
Gorjeta
Este conteúdo é um trecho do eBook, .NET Microservices Architecture for Containerized .NET Applications, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.
Para o Banco de Dados SQL do Azure, o Entity Framework (EF) Core já fornece resiliência de conexão de banco de dados interno e lógica de repetição. Mas você precisa habilitar a estratégia de execução do Entity Framework para cada DbContext conexão se quiser ter conexões EF Core resilientes.
Por exemplo, o código a seguir no nível de conexão EF Core permite conexões SQL resilientes que são repetidas se a conexão falhar.
// Program.cs from any ASP.NET Core Web API
// Other code ...
builder.Services.AddDbContext<CatalogContext>(options =>
{
options.UseSqlServer(
builder.Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
});
Importante
A Microsoft recomenda que você use o fluxo de autenticação mais seguro disponível. Se você estiver se conectando ao SQL do Azure, as Identidades Gerenciadas para recursos do Azure serão o método de autenticação recomendado.
Estratégias de execução e transações explícitas usando BeginTransaction e vários DbContexts
Quando novas tentativas são habilitadas em conexões EF Core, cada operação executada usando o EF Core se torna sua própria operação retryable. Cada consulta e cada chamada para SaveChanges
será repetida como uma unidade se ocorrer uma falha transitória.
No entanto, se o código iniciar uma transação usando BeginTransaction
o , você está definindo seu próprio grupo de operações que precisam ser tratadas como uma unidade. Tudo dentro da transação tem que ser revertido se ocorrer uma falha.
Se você tentar executar essa transação ao usar uma estratégia de execução do EF (política de repetição) e chamar SaveChanges
de vários DbContexts, obterá uma exceção como esta:
System.InvalidOperationException: A estratégia de execução configurada 'SqlServerRetryingExecutionStrategy' não suporta transações iniciadas pelo usuário. Use a estratégia de execução retornada por 'DbContext.Database.CreateExecutionStrategy()' para executar todas as operações na transação como uma unidade recuperável.
A solução é invocar manualmente a estratégia de execução do EF com um delegado representando tudo o que precisa ser executado. Se ocorrer uma falha transitória, a estratégia de execução invocará o delegado novamente. Por exemplo, o código a seguir mostra como ele é implementado no eShopOnContainers com dois vários DbContexts (_catalogContext e o IntegrationEventLogContext) ao atualizar um produto e, em seguida, salvar o objeto ProductPriceChangedIntegrationEvent, que precisa usar um DbContext diferente.
public async Task<IActionResult> UpdateProduct(
[FromBody]CatalogItem productToUpdate)
{
// Other code ...
var oldPrice = catalogItem.Price;
var raiseProductPriceChangedEvent = oldPrice != productToUpdate.Price;
// Update current product
catalogItem = productToUpdate;
// Save product's data and publish integration event through the Event Bus
// if price has changed
if (raiseProductPriceChangedEvent)
{
//Create Integration Event to be published through the Event Bus
var priceChangedEvent = new ProductPriceChangedIntegrationEvent(
catalogItem.Id, productToUpdate.Price, oldPrice);
// Achieving atomicity between original Catalog database operation and the
// IntegrationEventLog thanks to a local transaction
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(
priceChangedEvent);
// Publish through the Event Bus and mark the saved event as published
await _catalogIntegrationEventService.PublishThroughEventBusAsync(
priceChangedEvent);
}
// Just save the updated product because the Product's Price hasn't changed.
else
{
await _catalogContext.SaveChangesAsync();
}
}
O primeiro DbContext é _catalogContext
e o segundo DbContext
está dentro do _catalogIntegrationEventService
objeto. A ação Confirmar é executada em todos os DbContext
objetos usando uma estratégia de execução do EF.
Para obter essa confirmação múltipla DbContext
, o SaveEventAndCatalogContextChangesAsync
usa uma ResilientTransaction
classe, conforme mostrado no código a seguir:
public class CatalogIntegrationEventService : ICatalogIntegrationEventService
{
//…
public async Task SaveEventAndCatalogContextChangesAsync(
IntegrationEvent evt)
{
// Use of an EF Core resiliency strategy when using multiple DbContexts
// within an explicit BeginTransaction():
// https://learn.microsoft.com/ef/core/miscellaneous/connection-resiliency
await ResilientTransaction.New(_catalogContext).ExecuteAsync(async () =>
{
// Achieving atomicity between original catalog database
// operation and the IntegrationEventLog thanks to a local transaction
await _catalogContext.SaveChangesAsync();
await _eventLogService.SaveEventAsync(evt,
_catalogContext.Database.CurrentTransaction.GetDbTransaction());
});
}
}
O ResilientTransaction.ExecuteAsync
método basicamente inicia uma transação a partir do passado DbContext
(_catalogContext
) e, em seguida, faz o EventLogService
uso dessa transação para salvar as alterações do IntegrationEventLogContext
e, em seguida, confirma toda a transação.
public class ResilientTransaction
{
private DbContext _context;
private ResilientTransaction(DbContext context) =>
_context = context ?? throw new ArgumentNullException(nameof(context));
public static ResilientTransaction New (DbContext context) =>
new ResilientTransaction(context);
public async Task ExecuteAsync(Func<Task> action)
{
// Use of an EF Core resiliency strategy when using multiple DbContexts
// within an explicit BeginTransaction():
// https://learn.microsoft.com/ef/core/miscellaneous/connection-resiliency
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using var transaction = await _context.Database.BeginTransactionAsync();
await action();
await transaction.CommitAsync();
});
}
}
Recursos adicionais
Resiliência de conexão e intercetação de comandos com EF em um aplicativo MVC ASP.NET
https://learn.microsoft.com/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/connection-resiliency-and-command-interception-with-the-entity-framework-in-an-asp-net-mvc-applicationCésar da Torre. Usando conexões e transações SQL principais do Resilient Entity Framework
https://devblogs.microsoft.com/cesardelatorre/using-resilient-entity-framework-core-sql-connections-and-transactions-retries-with-exponential-backoff/