Elastyczność połączenia

odporność Połączenie automatycznie ponawia próbę nieudanych poleceń bazy danych. Funkcji można używać z dowolną bazą danych, podając "strategię wykonywania", która hermetyzuje logikę niezbędną do wykrywania błędów i ponawiania prób poleceń. Dostawcy platformy EF Core mogą dostarczać strategie wykonywania dostosowane do określonych warunków awarii bazy danych i optymalne zasady ponawiania prób.

Na przykład dostawca programu SQL Server zawiera strategię wykonywania, która jest specjalnie dopasowana do programu SQL Server (w tym Usługi SQL Azure). Jest świadomy typów wyjątków, które można ponowić i ma rozsądne wartości domyślne dla maksymalnych ponownych prób, opóźnienia między ponownymi próbami itp.

Strategia wykonywania jest określana podczas konfigurowania opcji dla kontekstu. Zazwyczaj jest OnConfiguring to metoda kontekstu pochodnego:

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

lub w Startup.cs aplikacji ASP.NET Core:

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

Uwaga

Włączenie ponawiania próby po awarii powoduje, że program EF wewnętrznie buforuje zestaw wyników, co może znacznie zwiększyć wymagania dotyczące pamięci dla zapytań zwracających duże zestawy wyników. Aby uzyskać więcej informacji, zobacz buforowanie i przesyłanie strumieniowe .

Strategia wykonywania niestandardowego

Istnieje mechanizm rejestrowania własnej niestandardowej strategii wykonywania, jeśli chcesz zmienić dowolną z wartości domyślnych.

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

Strategie wykonywania i transakcje

Strategia wykonywania, która automatycznie ponawia próby w przypadku niepowodzeń, musi być w stanie odtworzyć każdą operację w bloku ponawiania, który kończy się niepowodzeniem. Po włączeniu ponownych prób każda operacja wykonywana za pośrednictwem programu EF Core staje się własną operacją ponawiania prób. Oznacza to, że każde zapytanie i każde wywołanie SaveChanges() zostaną ponawiane jako jednostka, jeśli wystąpi błąd przejściowy.

Jeśli jednak kod inicjuje transakcję przy użyciu BeginTransaction() definiowania własnej grupy operacji, które muszą być traktowane jako jednostka, a wszystko wewnątrz transakcji musi zostać odtworzona, wystąpi awaria. Jeśli spróbujesz to zrobić podczas korzystania ze strategii wykonywania, otrzymasz wyjątek podobny do następującego:

InvalidOperationException: skonfigurowana strategia wykonywania "SqlServerRetryingExecutionStrategy" nie obsługuje transakcji inicjowanych przez użytkownika. Użyj strategii wykonywania zwróconej przez polecenie "DbContext.Database.CreateExecutionStrategy()", aby wykonać wszystkie operacje w transakcji jako jednostkę, którą można pobrać.

Rozwiązaniem jest ręczne wywołanie strategii wykonywania z delegatem reprezentującym wszystko, co należy wykonać. Jeśli wystąpi błąd przejściowy, strategia wykonywania ponownie wywoła delegata.


using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();

strategy.Execute(
    () =>
    {
        using var context = new BloggingContext();
        using var transaction = context.Database.BeginTransaction();

        context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        context.SaveChanges();

        context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
        context.SaveChanges();

        transaction.Commit();
    });

Takie podejście można również stosować w przypadku transakcji otoczenia.


using var context1 = new BloggingContext();
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });

var strategy = context1.Database.CreateExecutionStrategy();

strategy.Execute(
    () =>
    {
        using var context2 = new BloggingContext();
        using var transaction = new TransactionScope();

        context2.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        context2.SaveChanges();

        context1.SaveChanges();

        transaction.Complete();
    });

Niepowodzenie zatwierdzania transakcji i problem z idempotencją

Ogólnie rzecz biorąc, gdy wystąpi błąd połączenia, bieżąca transakcja zostanie wycofana. Jeśli jednak połączenie zostanie przerwane, gdy transakcja jest zatwierdzana, wynikowy stan transakcji jest nieznany.

