Implementera elastiska Entity Framework Core SQL-anslutningar
Dricks
Det här innehållet är ett utdrag från eBook, .NET Microservices Architecture for Containerized .NET Applications, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.
För Azure SQL DB tillhandahåller Entity Framework (EF) Core redan intern databasanslutningsåterhämtning och logik för återförsök. Men du måste aktivera Entity Framework-körningsstrategin för varje DbContext anslutning om du vill ha elastiska EF Core-anslutningar.
Följande kod på EF Core-anslutningsnivån möjliggör till exempel elastiska SQL-anslutningar som görs om om anslutningen misslyckas.
// 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);
});
});
Viktigt!
Microsoft rekommenderar att du använder det säkraste tillgängliga autentiseringsflödet. Om du ansluter till Azure SQL är hanterade identiteter för Azure-resurser den rekommenderade autentiseringsmetoden.
Körningsstrategier och explicita transaktioner med BeginTransaction och flera DbContexts
När återförsök aktiveras i EF Core-anslutningar blir varje åtgärd som du utför med EF Core en egen återförsöksbar åtgärd. Varje fråga och varje anrop till SaveChanges
görs på nytt som en enhet om ett tillfälligt fel inträffar.
Men om koden initierar en transaktion med , BeginTransaction
definierar du din egen grupp med åtgärder som måste behandlas som en enhet. Allt i transaktionen måste återställas om ett fel inträffar.
Om du försöker köra transaktionen när du använder en EF-körningsstrategi (återförsöksprincip) och du anropar SaveChanges
från flera DbContexts får du ett undantag som det här:
System.InvalidOperationException: Den konfigurerade körningsstrategin "SqlServerRetryingExecutionStrategy" stöder inte användarinitierade transaktioner. Använd körningsstrategin som returneras av "DbContext.Database.CreateExecutionStrategy()" för att köra alla åtgärder i transaktionen som en enhet som kan göras om.
Lösningen är att manuellt anropa EF-körningsstrategin med ett ombud som representerar allt som behöver köras. Om det inträffar ett tillfälligt fel anropar körningsstrategin delegate igen. Följande kod visar till exempel hur den implementeras i eShopOnContainers med två flera DbContexts (_catalogContext och IntegrationEventLogContext) när du uppdaterar en produkt och sedan sparar objektet ProductPriceChangedIntegrationEvent, som måste använda en annan 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();
}
}
Den första DbContext är _catalogContext
och den andra DbContext
finns i _catalogIntegrationEventService
objektet. Incheckningsåtgärden utförs för alla DbContext
objekt med hjälp av en EF-körningsstrategi.
För att uppnå denna flera DbContext
incheckning SaveEventAndCatalogContextChangesAsync
använder en ResilientTransaction
klass, som visas i följande kod:
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());
});
}
}
Metoden ResilientTransaction.ExecuteAsync
börjar i princip en transaktion från den skickade DbContext
(_catalogContext
) och använder sedan transaktionen EventLogService
för att spara ändringar från IntegrationEventLogContext
och genomför sedan hela transaktionen.
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();
});
}
}
Ytterligare resurser
Anslutningsåterhämtning och kommandoavlyssning med EF i ett ASP.NET MVC-program
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. Använda Resilient Entity Framework Core SQL-anslutningar och -transaktioner
https://devblogs.microsoft.com/cesardelatorre/using-resilient-entity-framework-core-sql-connections-and-transactions-retries-with-exponential-backoff/