Freigeben über


Zugreifen auf nachverfolgte Entitäten

Es gibt vier Haupt-APIs zum Zugriff auf Entitäten, die von einem DbContext verwaltet werden.

Jede dieser Elemente wird in den folgenden Abschnitten ausführlicher beschrieben.

Tipp

In diesem Dokument wird davon ausgegangen, dass Entitätszustände und die Grundlagen der EF Core-Änderungsnachverfolgung verstanden werden. Weitere Informationen zu diesen Themen finden Sie unter Change Tracking in EF Core .

Tipp

Sie können den gesamten Code in diesem Dokument ausführen und debuggen, indem Sie den Beispielcode von GitHub herunterladen.

Verwendung von DbContext.Entry- und EntityEntry-Instanzen

Für jede nachverfolgte Entität verfolgt Entity Framework Core (EF Core) Folgendes:

  • Der Gesamtstatus der Entität. Dies ist eine von Unchanged, Modified, , Addedoder Deleted; siehe Änderungsnachverfolgung in EF Core für weitere Informationen.
  • Die Beziehungen zwischen nachverfolgten Entitäten. Beispielsweise der Blog, zu dem ein Beitrag gehört.
  • Die "aktuellen Werte" von Eigenschaften.
  • Die "Originalwerte" von Eigenschaften, wenn diese Informationen verfügbar sind. Ursprüngliche Werte sind die Eigenschaftswerte, die vorhanden waren, wenn die Entität aus der Datenbank abgefragt wurde.
  • Welche Eigenschaftswerte seit der Abfrage geändert wurden.
  • Andere Informationen zu Eigenschaftswerten, z. B. ob der Wert temporär ist oder nicht.

Das Übergeben einer Entitätsinstanz an DbContext.Entry führt zu einem EntityEntry<TEntity>, das den Zugriff auf diese Informationen für die gegebene Entität ermöglicht. Beispiel:

using var context = new BlogsContext();

var blog = await context.Blogs.SingleAsync(e => e.Id == 1);
var entityEntry = context.Entry(blog);

In den folgenden Abschnitten wird gezeigt, wie Sie mithilfe eines EntityEntry-Elements auf den Entitätsstatus und den Status der Eigenschaften und Navigationen der Entität zugreifen und diesen bearbeiten.

Arbeiten mit der Entität

Die häufigste Verwendung EntityEntry<TEntity> besteht darin, auf die aktuelle EntityState Entität zuzugreifen. Beispiel:

var currentState = context.Entry(blog).State;
if (currentState == EntityState.Unchanged)
{
    context.Entry(blog).State = EntityState.Modified;
}

Die Entry-Methode kann auch für Entitäten verwendet werden, die noch nicht nachverfolgt wurden. Dies beginnt nicht mit der Nachverfolgung der Entität; der Zustand der Entität bleibt unverändert Detached. Der zurückgegebene EntityEntry kann dann verwendet werden, um den Entitätsstatus zu ändern, wobei die Entität dann im angegebenen Zustand nachverfolgt wird. Der folgende Code beginnt z. B. mit der Nachverfolgung einer Bloginstanz als Added:

var newBlog = new Blog();
Debug.Assert(context.Entry(newBlog).State == EntityState.Detached);

context.Entry(newBlog).State = EntityState.Added;
Debug.Assert(context.Entry(newBlog).State == EntityState.Added);

Tipp

Im Gegensatz zu EF6 bewirkt das Festlegen des Status einer einzelnen Entität nicht, dass alle verbundenen Entitäten nachverfolgt werden. Dadurch wird das Festlegen des Zustands auf diese Weise zu einem Vorgang auf niedrigerer Ebene als das Aufrufen von Add, Attach oder Update, die auf einem ganzen Diagramm von Entitäten ausgeführt werden.

In der folgenden Tabelle werden Die Möglichkeiten für die Verwendung eines EntityEntry-Elements zum Arbeiten mit einer gesamten Entität zusammengefasst:

EntityEntry-Mitglied BESCHREIBUNG
EntityEntry.State Ruft die Eigenschaft EntityState der Entität ab und legt sie fest.
EntityEntry.Entity Ruft die Entitätsinstanz ab.
EntityEntry.Context Das DbContext verfolgt diese Entität.
EntityEntry.Metadata IEntityType Metadaten für den Entitätstyp.
EntityEntry.IsKeySet Gibt an, ob die Entität ihren Schlüsselwert festgelegt hat.
EntityEntry.Reload() Überschreibt Eigenschaftswerte mit Werten, die aus der Datenbank gelesen werden.
EntityEntry.DetectChanges() Erzwingt nur die Erkennung von Änderungen für diese Entität; siehe Änderungserkennung und Benachrichtigungen.

Arbeiten mit einer einzelnen Eigenschaft

Mehrere Überladungen von EntityEntry<TEntity>.Property ermöglichen den Zugriff auf Informationen über ein einzelnes Merkmal einer Entität. Verwenden Sie z. B. eine stark typierte, fluent-ähnliche API:

PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property(e => e.Name);

Der Eigenschaftsname kann stattdessen als Zeichenfolge übergeben werden. Beispiel:

PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property<string>("Name");

Die zurückgegebene PropertyEntry<TEntity,TProperty> kann dann verwendet werden, um Informationen über die Eigenschaft abzurufen. Sie kann beispielsweise verwendet werden, um den aktuellen Wert der Eigenschaft für diese Entität abzurufen und festzulegen:

string currentValue = context.Entry(blog).Property(e => e.Name).CurrentValue;
context.Entry(blog).Property(e => e.Name).CurrentValue = "1unicorn2";

Beide oben verwendeten Property-Methoden geben eine stark typierte generische PropertyEntry<TEntity,TProperty> Instanz zurück. Die Verwendung dieses generischen Typs wird bevorzugt, da der Zugriff auf Eigenschaftswerte ohne Boxwerttypen ermöglicht wird. Wenn der Typ der Entität oder Eigenschaft zur Kompilierungszeit jedoch nicht bekannt ist, kann stattdessen ein nicht generischer PropertyEntry Wert abgerufen werden:

PropertyEntry propertyEntry = context.Entry(blog).Property("Name");

Dies ermöglicht den Zugriff auf Eigenschaftsinformationen für jede Eigenschaft unabhängig vom Typ auf Kosten von Boxwerttypen. Beispiel:

object blog = await context.Blogs.SingleAsync(e => e.Id == 1);

object currentValue = context.Entry(blog).Property("Name").CurrentValue;
context.Entry(blog).Property("Name").CurrentValue = "1unicorn2";

In der folgenden Tabelle sind Eigenschafteninformationen zusammengefasst, die von PropertyEntry verfügbar gemacht werden:

PropertyEntry-Mitglied BESCHREIBUNG
PropertyEntry<TEntity,TProperty>.CurrentValue Dient zum Abrufen und Festlegen des aktuellen Werts der Eigenschaft.
PropertyEntry<TEntity,TProperty>.OriginalValue Dient zum Abrufen und Festlegen des ursprünglichen Werts der Eigenschaft, falls verfügbar.
PropertyEntry<TEntity,TProperty>.EntityEntry Ein Rückverweis auf die EntityEntry<TEntity>-Entität.
PropertyEntry.Metadata IProperty Metadaten für die Eigenschaft.
PropertyEntry.IsModified Gibt an, ob diese Eigenschaft als geändert markiert ist und dass dieser Zustand geändert werden kann.
PropertyEntry.IsTemporary Gibt an, ob diese Eigenschaft als temporär markiert ist und dass dieser Zustand geändert werden kann.

