Speichern von Daten

Während Sie beim Abfragen Daten aus der Datenbank lesen, bedeutet das Speichern von Daten, der Datenbank neue Entitäten hinzuzufügen, Entitäten zu entfernen oder die Eigenschaften vorhandener Entitäten in bestimmter Weise zu ändern. Entity Framework Core (EF Core) unterstützt zwei grundlegende Ansätze zum Speichern von Daten in der Datenbank.

Ansatz 1: Änderungsnachverfolgung und SaveChanges

In vielen Szenarien muss Ihr Programm einige Daten aus der Datenbank abfragen, einige Änderungen daran vornehmen und diese Änderungen zurückspeichern. Dies wird manchmal als „Arbeitseinheit“ bezeichnet. Angenommen, Sie verfügen über eine Reihe von Blogs, und Sie möchten die Url-Eigenschaft eines dieser Blogs ändern. In EF erfolgt dies in der Regel wie folgt:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Single(b => b.Url == "http://example.com");
    blog.Url = "http://example.com/blog";
    context.SaveChanges();
}

Der obige Code führt die folgenden Schritte aus:

  1. Er lädt mit einer regulären LINQ-Abfrage eine Entität aus der Datenbank (siehe Abfragedaten). Die Abfragen von EF werden standardmäßig nachverfolgt, was bedeutet, dass EF die geladenen Entitäten in seiner internen Änderungsnachverfolgung nachverfolgt.
  2. Die geladene Entitäteninstanz wird wie gewohnt durch Zuweisen einer .NET-Eigenschaft bearbeitet. EF ist an diesem Schritt nicht beteiligt.
  3. Schließlich wird DbContext.SaveChanges() aufgerufen. An diesem Punkt erkennt EF automatisch alle Änderungen, indem die Entitäten mit einer Momentaufnahme des Zeitpunkts des Ladens verglichen werden. Alle erkannten Änderungen werden in der Datenbank beibehalten. Bei Verwendung einer relationalen Datenbank ist dies in der Regel mit dem Senden eines SQL-UPDATE verbunden, um die relevanten Zeilen zu aktualisieren.

Beachten Sie, dass oben ein typischer Aktualisierungsvorgang für vorhandene Daten beschrieben wird, aber ähnliche Prinzipien gelten für das Hinzufügen und Entfernen von Entitäten. Sie interagieren mit der Änderungsnachverfolgung von EF, indem Sie DbSet<TEntity>.Add und Remove aufrufen, wodurch die Änderungen nachverfolgt werden. EF wendet dann alle nachverfolgten Änderungen auf die Datenbank an, wenn SaveChanges() aufgerufen wird (bei Verwendung einer relationalen Datenbank z. B. über SQL INSERT und DELETE ).

SaveChanges() bietet die folgenden Vorteile:

  • Sie müssen keinen Code schreiben, um nachzuverfolgen, welche Entitäten und Eigenschaften geändert wurden. EF führt dies automatisch für Sie durch und aktualisiert nur diese Eigenschaften in der Datenbank, um die Leistung zu verbessern. Stellen Sie sich vor, Ihre geladenen Entitäten sind an eine Benutzeroberflächenkomponente gebunden, sodass Benutzer*innen beliebige Eigenschaften ändern können. EF befreit Sie davon, herauszufinden zu müssen, welche Entitäten und Eigenschaften tatsächlich geändert wurden.
  • Das Speichern von Änderungen an der Datenbank kann manchmal kompliziert sein! Wenn Sie beispielsweise einen Blog und einige Beiträge für diesen Blog hinzufügen möchten, müssen Sie möglicherweise den datenbankgenerierten Schlüssel für den eingefügten Blog abrufen, bevor Sie die Beiträge einfügen können (da sie auf den Blog verweisen müssen). EF übernimmt all dies für Sie und eliminiert die Komplexität.
  • EF kann Parallelitätsprobleme erkennen, z. B. wenn eine Datenbankzeile zwischen Ihrer Abfrage und SaveChanges() von einer anderen Person geändert wurde. Weitere Informationen finden Sie unter Parallelitätskonflikte.
  • Bei Datenbanken, die dies unterstützen, umschließt SaveChanges() automatisch mehrere Änderungen in einer Transaktion, um sicherzustellen, dass Ihre Daten konsistent bleiben, wenn ein Fehler auftritt. Weitere Informationen finden Sie unter Transaktionen.
  • SaveChanges() stellt außerdem in vielen Fällen mehrere Änderungen zu einem Stapel zusammen, wodurch die Anzahl der Datenbankroundtrips erheblich reduziert und die Leistung erheblich verbessert wird. Weitere Informationen finden Sie unter Effiziente Aktualisierung.

Weitere Informationen und Codebeispiele zur grundlegenden Verwendung von SaveChanges() finden Sie unter Grundlegendes zu SaveChanges. Weitere Informationen zur Änderungsnachverfolgung von EF finden Sie in der Übersicht zur Änderungsnachverfolgung.

Ansatz 2: ExecuteUpdate und ExecuteDelete („Massenupdate“)

Hinweis

Dieses Feature wurde in EF Core 7.0 eingeführt.

Die Änderungsnachverfolgung und SaveChanges() sind zwar leistungsfähige Möglichkeiten zum Speichern von Änderungen, haben jedoch einige Nachteile.