Domyślnie strategia wykonywania ponowi próbę wykonania operacji tak, jakby transakcja została wycofana, ale jeśli tak nie jest, spowoduje to wyjątek, jeśli nowy stan bazy danych jest niezgodny lub może prowadzić do uszkodzenia danych, jeśli operacja nie opiera się na określonym stanie, na przykład podczas wstawiania nowego wiersza z wartościami klucza generowanego automatycznie.

Istnieje kilka sposobów radzenia sobie z tym.

Opcja 1 — Nie rób (prawie) nic

Prawdopodobieństwo niepowodzenia połączenia podczas zatwierdzania transakcji jest niskie, więc może być akceptowalne, aby aplikacja mogła po prostu zakończyć się niepowodzeniem, jeśli ten warunek rzeczywiście wystąpi.

Należy jednak unikać używania kluczy generowanych przez magazyn, aby upewnić się, że wyjątek jest zgłaszany zamiast dodawania zduplikowanego wiersza. Rozważ użycie wartości identyfikatora GUID wygenerowanego przez klienta lub generatora wartości po stronie klienta.

Opcja 2 — ponowne kompilowanie stanu aplikacji

  1. Odrzuć bieżący DbContextelement .
  2. Utwórz nowy DbContext i przywróć stan aplikacji z bazy danych.
  3. Poinformuj użytkownika, że ostatnia operacja mogła nie zostać ukończona pomyślnie.

Opcja 3 — Dodawanie weryfikacji stanu

W przypadku większości operacji, które zmieniają stan bazy danych, można dodać kod sprawdzający, czy zakończył się pomyślnie. Program EF udostępnia metodę rozszerzenia, aby ułatwić ten proces — IExecutionStrategy.ExecuteInTransaction.

Ta metoda rozpoczyna i zatwierdza transakcję, a także akceptuje funkcję w parametrze verifySucceeded wywoływanym w przypadku wystąpienia błędu przejściowego podczas zatwierdzania transakcji.


using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();

var blogToAdd = new Blog { Url = "http://blogs.msdn.com/dotnet" };
db.Blogs.Add(blogToAdd);

strategy.ExecuteInTransaction(
    db,
    operation: context => { context.SaveChanges(acceptAllChangesOnSuccess: false); },
    verifySucceeded: context => context.Blogs.AsNoTracking().Any(b => b.BlogId == blogToAdd.BlogId));

db.ChangeTracker.AcceptAllChanges();

Uwaga

W tym miejscu SaveChanges wywoływana jest funkcja z ustawioną wartością acceptAllChangesOnSuccess , aby false uniknąć zmiany stanu Blog jednostki na Unchanged wartość w przypadku SaveChanges powodzenia. Dzięki temu można ponowić próbę wykonania tej samej operacji, jeśli zatwierdzenie zakończy się niepowodzeniem, a transakcja zostanie wycofana.

Opcja 4 — ręczne śledzenie transakcji

Jeśli musisz użyć kluczy generowanych przez magazyn lub potrzebujesz ogólnego sposobu obsługi niepowodzeń zatwierdzania, które nie zależą od operacji wykonanej każdej transakcji, można przypisać identyfikator sprawdzany, gdy zatwierdzenie zakończy się niepowodzeniem.

  1. Dodaj tabelę do bazy danych używanej do śledzenia stanu transakcji.
  2. Wstaw wiersz do tabeli na początku każdej transakcji.
  3. Jeśli połączenie nie powiedzie się podczas zatwierdzania, sprawdź obecność odpowiedniego wiersza w bazie danych.
  4. Jeśli zatwierdzenie zakończy się pomyślnie, usuń odpowiedni wiersz, aby uniknąć wzrostu tabeli.

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

strategy.ExecuteInTransaction(
    db,
    operation: context => { context.SaveChanges(acceptAllChangesOnSuccess: false); },
    verifySucceeded: context => context.Transactions.AsNoTracking().Any(t => t.Id == transaction.Id));

db.ChangeTracker.AcceptAllChanges();
db.Transactions.Remove(transaction);
db.SaveChanges();

Uwaga

Upewnij się, że kontekst używany do weryfikacji ma strategię wykonywania zdefiniowaną jako połączenie prawdopodobnie nie powiedzie się ponownie podczas weryfikacji, jeśli nie powiodło się podczas zatwierdzania transakcji.

Dodatkowe zasoby