Verbindungsstabilität

Mit der Verbindungsresilienz werden fehlerhaft ausgeführte Datenbankbefehle automatisch erneut versucht. Das Feature kann mit jeder Datenbank verwendet werden, indem eine Ausführungsstrategie bereitgestellt wird, die die erforderliche Logik zum Erkennen von Fehlern und Wiederholungsbefehlen kapselt. EF Core-Anbieter können Ausführungsstrategien bereitstellen, die auf ihre spezifischen Datenbankfehlerbedingungen und optimale Wiederholungsrichtlinien zugeschnitten sind.

Beispielsweise enthält der SQL Server-Anbieter eine Ausführungsstrategie, die speziell auf SQL Server (einschließlich SQL Azure) zugeschnitten ist. Dabei sind die Ausnahmetypen bekannt, die wiederholt werden können, und es liegen sinnvolle Standardwerte für maximale Wiederholungsversuche, Verzögerung zwischen Wiederholungen usw. vor.

Eine Ausführungsstrategie wird beim Konfigurieren der Optionen für den Kontext angegeben. Dies geschieht sich in der Regel in der OnConfiguring-Methode des abgeleiteten Kontexts:

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

Bei einer bestehenden ASP.NET Core-Anwendung geschieht dies stattdessen in Startup.cs:

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

Hinweis

Das Aktivieren von Wiederholungen bei Fehlern führt dazu, dass EF das Resultset intern puffert. Dies kann zu erheblich erhöhten Speicheranforderungen für Abfragen, die große Resultsets zurückgeben, führen. Weitere Informationen finden Sie unter Puffern und Streaming.

Benutzerdefinierte Ausführungsstrategie

Es gibt einen Mechanismus zum Registrieren einer eigenen benutzerdefinierten Ausführungsstrategie, wenn Sie einen der Standardwerte ändern möchten.

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

Ausführungsstrategien und Transaktionen

Eine Ausführungsstrategie, bei der bei Fehlern automatische Wiederholungen stattfinden, muss alle Vorgänge in einem fehlerhaften Wiederholungsblock wiedergeben können. Wenn Wiederholungen aktiviert sind, wird jeder Vorgang, den Sie mit EF Core durchführen, zu einem individuell wiederholbaren Vorgang. Jede Abfrage und jeder Aufruf von SaveChanges() wird somit als eine Einheit wiederholt, wenn ein vorübergehender Fehler auftritt.

Wenn Ihr Code jedoch eine Transaktion mit BeginTransaction() ausführt, definieren Sie eine eigene Gruppe von Vorgängen, die als Einheit behandelt werden müssen. Alles innerhalb der Transaktion muss zurückgenommen werden, wenn ein Fehler auftritt. Sie erhalten eine Ausnahme wie die folgende, wenn Sie dies bei Verwendung einer Ausführungsstrategie versuchen:

InvalidOperationException: Die konfigurierte Ausführungsstrategie „SqlServerRetryingExecutionStrategy“ unterstützt keine von Benutzer*innen instanziierten Transaktionen. Verwenden Sie die Ausführungsstrategie, die von „DbContext.Database.CreateExecutionStrategy()“ zurückgegeben wird, um alle Vorgänge in der Transaktion als wiederholbare Einheit auszuführen.

Die Lösung besteht darin, die Ausführungsstrategie mit einem Delegaten manuell aufzurufen, der alle Komponenten darstellt, die ausgeführt werden müssen. Die Ausführungsstrategie ruft den Delegaten erneut auf, wenn ein vorübergehender Fehler auftritt.


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

Dieser Ansatz kann auch bei Ambient-Transaktionen verwendet werden.


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

Transaktionscommitfehler und das Idempotenzproblem

Im Allgemeinen wird bei einem Verbindungsfehler die aktuelle Transaktion zurückgesetzt. Wenn die Verbindung jedoch verworfen wird, während die Transaktion committet wird, ist der resultierende Status der Transaktion unbekannt.