Hinweise:

  • Der ursprüngliche Wert einer Eigenschaft ist der Wert, den die Eigenschaft hatte, als die Entität aus der Datenbank abgefragt wurde. Ursprüngliche Werte sind jedoch nicht verfügbar, wenn die Entität getrennt und dann explizit an einen anderen DbContext angefügt wurde, z. B. mit Attach oder Update. In diesem Fall entspricht der zurückgegebene ursprüngliche Wert dem aktuellen Wert.
  • SaveChanges aktualisiert nur eigenschaften, die als geändert markiert sind. Legen Sie IsModified "true" fest, um zu erzwingen, dass EF Core einen bestimmten Eigenschaftswert aktualisiert, oder legen Sie ihn auf "false" fest, um zu verhindern, dass EF Core den Eigenschaftswert aktualisiert.
  • Temporäre Werte werden in der Regel von EF Core-Wertgeneratoren generiert. Durch Festlegen des aktuellen Werts einer Eigenschaft wird der temporäre Wert durch den angegebenen Wert ersetzt und die Eigenschaft als nicht temporär markiert. Legen Sie IsTemporary den Wert auf "true" fest, um zu erzwingen, dass ein Wert auch nach dem expliziten Festlegen vorübergehend ist.

Arbeiten mit einer einfachen Navigation

Mehrere Überladungen von EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collectionund EntityEntry.Navigation ermöglichen den Zugriff auf Informationen über eine einzelne Navigation.

Auf Referenznavigationen einer einzelnen verwandten Entität greift man über die Reference-Methoden zu. Navigationselemente verweisen auf die "1"-Seiten von 1:n-Beziehungen und auf beide Seiten von 1:1-Beziehungen. Beispiel:

ReferenceEntry<Post, Blog> referenceEntry1 = context.Entry(post).Reference(e => e.Blog);
ReferenceEntry<Post, Blog> referenceEntry2 = context.Entry(post).Reference<Blog>("Blog");
ReferenceEntry referenceEntry3 = context.Entry(post).Reference("Blog");

Navigationen können auch Sammlungen verwandter Einheiten sein, wenn sie für die "vielen" Seiten von Eins-zu-vielen- und Viele-zu-vielen-Beziehungen verwendet werden. Die Collection Methoden werden für den Zugriff auf Navigationen von Sammlungen verwendet. Beispiel:

CollectionEntry<Blog, Post> collectionEntry1 = context.Entry(blog).Collection(e => e.Posts);
CollectionEntry<Blog, Post> collectionEntry2 = context.Entry(blog).Collection<Post>("Posts");
CollectionEntry collectionEntry3 = context.Entry(blog).Collection("Posts");

Einige Vorgänge sind für alle Navigationen üblich. Auf diese können sowohl für Referenz- als auch Sammlungsnavigationen mithilfe der EntityEntry.Navigation Methode zugegriffen werden. Beachten Sie, dass nur nicht-generischer Zugriff beim gleichzeitigen Zugriff auf alle Navigationen verfügbar ist. Beispiel:

NavigationEntry navigationEntry = context.Entry(blog).Navigation("Posts");

In der folgenden Tabelle sind die Möglichkeiten zur Verwendung von ReferenceEntry<TEntity,TProperty>, CollectionEntry<TEntity,TRelatedEntity> und NavigationEntry zusammengefasst:

NavigationEntry-Mitglied BESCHREIBUNG
MemberEntry.CurrentValue Dient zum Abrufen und Festlegen des aktuellen Werts der Navigation. Dies ist die gesamte Sammlung für Sammlungsnavigation.
NavigationEntry.Metadata INavigationBase Metadaten für die Navigation.
NavigationEntry.IsLoaded Dient zum Abrufen oder Festlegen eines Werts, der angibt, ob die zugehörige Entität oder Auflistung vollständig aus der Datenbank geladen wurde.
NavigationEntry.Load() Lädt die zugehörige Entität oder Sammlung aus der Datenbank; siehe Explizites Laden verwandter Daten.
NavigationEntry.Query() Die Abfrage, die EF Core verwenden würde, um diese Navigation als IQueryable zu laden, die weiter zusammengesetzt werden kann; siehe Explizites Laden verwandter Daten.

Arbeiten mit allen Eigenschaften einer Entität

EntityEntry.Properties gibt eine IEnumerable<T> der PropertyEntry für jede Eigenschaft der Entität zurück. Dies kann verwendet werden, um eine Aktion für jede Eigenschaft der Entität auszuführen. So legen Sie beispielsweise eine beliebige DateTime-Eigenschaft auf DateTime.Now fest.

