Implementace odolných připojení Entity Framework Core SQL
Tip
Tento obsah je výňatek z eBooku, architektury mikroslužeb .NET pro kontejnerizované aplikace .NET, které jsou k dispozici na .NET Docs nebo jako zdarma ke stažení PDF, které lze číst offline.
Pro Azure SQL DB už Entity Framework (EF) Core poskytuje interní odolnost připojení k databázi a logiku opakování. Pokud ale chcete mít odolná připojení EF Core, musíte pro každé DbContext připojení povolit strategii provádění entity Framework.
Například následující kód na úrovni připojení EF Core umožňuje odolná připojení SQL, která se budou opakovat, pokud se připojení nezdaří.
// 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);
});
});
Důležité
Microsoft doporučuje používat nejbezpečnější dostupný tok ověřování. Pokud se připojujete k Azure SQL, spravované identity pro prostředky Azure se doporučují metodou ověřování.
Strategie provádění a explicitní transakce pomocí BeginTransaction a více DbContexts
Pokud se v připojeních EF Core povolí opakování, každá operace, kterou provádíte pomocí EF Core, se stane vlastní retryable operací. Každý dotaz a každé volání SaveChanges
se bude opakovat jako jednotka, pokud dojde k přechodnému selhání.
Pokud však kód inicializuje transakci pomocí BeginTransaction
, definujete vlastní skupinu operací, které je potřeba považovat za jednotku. Vše uvnitř transakce se musí vrátit zpět, pokud dojde k selhání.
Pokud se pokusíte tuto transakci spustit při použití strategie provádění EF (zásady opakování) a zavoláte SaveChanges
z více dbContexts, zobrazí se výjimka podobná této:
System.InvalidOperationException: Nakonfigurovaná strategie spouštění SqlServerRetryingExecutionStrategy nepodporuje transakce iniciované uživatelem. Použijte strategii provádění vrácenou dbContext.Database.CreateExecutionStrategy() ke spuštění všech operací v transakci jako opakovatelné jednotky.
Řešením je ručně vyvolat strategii provádění EF s delegátem představujícím vše, co je potřeba provést. Pokud dojde k přechodnému selhání, strategie provádění znovu vyvolá delegáta. Následující kód například ukazuje, jak se implementuje v eShopOnContainers se dvěma více DbContexts (_catalogContext a IntegrationEventLogContext) při aktualizaci produktu a uložení objektu ProductPriceChangedIntegrationEvent, který potřebuje použít jiný DbContext.
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();
}
}
První DbContext je a druhý DbContext
je uvnitř objektu _catalogIntegrationEventService
_catalogContext
. Akce Potvrzení se provádí napříč všemi DbContext
objekty pomocí strategie provádění EF.
K dosažení tohoto více DbContext
potvrzení SaveEventAndCatalogContextChangesAsync
používá ResilientTransaction
třídu, jak je znázorněno v následujícím kódu:
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());
});
}
}
Metoda ResilientTransaction.ExecuteAsync
v podstatě zahájí transakci z předaného DbContext
(_catalogContext
) a pak provede EventLogService
použití této transakce k uložení změn z IntegrationEventLogContext
a pak potvrdí celou transakci.
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();
});
}
}
Další materiály
Odolnost připojení a zachycení příkazů s EF v aplikaci ASP.NET MVC
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-applicationCesar de la Torre. Použití odolných připojení a transakcí SQL Entity Framework Core
https://devblogs.microsoft.com/cesardelatorre/using-resilient-entity-framework-core-sql-connections-and-transactions-retries-with-exponential-backoff/