Standardmäßig versucht die Ausführungsstrategie den Vorgang erneut, als ob die Transaktion zurückgesetzt wurde. Andernfalls führt dies jedoch zu einer Ausnahme, wenn der neue Datenbankstatus inkompatibel ist, oder kann zu Datenbeschädigung führen, wenn der Vorgang nicht auf einen bestimmten Zustand angewiesen ist, etwa beim Einfügen einer neuen Zeile mit automatisch generierten Schlüsselwerten.

Für den Umgang damit haben Sie verschiedene Möglichkeiten.

Option 1: (fast) nichts tun

Die Wahrscheinlichkeit eines Verbindungsfehlers während des Transaktionscommits ist niedrig, sodass es für Ihre Anwendung akzeptabel sein kann, nur fehlschlagen zu können, wenn diese Bedingung tatsächlich auftritt.

Sie müssen jedoch die Verwendung von im Speicher generierten Schlüsseln vermeiden, um sicherzustellen, dass eine Ausnahme ausgelöst wird, anstatt eine doppelte Zeile hinzuzufügen. Erwägen Sie die Verwendung eines clientseitig generierten GUID-Werts oder eines clientseitigen Wertgenerators.

Option 2: Neuerstellen des Anwendungszustands

  1. Verwerfen Sie den aktuellen DbContext.
  2. Erstellen Sie einen neuen DbContext, und stellen Sie den Status Ihrer Anwendung aus der Datenbank wieder her.
  3. Informieren Sie den/die Benutzer*in, dass der letzte Vorgang möglicherweise nicht erfolgreich abgeschlossen wurde.

Option 3: Hinzufügen einer Zustandsüberprüfung

Für die meisten Vorgänge, die den Datenbankzustand ändern, kann Code hinzugefügt werden, der überprüft, ob der Vorgang erfolgreich war. EF bietet eine Erweiterungsmethode, um dies zu vereinfachen: IExecutionStrategy.ExecuteInTransaction.

Diese Methode führt zunächst einen Commit für eine Transaktion durch und akzeptiert außerdem eine Funktion im verifySucceeded-Parameter, die aufgerufen wird, wenn während des Transaktionscommits ein vorübergehender Fehler auftritt.


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

Hinweis

Hier wird SaveChanges aufgerufen, wobei acceptAllChangesOnSuccess auf false festgelegt ist, um zu vermeiden, dass der Zustand der Blog-Entität in Unchanged geändert wird, wenn SaveChanges erfolgreich ist. Dadurch kann derselbe Vorgang erneut ausgeführt werden, wenn beim Commit ein Fehler auftritt und die Transaktion zurückgesetzt wird.

Option 4: manuelles Nachverfolgen der Transaktion

Wenn Sie im Speicher generierte Schlüssel verwenden oder eine generische Methode zum Behandeln von Commitfehlern benötigen, die unabhängig vom Vorgang ist, können Sie jeder Transaktion eine ID zuweisen, die überprüft wird, wenn beim Commit ein Fehler auftritt.

  1. Fügen Sie der Datenbank eine Tabelle hinzu, die zum Nachverfolgen des Status der Transaktionen verwendet wird.
  2. Fügen Sie am Anfang jeder Transaktion eine Zeile in die Tabelle ein.
  3. Wenn die Verbindung während des Commits fehlschlägt, überprüfen Sie, ob die entsprechende Zeile in der Datenbank vorhanden ist.
  4. Wenn der Commit erfolgreich ist, löschen Sie die entsprechende Zeile, um das Wachstum der Tabelle zu vermeiden.

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

Hinweis

Stellen Sie sicher, dass der für den Kontext für die Überprüfung eine Ausführungsstrategie definiert ist, da bei der Verbindung während der Überprüfung wahrscheinlich erneut ein Fehler auftritt, wenn dies während des Transaktionscommits geschehen ist.

Zusätzliche Ressourcen