Änderungsnachverfolgung in EF CoreChange Tracking in EF Core

Jede DbContext-Instanz verfolgt Änderungen nach, die an Entitäten vorgenommen wurden.Each DbContext instance tracks changes made to entities. Diese nachverfolgten Entitäten bestimmen wiederum die Änderungen an der Datenbank, wenn SaveChanges aufgerufen wird.These tracked entities in turn drive the changes to the database when SaveChanges is called.

Dieses Dokument enthält eine Übersicht über die EF Core-Änderungsnachverfolgung (Entity Framework Core) und deren Beziehung zu Abfragen und Updates.This document presents an overview of Entity Framework Core (EF Core) change tracking and how it relates to queries and updates.

Tipp

Sie können den gesamten Code in diesem Dokument ausführen und debuggen, indem Sie den Beispielcode von GitHub herunterladen.You can run and debug into all the code in this document by downloading the sample code from GitHub.

Tipp

Der Einfachheit halber werden in diesem Dokument synchrone Methoden wie z. B. SaveChanges anstatt ihrer asynchronen Entsprechungen wie SaveChangesAsync verwendet und darauf verwiesen.For simplicity, this document uses and references synchronous methods such as SaveChanges rather their async equivalents such as SaveChangesAsync. Das Aufrufen und Warten auf die asynchrone Methode kann ersetzt werden, sofern nicht anders angegeben.Calling and awaiting the async method can be substituted unless otherwise noted.

Vorgehensweise: Nachverfolgen von EntitätenHow to track entities

Entitätsinstanzen werden nachverfolgt, wenn auf sie Folgendes zutrifft:Entity instances become tracked when they are:

  • Zurückgegeben von einer Abfrage der DatenbankReturned from a query executed against the database
  • Explizit durch Add, Attach, Update oder ähnliche Methoden DbContext angefügtExplicitly attached to the DbContext by Add, Attach, Update, or similar methods
  • Erkannt als neue, mit vorhandenen nachverfolgten Entitäten verbundene EntitätenDetected as new entities connected to existing tracked entities

Entitätsinstanzen werden in folgenden Fällen nicht mehr nachverfolgt:Entity instances are no longer tracked when:

  • DbContext wurde verworfenThe DbContext is disposed
  • Die Änderungsnachverfolgung ist deaktiviert (EF Core 5.0 und höher)The change tracker is cleared (EF Core 5.0 and later)
  • Die Entitäten sind explizit getrenntThe entities are explicitly detached

DbContext ist dazu konzipiert, eine kurzlebige Arbeitseinheit darzustellen, wie in DbContext-Lebensdauer, -Konfiguration und -Initialisierung beschrieben.DbContext is designed to represent a short-lived unit-of-work, as described in DbContext Initialization and Configuration. Dies bedeutet, dass das Verwerfen von DbContext die normale Art und Weise ist, die Nachverfolgung von Entitäten zu beenden.This means that disposing the DbContext is the normal way to stop tracking entities. Anders ausgedrückt, die Lebensdauer einer DbContext-Instanz sollte so aussehen:In other words, the lifetime of a DbContext should be:

  1. Erstellen der DbContext-InstanzCreate the DbContext instance
  2. Nachverfolgen einiger EntitätenTrack some entities
  3. Vornehmen von Änderungen an den EntitätenMake some changes to the entities
  4. Aufrufen von SaveChanges zum Aktualisieren der DatenbankCall SaveChanges to update the database
  5. Verwerfen der DbContext-InstanzDispose the DbContext instance

Tipp

Es ist nicht erforderlich, die Änderungsnachverfolgung zu löschen oder Entitätsinstanzen explizit zu trennen, wenn Sie derart vorgehen.It is not necessary to clear the change tracker or explicitly detach entity instances when taking this approach. Wenn Sie jedoch Entitäten trennen müssen, ist das Aufrufen von ChangeTracker.Clear effizienter, als Entitäten einzeln zu trennen.However, if you do need to detach entities, then calling ChangeTracker.Clear is more efficient than detaching entities one-by-one.

