Реализация устойчивых SQL-подключений Entity Framework Core
Совет
Это содержимое является фрагментом из электронной книги, архитектуры микрослужб .NET для контейнерных приложений .NET, доступных в документации .NET или в виде бесплатного скачиваемого PDF-файла, который можно читать в автономном режиме.
Для Базы данных SQL Azure платформа Entity Framework (EF) Core уже предоставляет логику устойчивости и повторных попыток при подключении к внутренним базам данных. Но вам необходимо применить стратегию выполнения Entity Framework к каждому подключению DbContext, чтобы обеспечить устойчивое подключение к EF Core.
Например, следующий код на уровне подключения к EF Core обеспечивает устойчивое SQL-подключение, которое устанавливается повторно при сбое.
// 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);
});
});
Внимание
Корпорация Майкрософт рекомендует использовать самый безопасный поток проверки подлинности. Если вы подключаетесь к SQL Azure, управляемые удостоверения для ресурсов Azure — это рекомендуемый метод проверки подлинности.
Стратегии выполнения и явные транзакции с использованием BeginTransaction и нескольких DbContext
Если в подключениях к EF Core включены повторные попытки, каждая операция, выполняемая с помощью EF Core, будет предпринимать повторные попытки. Каждый запрос и каждый вызов к SaveChanges
будет повторяться снова как единица в случае сбоя.
Но если код инициирует транзакцию с помощью BeginTransaction
, вы сами определяете группу операций, которые должны рассматриваться как единица. Все содержимое транзакции можно будет откатить в случае сбоя.
Если вы попытаетесь выполнить эту транзакцию при использовании стратегии выполнения EF (политика повтора) и вызываете SaveChanges
из нескольких DbContext, отобразится исключение такого типа:
System.InvalidOperationException: настроенная стратегия выполнения 'SqlServerRetryingExecutionStrategy' не поддерживает запуск транзакций пользователем. Используйте стратегию выполнения, возвращенную 'DbContext.Database.CreateExecutionStrategy()', чтобы выполнить все операции в транзакции как повторяемую единицу.
Необходимо вручную вызвать стратегию выполнения EF с делегатом, который представляет все, что должно быть выполнено. Если происходит временный сбой, стратегия выполнения снова вызывает делегат. Например, в следующем коде показано, как он реализован в eShopOnContainers с двумя несколькими DbContexts (_catalogContext и IntegrationEventLogContext) при обновлении продукта и последующем сохранении объекта ProductPriceChangedIntegrationEvent, который должен использовать другой 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();
}
}
Первый DbContext — это _catalogContext
, а второй DbContext
находится в пределах объекта _catalogIntegrationEventService
. Действие фиксации выполняется во всех объектах DbContext
с помощью стратегии выполнения EF.
Для достижения такой фиксации нескольких DbContext
SaveEventAndCatalogContextChangesAsync
использует класс ResilientTransaction
, как показано в приведенном ниже примере кода.
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());
});
}
}
Метод ResilientTransaction.ExecuteAsync
, по сути, начинает транзакцию из переданного DbContext
(_catalogContext
) и затем указывает EventLogService
использовать эту транзакцию для сохранения изменений из IntegrationEventLogContext
, после чего фиксирует всю транзакцию.
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();
});
}
}
Дополнительные ресурсы
Устойчивость подключений и перехват команд c помощью EF в приложении 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-applicationСезар де ла Торре (Cesar de la Torre). Using Resilient Entity Framework Core SQL Connections and Transactions (Использование устойчивых SQL-подключений Entity Framework Core и транзакций)
https://devblogs.microsoft.com/cesardelatorre/using-resilient-entity-framework-core-sql-connections-and-transactions-retries-with-exponential-backoff/