Verbindingstolerantie
Verbindingstolerantie probeert automatisch mislukte databaseopdrachten opnieuw uit te voeren. De functie kan worden gebruikt met elke database door een 'uitvoeringsstrategie' op te geven, die de logica inkapselt die nodig is om fouten te detecteren en opdrachten opnieuw uit te voeren. EF Core-providers kunnen uitvoeringsstrategieën leveren die zijn afgestemd op hun specifieke voorwaarden voor databasefouten en optimaal beleid voor opnieuw proberen.
De SQL Server-provider bevat bijvoorbeeld een uitvoeringsstrategie die speciaal is afgestemd op SQL Server (inclusief SQL Azure). Het is op de hoogte van de uitzonderingstypen die opnieuw kunnen worden geprobeerd en die verstandige standaardwaarden hebben voor maximale nieuwe pogingen, vertraging tussen nieuwe pogingen, enzovoort.
Er wordt een uitvoeringsstrategie opgegeven bij het configureren van de opties voor uw context. Dit bevindt zich doorgaans in de OnConfiguring
methode van uw afgeleide context:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFMiscellanous.ConnectionResiliency;Trusted_Connection=True;ConnectRetryCount=0",
options => options.EnableRetryOnFailure());
}
of in Startup.cs
voor een ASP.NET Core-toepassing:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PicnicContext>(
options => options.UseSqlServer(
"<connection string>",
providerOptions => providerOptions.EnableRetryOnFailure()));
}
Notitie
Het inschakelen van nieuwe pogingen bij fouten zorgt ervoor dat EF de resultatenset intern buffert, waardoor de geheugenvereisten voor query's die grote resultatensets retourneren aanzienlijk kunnen verhogen. Zie buffering en streaming voor meer informatie.
Aangepaste uitvoeringsstrategie
Er is een mechanisme voor het registreren van een aangepaste uitvoeringsstrategie van uw eigen als u een van de standaardinstellingen wilt wijzigen.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseMyProvider(
"<connection string>",
options => options.ExecutionStrategy(...));
}
Uitvoeringsstrategieën en -transacties
Een uitvoeringsstrategie die automatisch opnieuw probeert bij fouten, moet elke bewerking in een falende herhaalblok opnieuw kunnen uitvoeren. Wanneer nieuwe pogingen zijn ingeschakeld, wordt elke bewerking die u uitvoert via EF Core een eigen herhaalbare operatie. Dat wil gezegd, elke query en elke aanroep naar SaveChangesAsync()
opnieuw worden geprobeerd als een eenheid als er een tijdelijke fout optreedt.
Als uw code echter een transactie initieert met behulp van BeginTransactionAsync()
, definieert u uw eigen groep bewerkingen die als eenheid moeten worden behandeld. In geval van een fout moet alles binnen de transactie worden teruggedraaid. U ontvangt een uitzondering zoals hieronder als u dit probeert te doen wanneer u een uitvoeringsstrategie gebruikt:
InvalidOperationException: De geconfigureerde uitvoeringsstrategie SqlServerRetryingExecutionStrategy biedt geen ondersteuning voor door de gebruiker geïnitieerde transacties. Gebruik de uitvoeringsstrategie die wordt geretourneerd door DbContext.Database.CreateExecutionStrategy()) om alle bewerkingen in de transactie uit te voeren als een ophaalbare eenheid.
De oplossing is om de uitvoeringsstrategie handmatig aan te roepen met een gemachtigde die alles vertegenwoordigt dat moet worden uitgevoerd. Als er een tijdelijke fout optreedt, roept de uitvoeringsstrategie de gemachtigde opnieuw aan.
using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(
async () =>
{
using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
await context.SaveChangesAsync();
await transaction.CommitAsync();
});
Deze benadering kan ook worden gebruikt met omgevingstransacties.
using var context1 = new BloggingContext();
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
var strategy = context1.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(
async () =>
{
using var context2 = new BloggingContext();
using var transaction = new TransactionScope();
context2.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context2.SaveChangesAsync();
await context1.SaveChangesAsync();
transaction.Complete();
});
Fout bij het doorvoeren van transacties en het idempotentieprobleem
In het algemeen wordt de huidige transactie teruggedraaid wanneer er een verbindingsfout optreedt. Als de verbinding echter wordt verbroken terwijl de transactie wordt doorgevoerd, is de resulterende status van de transactie onbekend.
Standaard wordt met de uitvoeringsstrategie de bewerking opnieuw uitgevoerd alsof de transactie is teruggedraaid, maar als dit niet het geval is, resulteert dit in een uitzondering als de nieuwe databasestatus niet compatibel is of kan leiden tot beschadiging van gegevens als de bewerking niet afhankelijk is van een bepaalde status, bijvoorbeeld wanneer een nieuwe rij met automatisch gegenereerde sleutelwaarden wordt ingevoegd.
Er zijn verschillende manieren om hiermee om te gaan.
Optie 1: doe (bijna) niets
De kans op een verbindingsfout tijdens het doorvoeren van transacties is laag, dus het kan acceptabel zijn dat uw toepassing alleen mislukt als deze voorwaarde daadwerkelijk optreedt.
U moet echter voorkomen dat u opgeslagen sleutels gebruikt om ervoor te zorgen dat er een uitzondering wordt gegenereerd in plaats van een dubbele rij toe te voegen. Overweeg om een door de client gegenereerde GUID-waarde of een generator voor clientzijdewaarden te gebruiken.
Optie 2: Toepassingsstatus opnieuw opbouwen
- Verwijder de huidige
DbContext
. - Maak een nieuwe
DbContext
en herstel de status van uw toepassing vanuit de database. - Informeer de gebruiker dat de laatste bewerking mogelijk niet is voltooid.
Optie 3: statusverificatie toevoegen
Voor de meeste bewerkingen die de databasestatus wijzigen, is het mogelijk om code toe te voegen die controleert of deze is geslaagd. EF biedt een uitbreidingsmethode om dit eenvoudiger te maken - IExecutionStrategy.ExecuteInTransaction
.
Deze methode begint en voert een transactie door en accepteert ook een functie in de verifySucceeded
parameter die wordt aangeroepen wanneer er een tijdelijke fout optreedt tijdens het doorvoeren van de transactie.
using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();
var blogToAdd = new Blog { Url = "http://blogs.msdn.com/dotnet" };
db.Blogs.Add(blogToAdd);
await strategy.ExecuteInTransactionAsync(
db,
operation: (context, cancellationToken) => context.SaveChangesAsync(acceptAllChangesOnSuccess: false, cancellationToken),
verifySucceeded: (context, cancellationToken) => context.Blogs.AsNoTracking().AnyAsync(b => b.BlogId == blogToAdd.BlogId, cancellationToken));
db.ChangeTracker.AcceptAllChanges();
Notitie
Hier SaveChanges
wordt aangeroepen met acceptAllChangesOnSuccess
ingesteld op false
om te voorkomen dat de status van de Blog
-entiteit wordt gewijzigd in Unchanged
als SaveChanges
slaagt. Hierdoor kan dezelfde bewerking opnieuw worden uitgevoerd als de doorvoer mislukt en de transactie wordt teruggedraaid.
Optie 4: de transactie handmatig bijhouden
Als u opgeslagen sleutels wilt gebruiken of een algemene manier nodig hebt om doorvoerfouten af te handelen die niet afhankelijk zijn van de bewerking die elke transactie heeft uitgevoerd, kan een id worden toegewezen die wordt gecontroleerd wanneer de doorvoer mislukt.
- Voeg een tabel toe aan de database die wordt gebruikt om de status van de transacties bij te houden.
- Voeg aan het begin van elke transactie een rij in de tabel in.
- Als de verbinding mislukt tijdens het doorvoeren, controleert u of de bijbehorende rij in de database aanwezig is.
- Als de doorvoering is geslaagd, verwijdert u de bijbehorende rij om de groei van de tabel te voorkomen.
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);
await strategy.ExecuteInTransactionAsync(
db,
operation: (context, cancellationToken) => context.SaveChangesAsync(acceptAllChangesOnSuccess: false, cancellationToken),
verifySucceeded: (context, cancellationToken) => context.Transactions.AsNoTracking().AnyAsync(t => t.Id == transaction.Id, cancellationToken));
db.ChangeTracker.AcceptAllChanges();
db.Transactions.Remove(transaction);
await db.SaveChangesAsync();
Notitie
Zorg ervoor dat voor de context die wordt gebruikt voor de verificatie een uitvoeringsstrategie is gedefinieerd, omdat de verbinding waarschijnlijk opnieuw mislukt tijdens de verificatie als deze is mislukt tijdens het doorvoeren van transacties.