Status von EntitätenEntity states

Jede Entität ist einem bestimmten EntityState-Wert zugeordnet:Every entity is associated with a given EntityState:

  • Detached Entitäten werden von DbContext nicht nachverfolgt.Detached entities are not being tracked by the DbContext.
  • Added Entitäten sind neu und wurden noch nicht in die Datenbank eingefügt.Added entities are new and have not yet been inserted into the database. Dies bedeutet, dass sie eingefügt werden, wenn SaveChanges aufgerufen wird.This means they will be inserted when SaveChanges is called.
  • Unchanged Entitäten wurden nicht geändert, seit sie von der Datenbank abgefragt wurden.Unchanged entities have not been changed since they were queried from the database. Alle von Abfragen zurückgegebenen Entitäten sind anfänglich in diesem Zustand.All entities returned from queries are initially in this state.
  • Modified Entitäten wurden geändert, seit sie von der Datenbank abgefragt wurden.Modified entities have been changed since they were queried from the database. Dies bedeutet, dass sie aktualisiert werden, wenn SaveChanges aufgerufen wird.This means they will be updated when SaveChanges is called.
  • Deleted Entitäten sind in der Datenbank vorhanden, werden jedoch als gelöscht markiert, wenn SaveChanges aufgerufen wird.Deleted entities exist in the database, but are marked to be deleted when SaveChanges is called.

EF Core verfolgt Änderungen auf der Eigenschaftenebene nach.EF Core tracks changes at the property level. Wenn z. B. nur ein einzelner Eigenschaftswert geändert wird, ändert ein Datenbankupdate nur diesen Wert.For example, if only a single property value is modified, then a database update will change only that value. Eigenschaften können jedoch nur als geändert gekennzeichnet werden, wenn sich die Entität selbst im Zustand „Geändert“ befindet.However, properties can only be marked as modified when the entity itself is in the Modified state. (Aus alternativer Sicht bedeutet der Zustand „Geändert“, dass mindestens ein Eigenschaftswert als geändert markiert wurde.)(Or, from an alternate perspective, the Modified state means that at least one property value has been marked as modified.)

In der folgenden Tabelle sind die verschiedenen Status zusammengefasst:The following table summarizes the different states:

EntitätszustandEntity state Nachverfolgt von DbContextTracked by DbContext In Datenbank vorhandenExists in database Geänderte EigenschaftenProperties modified Aktion bei SaveChangesAction on SaveChanges
Detached NeinNo - - -
Added JaYes NeinNo - EinfügenInsert
Unchanged JaYes JaYes NeinNo -
Modified JaYes JaYes JaYes AktualisierenUpdate
Deleted JaYes JaYes - LöschenDelete

Hinweis

In diesem Text werden zur Verdeutlichung Begriffe für relationale Datenbanken verwendet.This text uses relational database terms for clarity. NoSQL-Datenbanken unterstützen in der Regel ähnliche Vorgänge, aber möglicherweise mit anderen Namen.NoSQL databases typically support similar operations but possibly with different names. Nähere Informationen dazu finden Sie in der Dokumentation Ihres Datenbankanbieters.Consult your database provider documentation for more information.

Nachverfolgung von AbfragenTracking from queries

EF Core-Änderungsnachverfolgung funktioniert am besten, wenn dieselbe DbContext-Instanz verwendet wird, um Entitäten abzufragen und sie durch Aufrufen von SaveChanges zu aktualisieren.EF Core change tracking works best when the same DbContext instance is used to both query for entities and update them by calling SaveChanges. Der Grund hierfür ist, dass EF Core den Status von abgefragten Entitäten automatisch nachverfolgt und dann alle Änderungen erkennt, die an diesen Entitäten vorgenommen werden, wenn SaveChanges aufgerufen wird.This is because EF Core automatically tracks the state of queried entities and then detects any changes made to these entities when SaveChanges is called.