foreach (var propertyEntry in context.Entry(blog).Properties)
{
    if (propertyEntry.Metadata.ClrType == typeof(DateTime))
    {
        propertyEntry.CurrentValue = DateTime.Now;
    }
}

Darüber hinaus enthält EntityEntry mehrere Methoden, um alle Eigenschaftswerte gleichzeitig abzurufen und festzulegen. Diese Methoden verwenden die PropertyValues Klasse, die eine Auflistung von Eigenschaften und deren Werten darstellt. PropertyValues können für aktuelle oder ursprüngliche Werte oder für die Werte abgerufen werden, die aktuell in der Datenbank gespeichert sind. Beispiel:

var currentValues = context.Entry(blog).CurrentValues;
var originalValues = context.Entry(blog).OriginalValues;
var databaseValues = await context.Entry(blog).GetDatabaseValuesAsync();

Diese PropertyValues-Objekte sind selbst nicht sehr nützlich. Sie können jedoch kombiniert werden, um gemeinsame Vorgänge auszuführen, die beim Bearbeiten von Entitäten erforderlich sind. Dies ist hilfreich beim Arbeiten mit Datenübertragungsobjekten und beim Auflösen optimistischer Parallelitätskonflikte. Die folgenden Abschnitte enthalten einige Beispiele.

Festlegen von aktuellen oder ursprünglichen Werten aus einer Entität oder einem Datenübertragungsobjekt (DTO)

Die aktuellen oder ursprünglichen Werte einer Entität können aktualisiert werden, indem Werte aus einem anderen Objekt kopiert werden. Betrachten Sie z. B. ein BlogDto DTO -Objekt (Data Transfer Object) mit denselben Eigenschaften wie der Entitätstyp:

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

Dies kann verwendet werden, um die aktuellen Werte einer überwachten Entität mithilfe von PropertyValues.SetValues festzulegen.

var blogDto = new BlogDto { Id = 1, Name = "1unicorn2" };

context.Entry(blog).CurrentValues.SetValues(blogDto);

Diese Technik wird manchmal verwendet, wenn eine Entität mit Werten aktualisiert wird, die von einem Dienstaufruf oder einem Client in einer n-Ebene-Anwendung abgerufen wurden. Beachten Sie, dass das verwendete Objekt nicht denselben Typ wie die Entität aufweisen muss, solange es Eigenschaften aufweist, deren Namen mit denen der Entität übereinstimmen. Im obigen Beispiel wird eine Instanz des DTO BlogDto verwendet, um die aktuellen Werte einer nachverfolgten Blog Entität festzulegen.

Beachten Sie, dass Eigenschaften nur dann als geändert markiert werden, wenn sich der Wertsatz vom aktuellen Wert unterscheidet.

Festlegen aktueller oder ursprünglicher Werte aus einem Wörterbuch

Im vorherigen Beispiel wurden Werte aus einer Entität oder DTO-Instanz übernommen. Das gleiche Verhalten ist verfügbar, wenn Eigenschaftswerte als Name/Wert-Paare in einem Wörterbuch gespeichert werden. Beispiel:

var blogDictionary = new Dictionary<string, object> { ["Id"] = 1, ["Name"] = "1unicorn2" };

context.Entry(blog).CurrentValues.SetValues(blogDictionary);

Festlegen aktueller oder ursprünglicher Werte aus der Datenbank

Die aktuellen oder ursprünglichen Werte einer Entität können mit den neuesten Werten aus der Datenbank aktualisiert werden, indem Sie GetDatabaseValues() oder GetDatabaseValuesAsync aufrufen und das zurückgegebene Objekt verwenden, um die aktuellen oder ursprünglichen Werte oder beides festzulegen. Beispiel:

var databaseValues = await context.Entry(blog).GetDatabaseValuesAsync();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);

Erstellen eines geklonten Objekts mit aktuellen, ursprünglichen oder Datenbankwerten

Das PropertyValues-Objekt, das von CurrentValues, OriginalValues oder GetDatabaseValues zurückgegeben wird, kann zum Erstellen eines Klons der Entität verwendet werden.PropertyValues.ToObject() Beispiel:

var clonedBlog = (await context.Entry(blog).GetDatabaseValuesAsync()).ToObject();

Beachten Sie, dass ToObject eine neue Instanz zurückgegeben wird, die nicht vom DbContext nachverfolgt wird. Das zurückgegebene Objekt verfügt auch nicht über Beziehungen, die auf andere Entitäten festgelegt sind.

Das geklonte Objekt kann nützlich sein, um Probleme im Zusammenhang mit gleichzeitigen Aktualisierungen der Datenbank zu beheben, insbesondere bei der Datenbindung an Objekte eines bestimmten Typs. Weitere Informationen finden Sie unter optimistischer Parallelität .

Arbeiten mit allen Navigationselementen einer Entität

EntityEntry.Navigations gibt eine IEnumerable<T> der NavigationEntry für jede Navigation der Entität zurück. EntityEntry.References und EntityEntry.Collections machen dasselbe, aber jeweils beschränkt auf Verweise oder Sammlungsnavigationen. Dies kann verwendet werden, um eine Aktion für jede Navigation der Entität auszuführen. Um beispielsweise das Laden aller verwandten Entitäten zu erzwingen:

foreach (var navigationEntry in context.Entry(blog).Navigations)
{
    navigationEntry.Load();
}

Arbeiten mit allen Mitgliedern einer Entität

Reguläre Eigenschaften und Navigationseigenschaften weisen einen anderen Zustand und ein anderes Verhalten auf. Es ist daher üblich, Navigationen und Nichtnavigationen separat zu verarbeiten, wie in den abschnitten oben gezeigt. Manchmal kann es jedoch hilfreich sein, etwas mit einem beliebigen Element der Entität zu tun, unabhängig davon, ob es sich um eine normale Eigenschaft oder Navigation handelt. EntityEntry.Member und EntityEntry.Members werden zu diesem Zweck bereitgestellt. Beispiel:

foreach (var memberEntry in context.Entry(blog).Members)
{
    Console.WriteLine(
        $"Member {memberEntry.Metadata.Name} is of type {memberEntry.Metadata.ClrType.ShortDisplayName()} and has value {memberEntry.CurrentValue}");
}

Wenn Sie diesen Code in einem Blog aus dem Beispiel ausführen, wird die folgende Ausgabe generiert:

Member Id is of type int and has value 1
Member Name is of type string and has value .NET Blog
Member Posts is of type IList<Post> and has value System.Collections.Generic.List`1[Post]

Tipp

Die Debugansicht der Änderungsverfolgung zeigt Informationen wie folgt an. Die Debugansicht für die gesamte Änderungsverfolgung wird aus den einzelnen EntityEntry.DebugView jeder nachverfolgten Entität generiert.

Suchen und Async-Suchen

DbContext.Find, DbContext.FindAsync, DbSet<TEntity>.Findund DbSet<TEntity>.FindAsync sind für eine effiziente Suche einer einzelnen Entität konzipiert, wenn ihr Primärschlüssel bekannt ist. Der Vorgang "Find" prüft zunächst, ob die Entität bereits verfolgt wird, und gibt, falls dies zutrifft, die Entität sofort zurück. Eine Datenbankabfrage wird nur durchgeführt, wenn die Entität nicht lokal nachverfolgt wird. Betrachten Sie beispielsweise diesen Code, der Find zweimal für dieselbe Entität aufruft:

using var context = new BlogsContext();

Console.WriteLine("First call to Find...");
var blog1 = await context.Blogs.FindAsync(1);

Console.WriteLine($"...found blog {blog1.Name}");

Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = await context.Blogs.FindAsync(1);
Debug.Assert(blog1 == blog2);

Console.WriteLine("...returned the same instance without executing a query.");

Die Ausgabe dieses Codes (einschließlich EF Core-Protokollierung) bei Verwendung von SQLite lautet:

First call to Find...
info: 12/29/2020 07:45:53.682 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[@__p_0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
      SELECT "b"."Id", "b"."Name"
      FROM "Blogs" AS "b"
      WHERE "b"."Id" = @__p_0
      LIMIT 1
...found blog .NET Blog

Second call to Find...
...returned the same instance without executing a query.

Beachten Sie, dass der erste Aufruf die Entität nicht lokal findet und führt daher eine Datenbankabfrage aus. Umgekehrt gibt der zweite Aufruf dieselbe Instanz zurück, ohne die Datenbank abfragen zu müssen, da sie bereits nachverfolgt wird.

Find gibt null zurück, wenn eine Entität mit dem angegebenen Schlüssel nicht lokal verfolgt wird und in der Datenbank nicht existiert.

Zusammengesetzte Tasten

Es ist möglich, die Suche mit zusammengesetzten Schlüsseln zu verwenden. Betrachten Sie beispielsweise eine OrderLine Entität mit einem zusammengesetzten Schlüssel, der aus der Auftrags-ID und der Produkt-ID besteht:

public class OrderLine
{
    public int OrderId { get; set; }
    public int ProductId { get; set; }

    //...
}

Der zusammengesetzte Schlüssel muss so konfiguriert DbContext.OnModelCreating werden, dass die wichtigsten Teile und deren Reihenfolge definiert werden. Beispiel:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<OrderLine>()
        .HasKey(e => new { e.OrderId, e.ProductId });
}

Beachten Sie, dass OrderId es sich um den ersten Teil des Schlüssels handelt und ProductId der zweite Teil des Schlüssels ist. Diese Reihenfolge muss beim Übergeben von Schlüsselwerten an Find verwendet werden. Beispiel:

var orderline = await context.OrderLines.FindAsync(orderId, productId);

Verwenden von ChangeTracker.Entries für den Zugriff auf alle nachverfolgten Entitäten

Bisher haben wir jeweils nur auf einen einzelnen EntityEntry zugegriffen. ChangeTracker.Entries() gibt einen EntityEntry für jede Entität zurück, die derzeit vom DbContext nachverfolgt wird. Beispiel:

using var context = new BlogsContext();
var blogs = await context.Blogs.Include(e => e.Posts).ToListAsync();

foreach (var entityEntry in context.ChangeTracker.Entries())
{
    Console.WriteLine($"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property("Id").CurrentValue}");
}

Dieser Code generiert die folgende Ausgabe:

Found Blog entity with ID 1
Found Post entity with ID 1
Found Post entity with ID 2

Beachten Sie, dass Einträge für Blogs und Beiträge zurückgegeben werden. Die Ergebnisse können stattdessen mithilfe der ChangeTracker.Entries<TEntity>() generischen Überladung nach einem bestimmten Entitätstyp gefiltert werden:

foreach (var entityEntry in context.ChangeTracker.Entries<Post>())
{
    Console.WriteLine(
        $"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}

Die Ausgabe dieses Codes zeigt, dass nur Beiträge zurückgegeben werden:

Found Post entity with ID 1
Found Post entity with ID 2

Außerdem gibt die Verwendung der generischen Überladung generische EntityEntry<TEntity> Instanzen zurück. Dies ermöglicht den fließenden Zugriff auf die Id Eigenschaft in diesem Beispiel.

Der generische Typ, der für die Filterung verwendet wird, muss kein zugeordneter Entitätstyp sein; Stattdessen kann ein nicht zugeordneter Basistyp oder eine nicht zugeordnete Schnittstelle verwendet werden. Wenn beispielsweise alle Entitätstypen im Modell eine Schnittstelle implementieren, die ihre Schlüsseleigenschaft definiert:

public interface IEntityWithKey
{
    int Id { get; set; }
}

Anschließend kann diese Schnittstelle verwendet werden, um mit dem Schlüssel jeder nachverfolgten Entität auf stark typierte Weise zu arbeiten. Beispiel:

foreach (var entityEntry in context.ChangeTracker.Entries<IEntityWithKey>())
{
    Console.WriteLine(
        $"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}

Verwenden von DbSet.Local zum Abfragen nachverfolgter Entitäten

EF Core-Abfragen werden immer in der Datenbank ausgeführt und geben nur Entitäten zurück, die in der Datenbank gespeichert wurden. DbSet<TEntity>.Local stellt einen Mechanismus zum Abfragen von DbContext für lokale, nachverfolgte Entitäten bereit.

Da DbSet.Local zum Abfragen nachverfolgter Entitäten verwendet wird, ist es typisch, Entitäten in den DbContext zu laden und dann mit diesen geladenen Entitäten zu arbeiten. Dies gilt insbesondere für die Datenbindung, kann aber auch in anderen Situationen nützlich sein. Im folgenden Code wird die Datenbank beispielsweise zuerst für alle Blogs und Beiträge abgefragt. Die Load Erweiterungsmethode wird verwendet, um diese Abfrage mit den Ergebnissen auszuführen, die vom Kontext nachverfolgt werden, ohne direkt an die Anwendung zurückgegeben zu werden. (Die Verwendung von ToList oder ähnlichem hat den gleichen Effekt, jedoch mit dem Aufwand zur Erstellung der zurückgegebenen Liste, die hier nicht erforderlich ist.) Das Beispiel verwendet dann DbSet.Local, um auf die lokal nachverfolgten Entitäten zuzugreifen.

using var context = new BlogsContext();

await context.Blogs.Include(e => e.Posts).LoadAsync();

foreach (var blog in context.Blogs.Local)
{
    Console.WriteLine($"Blog: {blog.Name}");
}

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"Post: {post.Title}");
}

Beachten Sie, dass ChangeTracker.Entries() im Gegensatz zu DbSet.Local Entitätsinstanzen direkt zurückgibt. Ein EntityEntry kann natürlich immer durch Aufrufen von DbContext.Entry für die zurückgegebene Entität abgerufen werden.

Die lokale Ansicht

DbSet<TEntity>.Local gibt eine Ansicht der lokal nachverfolgten Entitäten zurück, die den aktuellen Zustand dieser Entitäten widerspiegelt. Dies bedeutet insbesondere Folgendes:

  • Added Entitäten sind enthalten. Beachten Sie, dass dies nicht für normale EF Core-Abfragen der Fall ist, da Added Entitäten noch nicht in der Datenbank vorhanden sind und daher nie von einer Datenbankabfrage zurückgegeben werden.
  • Deleted Entitäten werden ausgeschlossen. Beachten Sie, dass dies bei normalen EF Core-Abfragen nicht der Fall ist, da Deleted Entitäten weiterhin in der Datenbank vorhanden sind und daher von Datenbankabfragen zurückgegeben werden .

All dies bedeutet, dass DbSet.Local es sich um eine Ansicht über die Daten handelt, die den aktuellen konzeptionellen Zustand des Entitätsdiagramms widerspiegeln, wobei Added Entitäten eingeschlossen und Deleted Entitäten ausgeschlossen sind. Dies stimmt mit dem Datenbankstatus überein, der nach dem Aufruf von SaveChanges erwartet wird.

Dies ist in der Regel die ideale Ansicht für die Datenbindung, da die Daten dem Benutzer so angezeigt werden, wie er sie versteht, basierend auf den von der Anwendung vorgenommenen Änderungen.

Der folgende Code veranschaulicht dies durch Markieren eines Beitrags als Deleted und anschließendes Hinzufügen eines neuen Beitrags als Added:

using var context = new BlogsContext();

var posts = await context.Posts.Include(e => e.Blog).ToListAsync();

Console.WriteLine("Local view after loading posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

context.Remove(posts[1]);

context.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many...",
        Blog = posts[0].Blog
    });

Console.WriteLine("Local view after adding and deleting posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

Die Ausgabe aus diesem Code lautet:

Local view after loading posts:
  Post: Announcing the Release of EF Core 5.0
  Post: Announcing F# 5
  Post: Announcing .NET 5.0
Local view after adding and deleting posts:
  Post: What’s next for System.Text.Json?
  Post: Announcing the Release of EF Core 5.0
  Post: Announcing .NET 5.0

Beachten Sie, dass der gelöschte Beitrag aus der lokalen Ansicht entfernt wird und der hinzugefügte Beitrag enthalten ist.

Verwenden von "Lokal" zum Hinzufügen und Entfernen von Entitäten

Eine Instanz von DbSet<TEntity>.Local wird von LocalView<TEntity> zurückgegeben. Dies ist eine Implementierung von ICollection<T>, die Benachrichtigungen generiert und darauf reagiert, wenn Entitäten zur Sammlung hinzugefügt oder daraus entfernt werden. (Dies ist dasselbe Konzept wie ObservableCollection<T>, aber als Projektion über vorhandene EF Core-Änderungsnachverfolgungseinträge und nicht als unabhängige Sammlung implementiert.)

Die Benachrichtigungen der lokalen Ansicht werden in die DbContext-Änderungsnachverfolgung eingebunden, sodass die lokale Ansicht mit dem DbContext synchronisiert bleibt. Dies gilt insbesondere in folgenden Fällen:

  • Das Hinzufügen einer neuen Entität DbSet.Local bewirkt, dass sie vom DbContext nachverfolgt wird, in der Regel im Added Zustand. (Wenn die Entität bereits über einen generierten Schlüsselwert verfügt, wird sie stattdessen als Unchanged nachverfolgt.)
  • Beim Entfernen einer Entität von DbSet.Local wird sie als Deleted markiert.
  • Eine Entität, die vom DbContext nachverfolgt wird, wird automatisch in der DbSet.Local Auflistung angezeigt. Wenn Sie beispielsweise eine Abfrage ausführen, um weitere Entitäten einzubringen, wird die lokale Ansicht automatisch aktualisiert.
  • Eine Entität, die als gekennzeichnet ist, Deleted wird automatisch aus der lokalen Auflistung entfernt.

Dies bedeutet, dass die lokale Ansicht verwendet werden kann, um nachverfolgte Entitäten einfach durch Hinzufügen und Entfernen aus der Auflistung zu bearbeiten. Ändern wir beispielsweise den vorherigen Beispielcode, um Beiträge aus der lokalen Auflistung hinzuzufügen und daraus zu entfernen:

using var context = new BlogsContext();

var posts = await context.Posts.Include(e => e.Blog).ToListAsync();

Console.WriteLine("Local view after loading posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

context.Posts.Local.Remove(posts[1]);

context.Posts.Local.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many...",
        Blog = posts[0].Blog
    });

Console.WriteLine("Local view after adding and deleting posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

Die Ausgabe bleibt unverändert zum vorherigen Beispiel, da die an der lokalen Ansicht vorgenommenen Änderungen mit dem DbContext synchronisiert werden.

Verwenden der lokalen Ansicht für Windows Forms- oder WPF-Datenbindung

DbSet<TEntity>.Local bildet die Grundlage für die Datenbindung an EF Core-Entitäten. Sowohl Windows Forms als auch WPF funktionieren jedoch am besten, wenn sie mit dem spezifischen Typ der Benachrichtigungssammlung verwendet werden, die sie erwarten. Die lokale Ansicht unterstützt das Erstellen dieser spezifischen Sammlungstypen:

Beispiel:

ObservableCollection<Post> observableCollection = context.Posts.Local.ToObservableCollection();
BindingList<Post> bindingList = context.Posts.Local.ToBindingList();

Weitere Informationen zur WPF-Datenbindung mit EF Core finden Sie unter " Erste Schritte mit WPF " und " Erste Schritte mit Windows Forms ", um weitere Informationen zur Datenbindung von Windows Forms mit EF Core zu erhalten.

Tipp

Die lokale Ansicht für eine bestimmte DbSet-Instanz wird beim ersten Zugriff und anschließenden Zwischenspeichern lazily erstellt. Die Erstellung von LocalView selbst ist schnell und verwendet keinen erheblichen Arbeitsspeicher. Es ruft jedoch DetectChanges auf, was für eine große Anzahl von Entitäten langsam sein kann. Die Sammlungen von ToObservableCollection und ToBindingList werden träge erstellt und dann zwischengespeichert. Beide Methoden erstellen neue Sammlungen, die langsam sein können und viel Arbeitsspeicher verwenden können, wenn Tausende von Entitäten beteiligt sind.