Zunächst setzt SaveChanges() voraus, dass Sie alle Entitäten abfragen und nachverfolgen, die Sie ändern oder löschen möchten. Wenn Sie beispielsweise alle Blogs löschen müssen, deren Bewertung unter einem bestimmten Schwellenwert liegt, müssen Sie eine potenziell große Anzahl von Zeilen abfragen, materialisieren und nachverfolgen, und für jede einzelne davon von SaveChanges() eine DELETE-Anweisung generieren lassen. Relationale Datenbanken bieten eine weitaus effizientere Alternative: Ein einzelner DELETE-Befehl kann gesendet werden, der angibt, welche Zeilen über eine WHERE-Klausel gelöscht werden sollen, aber das SaveChanges()-Modell lässt die Generierung nicht zu.

Um dieses „Massenupdate“-Szenario zu unterstützen, können Sie ExecuteDelete wie folgt verwenden:

context.Blogs.Where(b => b.Rating < 3).ExecuteDelete();

So können Sie eine SQL-DELETE-Anweisung ähnlich einer regulären LINQ-Abfrage über reguläre LINQ-Operatoren ausdrücken, wodurch die folgende SQL-Instanz für die Datenbank ausgeführt wird:

DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Dies wird in der Datenbank sehr effizient ausgeführt, ohne Daten aus der Datenbank zu laden oder die Änderungsnachverfolgung von EF einzubeziehen. Auf ähnliche Weise können Sie mit ExecuteUpdate eine SQL-UPDATE-Anweisung ausdrücken.

Auch wenn Sie Entitäten nicht massenweise ändern, wissen Sie möglicherweise genau, welche Eigenschaften der Entität Sie ändern möchten. Die Verwendung der Änderungsnachverfolgungs-API zum Ausführen der Änderung kann übermäßig komplex sein und das Erstellen einer Entitätsinstanz, die Nachverfolgung über Attach, das Vornehmen Ihrer Änderungen und schließlich das Aufrufen von SaveChanges() erfordern. Für solche Szenarien können ExecuteUpdate und ExecuteDelete eine wesentlich einfachere Möglichkeit sein, denselben Vorgang auszudrücken.

Schließlich erzwingt sowohl die Änderungsnachverfolgung als auch SaveChanges() selbst einen bestimmten Laufzeitaufwand. Wenn Sie eine Hochleistungsanwendung schreiben, können Sie mit ExecuteUpdate und ExecuteDelete beide Komponenten vermeiden und die gewünschte Anweisung effizient generieren.

Beachten Sie jedoch, dass ExecuteUpdate und ExecuteDelete auch bestimmte Einschränkungen haben:

  • Diese Methoden werden sofort ausgeführt und können derzeit nicht mit anderen Vorgängen zu einem Stapel zusammengefasst werden. Andererseits kann SaveChanges() mehrere Vorgänge zu einem Stapel zusammenfassen.
  • Da die Änderungsnachverfolgung nicht betroffen ist, müssen Sie genau wissen, welche Entitäten und Eigenschaften geändert werden müssen. Dies kann eine manuellere, Low-Level-Codenachverfolgung zur Folge haben, um festzustellen, was geändert werden muss und was nicht.
  • Da die Änderungsnachverfolgung nicht betroffen ist, wenden diese Methoden außerdem nicht automatisch die Parallelitätssteuerung an, wenn Änderungen beibehalten werden. Sie können jedoch weiterhin explizit eine Where-Klausel hinzufügen, um die Parallelitätssteuerung selbst zu implementieren.
  • Derzeit wird nur das Aktualisieren und Löschen unterstützt. Das Einfügen muss über DbSet<TEntity>.Add und SaveChanges() erfolgen.

Weitere Informationen und Codebeispiele finden Sie unter ExecuteUpdate und ExecuteDelete.

Zusammenfassung

Die folgenden Richtlinien zeigen, wann welcher Ansatz verwendet werden soll. Beachten Sie, dass dies keine absoluten Regeln sind, sondern eine nützliche Faustregel sein sollen:

  • Wenn Sie nicht im voraus wissen, welche Änderungen vorgenommen werden, verwenden Sie SaveChanges. Damit wird automatisch erkannt, welche Änderungen angewendet werden müssen. Beispielszenarien:
    • „Ich möchte einen Blog aus der Datenbank laden und ein Formular anzeigen, das Benutzer*innen ermöglicht, ihn zu ändern“
  • Wenn Sie ein Diagramm von Objekten (d. h. mehrere miteinander verbundene Objekte) bearbeiten müssen, verwenden Sie SaveChanges. Damit wird ermittelt, in welcher Reihenfolge die Änderungen ausgeführt werden sollen, und wie alles miteinander verknüpft werden kann.
    • „Ich möchte einen Blog aktualisieren, einige seiner Beiträge ändern und andere löschen“
  • Wenn Sie eine potenziell große Anzahl von Entitäten auf einem bestimmten basierend Kriterium ändern möchten, verwenden Sie ExecuteUpdate und ExecuteDelete. Beispielszenarien:
    • „Ich möchte allen Mitarbeitern eine Gehaltserhöhung geben“
    • „Ich möchte alle Blogs löschen, deren Name mit X beginnt“
  • Wenn Sie bereits genau wissen, welche Entitäten Sie ändern und wie Sie sie ändern möchten, verwenden Sie ExecuteUpdate und ExecuteDelete. Beispielszenarien:
    • „Ich möchte den Blog löschen, dessen Name ‚Foo‘ lautet“
    • „Ich möchte den Namen des Blogs mit Id 5 in ‚Bar‘ ändern“