Dieser Ansatz bietet mehrere Vorteile gegenüber der expliziten Nachverfolgung von Entitätsinstanzen:This approach has several advantages over explicitly tracking entity instances:

  • Er ist einfach.It is simple. Entitätszustände müssen selten explizit bearbeitet werden, weil EF Core die Zustandsänderungen übernimmt.Entity states rarely need to be manipulated explicitly--EF Core takes care of state changes.
  • Updates sind auf die Werte beschränkt, die tatsächlich geändert wurden.Updates are limited to only those values that have actually changed.
  • Die Werte von Schatteneigenschaften werden beibehalten und bei Bedarf verwendet.The values of shadow properties are preserved and used as needed. Dies ist besonders relevant, wenn Fremdschlüssel im Schattenzustand gespeichert werden.This is especially relevant when foreign keys are stored in shadow state.
  • Die ursprünglichen Eigenschaftswerte werden automatisch beibehalten und für effiziente Updates verwendet.The original values of properties are preserved automatically and used for efficient updates.

Einfache Abfrage und AktualisierungSimple query and update

Stellen Sie sich beispielsweise ein einfaches Blog/Beitrag-Modell vor:For example, consider a simple blog/posts model:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IList<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int? BlogId { get; set; }
    public Blog Blog { get; set; }
}

Wir können dieses Modell verwenden, um Blogs und Beiträge abzufragen und dann einige Updates der Datenbank vorzunehmen:We can use this model to query for blogs and posts and then make some updates to the database:

using var context = new BlogsContext();

var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");

blog.Name = ".NET Blog (Updated!)";

foreach (var post in blog.Posts.Where(e => !e.Title.Contains("5.0")))
{
    post.Title = post.Title.Replace("5", "5.0");
}

context.SaveChanges();

Das Aufrufen von SaveChanges führt zu den folgenden Datenbankupdates, wobei SQLite als Beispieldatenbank verwendet wird:Calling SaveChanges results in the following database updates, using SQLite as an example database:

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p1='2' (DbType = String), @p0='Announcing F# 5.0' (Size = 17)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "Title" = @p0
WHERE "Id" = @p1;
SELECT changes();

Die Debugansicht der Änderungsnachverfolgung ist hervorragend geeignet, um zu visualisieren, welche Entitäten nachverfolgt werden und wie ihre Zustände sind.The change tracker debug view is a great way visualize which entities are being tracked and what their states are. Fügen Sie beispielsweise den folgenden Code in das obige Beispiel ein, bevor Sie SaveChanges aufrufen:For example, inserting the following code into the sample above before calling SaveChanges:

context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Generiert folgende Ausgabe:Generates the following output:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}, {Id: 3}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Modified
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'
  Blog: {Id: 1}

Beachten Sie insbesondere:Notice specifically:

  • Die Blog.Name-Eigenschaft wird als geändert markiert (Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'), und dies führt dazu, dass sich der Blog im Modified-Zustand befindet.The Blog.Name property is marked as modified (Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'), and this results in the blog being in the Modified state.
  • Die Post.Title-Eigenschaft von Beitrag 2 wird als geändert markiert (Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'), und dies führt dazu, dass sich der Beitrag im Modified-Zustand befindet.The Post.Title property of post 2 is marked as modified (Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'), and this results in this post being in the Modified state.
  • Die anderen Eigenschaftswerte von Beitrag 2 wurden nicht geändert und sind daher nicht als geändert gekennzeichnet.The other property values of post 2 have not changed and are therefore not marked as modified. Aus diesem Grund sind diese Werte nicht im Datenbankupdate enthalten.This is why these values are not included in the database update.
  • Der andere Beitrag wurde in keiner Weise geändert.The other post was not modified in any way. Der Grund dafür ist, dass er sich noch im Unchanged-Zustand befindet und nicht im Datenbankupdate enthalten ist.This is why it is still in the Unchanged state and is not included in the database update.

