Sdílet prostřednictvím


Odolnost připojení

Odolnost připojení automaticky opakuje neúspěšné databázové příkazy. Tuto funkci lze použít s libovolnou databází tak, že zadáte "strategii provádění", která zapouzdřuje logiku potřebnou k detekci selhání a opakování příkazů. Poskytovatelé EF Core můžou poskytovat strategie provádění přizpůsobené konkrétním stavům selhání databáze a optimálním zásadám opakování.

Poskytovatel SQL Serveru například obsahuje strategii spouštění, která je speciálně přizpůsobená SQL Serveru (včetně SQL Azure). Je si vědom typů výjimek, které je možné opakovat, a má rozumné výchozí hodnoty pro maximální opakování, zpoždění mezi opakováními atd.

Při konfiguraci možností pro váš kontext se zadává strategie provádění. Obvykle se to nachází v metodě OnConfiguring odvozeného kontextu:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(
            @"Server=(localdb)\mssqllocaldb;Database=EFMiscellanous.ConnectionResiliency;Trusted_Connection=True;ConnectRetryCount=0",
            options => options.EnableRetryOnFailure());
}

nebo v Startup.cs pro aplikaci ASP.NET Core:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<PicnicContext>(
        options => options.UseSqlServer(
            "<connection string>",
            providerOptions => providerOptions.EnableRetryOnFailure()));
}

Poznámka

Povolení opakování při selhání způsobí, že EF interně uloží sadu výsledků do vyrovnávací paměti, což může výrazně zvýšit požadavky na paměť pro dotazy vracející velké sady výsledků. Další podrobnosti najdete v tématu ukládání do vyrovnávací paměti a streamování.

Vlastní strategie provádění

Existuje mechanismus pro registraci vlastní strategie spouštění, pokud chcete změnit některou z výchozích hodnot.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseMyProvider(
            "<connection string>",
            options => options.ExecutionStrategy(...));
}

Strategie provádění a transakce

Strategie provádění, která automaticky opakuje pokusy o selhání, musí být schopná přehrát každou operaci v bloku opakování, který selže. Pokud jsou povolené opakování, každá operace, kterou provedete přes EF Core, se stane vlastní operací s možností opakování. To znamená, že každý dotaz a každé volání SaveChangesAsync() bude znovu provedeno jako celek, pokud dojde k přechodnému selhání.

Pokud však váš kód zahájí transakci pomocí BeginTransactionAsync() tím, že definujete vlastní skupinu operací, které je třeba považovat za jednotku, a v případě selhání je potřeba vše uvnitř transakce obnovit. Pokud se to pokusíte provést při použití strategie provádění, zobrazí se výjimka podobná následující:

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í 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.


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();
    });

Tento přístup lze také použít s okolními transakcemi.


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();
    });

Selhání potvrzení transakce a problém s idempotenci

Obecně platí, že pokud dojde k selhání připojení, aktuální transakce se vrátí zpět. Pokud je však připojení ukončeno, zatímco transakce je potvrzena výsledný stav transakce je neznámý.

Ve výchozím nastavení strategie provádění operaci zopakuje, jako kdyby byla transakce vrácena zpět, ale pokud tomu tak není, dojde k výjimce, pokud je nový stav databáze nekompatibilní nebo může vést k poškození dat, pokud operace nespoléhá na konkrétní stav, například při vložení nového řádku s automaticky generovanými hodnotami klíče.

Existuje několik způsobů, jak to vyřešit.

Možnost 1 – Dělat (téměř) nic

Pravděpodobnost selhání připojení během potvrzení transakce je nízká, takže může být přijatelné, aby vaše aplikace mohla pouze selhat, pokud k této podmínce skutečně dojde.

Abyste zároveň zajistili, že místo přidání duplicitního řádku dojde k vyvolání výjimky, musíte se vyhnout použití klíčů vygenerovaných úložištěm. Zvažte použití hodnoty GUID generovaného klientem nebo generátoru hodnot na straně klienta.

Možnost 2 – Opětovné sestavení stavu aplikace

  1. Zbavte se aktuálního DbContext.
  2. Vytvořte novou DbContext a obnovte stav aplikace z databáze.
  3. Informujte uživatele, že poslední operace pravděpodobně nebyla úspěšně dokončena.

Možnost 3 – Přidání ověření stavu

U většiny operací, které mění stav databáze, je možné přidat kód, který zkontroluje, jestli byl úspěšný. EF nabízí metodu rozšíření, která to usnadňuje: IExecutionStrategy.ExecuteInTransaction.

Tato metoda začíná a potvrdí transakci a také přijímá funkci v verifySucceeded parametru, který je vyvolán v případě přechodné chyby během potvrzení transakce.


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();

Poznámka

Zde je SaveChanges vyvolán s acceptAllChangesOnSuccess nastaveným na false, aby se zabránilo změně stavu entity Blog na Unchanged, jestliže SaveChanges uspěje. To umožňuje opakovat stejnou operaci, pokud potvrzení selže a transakce se vrátí zpět.

Možnost 4 – Ruční sledování transakce

Pokud potřebujete použít systémem generované klíče nebo obecný způsob řešení selhání při potvrzování, který nezávisí na operaci prováděné každou transakcí, může být každé transakci přiřazeno ID, které se kontroluje, když potvrzení selže.

  1. Přidejte do databáze tabulku použitou ke sledování stavu transakcí.
  2. Vložte řádek do tabulky na začátku každé transakce.
  3. Pokud se připojení během potvrzení nezdaří, zkontrolujte přítomnost odpovídajícího řádku v databázi.
  4. Pokud je potvrzení úspěšné, odstraňte odpovídající řádek, abyste se vyhnuli růstu tabulky.

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();

Poznámka

Ujistěte se, že kontext použitý k ověření má definovanou strategii provádění, protože připojení pravděpodobně při ověření selže, pokud během potvrzení transakce selhalo.

Další zdroje informací