Abfragen und dann einfügen, aktualisieren und löschenQuery then insert, update, and delete

Updates wie im vorherigen Beispiel können mit Einfügungen und Löschungen in derselben Arbeitseinheit kombiniert werden.Updates like those in the previous example can be combined with inserts and deletes in the same unit-of-work. Beispiel:For example:

using var context = new BlogsContext();

var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");

// Modify property values
blog.Name = ".NET Blog (Updated!)";

// Insert a new Post
blog.Posts.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
    });

// Mark an existing Post as Deleted
var postToDelete = blog.Posts.Single(e => e.Title == "Announcing F# 5");
context.Remove(postToDelete);

context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

context.SaveChanges();

In diesem Beispiel:In this example:

  • Ein Blog und zugehörige Beiträge werden von der Datenbank abgefragt und nachverfolgt.A blog and related posts are queried from the database and tracked
  • Die Blog.Name-Eigenschaft wird geändert.The Blog.Name property is changed
  • Der Sammlung vorhandener Beiträge für den Blog wird ein neuer Beitrag hinzugefügt.A new post is added to the collection of existing posts for the blog
  • Ein vorhandener Beitrag ist zum Löschen durch Aufrufen von DbContext.Remove markiert.An existing post is marked for deletion by calling DbContext.Remove

Schauen Sie sich die Debugansicht der Änderungsnachverfolgung erneut an, bevor Sie SaveChanges aufrufen, um zu sehen, wie EF Core diese Änderungen nachverfolgt:Looking again at the change tracker debug view before calling SaveChanges shows how EF Core is tracking these changes:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}, {Id: 3}, {Id: -2147482638}]
Post {Id: -2147482638} Added
  Id: -2147482638 PK Temporary
  BlogId: 1 FK
  Content: '.NET 5.0 was released recently and has come with many...'
  Title: 'What's next for System.Text.Json?'
  Blog: {Id: 1}
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Deleted
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Beachten Sie Folgendes:Notice that:

  • Der Blog ist als Modified markiert.The blog is marked as Modified. Dadurch wird ein Datenbankupdate generiert.This will generate a database update.
  • Beitrag 2 wird als Deleted gekennzeichnet.Post 2 is marked as Deleted. Dadurch wird eine Datenbanklöschung generiert.This will generate a database delete.
  • Ein neuer Beitrag mit einer temporären ID ist mit Blog 1 verknüpft und wird als Added gekennzeichnet.A new post with a temporary ID is associated with blog 1 and is marked as Added. Dadurch wird eine Datenbankeinfügung generiert.This will generate a database insert.

Dies führt zu den folgenden Datenbankbefehlen (bei Verwendung von SQLite), wenn SaveChanges aufgerufen wird:This results in the following database commands (using SQLite) when SaveChanges is called:

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 was released recently and has come with many...' (Size = 56), @p2='What's next for System.Text.Json?' (Size = 33)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

Weitere Informationen zum Einfügen und Löschen von Entitäten finden Sie unter Explizites Nachverfolgen von Entitäten.See Explicitly Tracking Entities for more information on inserting and deleting entities. Weitere Informationen zur automatischen Erkennung von Änderungen wie dieser durch EF Core finden Sie unter Änderungserkennung und Benachrichtigungen.See Change Detection and Notifications for more information on how EF Core automatically detects changes like this.

Tipp

Rufen Sie ChangeTracker.HasChanges() auf, um zu bestimmen, ob Änderungen vorgenommen wurden, die SaveChanges veranlassen, Datenbankupdates vorzunehmen.Call ChangeTracker.HasChanges() to determine whether any changes have been made that will cause SaveChanges to make updates to the database. Wenn HasChanges „false“ zurückgibt, wird SaveChanges nicht ausgeführt.If HasChanges return false, then SaveChanges will be a no-op.