Abfragen mit Nachverfolgung im Vergleich zu Abfragen ohne Nachverfolgung

Das Nachverfolgungsverhalten steuert, ob Entity Framework Core Informationen über eine Entitätsinstanz in der Änderungsprotokollierung speichert. Wenn eine Entität nachverfolgt wird, werden alle Änderungen, die in der Entität erkannt werden, während SaveChanges in der Datenbank beibehalten. EF Core korrigiert auch Unstimmigkeiten zwischen den Navigationseigenschaften von Entitäten in einem Nachverfolgungs-Abfrageergebnis und Entitäten, die sich in der Änderungsprotokollierung befinden.

Hinweis

Schlüssellose Entitätstypen werden nie nachverfolgt. Wenn in diesem Artikel Entitätstypen erwähnt werden, sind Entitätstypen gemeint, für die ein Schlüssel definiert ist.

Tipp

Das in diesem Artikel verwendete Beispiel finden Sie auf GitHub.

Abfragen mit Nachverfolgung

Abfragen, die Entitätstypen zurückgeben, verfügen standardmäßig über Nachverfolgung. Eine Nachverfolgungsabfrage bedeutet, dass alle Änderungen an Entitätsinstanzen durch SaveChanges beibehalten werden. Im folgenden Beispiel wird die Änderung an der Bewertung des Blogs erkannt und während SaveChanges in der Datenbank beibehalten:

var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();

Wenn die Ergebnisse in einer Abfrage mit Nachverfolgung zurückgegeben werden, prüft EF Core, ob die Entität bereits im Kontext ist. Wenn EF Core eine vorhandene Entität findet, wird dieselbe Instanz zurückgegeben, die potenziell weniger Arbeitsspeicher und schneller als eine Abfrage ohne Nachverfolgung verwenden kann. EF Core überschreibt im Eintrag mit den Datenbankwerten nicht die aktuellen und ursprünglichen Werte der Eigenschaften der Entität. Wenn die Entität nicht im Kontext gefunden wird, erstellt EF Core eine neue Entitätsinstanz und fügt sie dem Kontext an. Abfrageergebnisse enthalten keine Entität, die dem Kontext hinzugefügt, aber noch nicht in der Datenbank gespeichert wurde.

Abfragen ohne Nachverfolgung

Abfragen ohne Nachverfolgung sind nützlich, wenn die Ergebnisse in einem schreibgeschützten Szenario verwendet werden. Sie werden in der Regel schneller ausgeführt, da keine Informationen für die Änderungsnachverfolgung eingerichtet werden müssen. Wenn Sie die aus der Datenbank abgerufenen Entitäten nicht aktualisieren müssen, sollte eine Abfrage ohne Nachverfolgung verwendet werden. Sie können eine einzelne Abfrage ändern, sodass sie keine Nachverfolgung ausführt. Eine Abfrage ohne Nachverfolgung liefert ebenfalls Ergebnisse, die auf dem basieren, was in der Datenbank vorhanden ist, wobei lokale Änderungen oder hinzugefügte Entitäten nicht berücksichtigt werden.

var blogs = context.Blogs
    .AsNoTracking()
    .ToList();

Sie können das Standardnachverfolgungsverhalten auch auf der Ebene der Kontextinstanz ändern:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var blogs = context.Blogs.ToList();

Im nächsten Abschnitt wird erläutert, wann eine Abfrage ohne Nachverfolgung möglicherweise weniger effizient ist als eine Nachverfolgungsabfrage.

Identitätsauflösung

Da eine Nachverfolgungsabfrage die Änderungsprotokollierung verwendet, führt EF Core in einer Nachverfolgungsabfrage eine Identitätsauflösung durch. Beim Materialisieren einer Entität gibt EF Core dieselbe Entitätsinstanz aus der Änderungsprotokollierung zurück, wenn sie bereits nachverfolgt wird. Wenn das Ergebnis dieselbe Entität mehrfach enthält, wird für jedes Auftreten dieselbe Instanz zurückgegeben. Abfragen ohne Nachverfolgung:

  • Verwenden weder die Änderungsprotokollierung noch führen sie eine Identitätsauflösung durch.
  • Geben eine neue Instanz der Entität zurück, auch wenn dieselbe Entität mehrfach im Ergebnis enthalten ist.

Nachverfolgung und Abfragen ohne Nachverfolgung können in derselben Abfrage kombiniert werden. Das heißt, Sie können eine Abfrage ohne Nachverfolgung haben, die eine Identitätsauflösung in den Ergebnissen vornimmt. Genau wie den abfragbaren Operator AsNoTracking haben wir mit AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>) einen weiteren Operator hinzugefügt. Der Enumeration QueryTrackingBehavior wurde außerdem ein zugeordneter Eintrag hinzugefügt. Wenn Sie die Abfrage so konfigurieren, dass die Identitätsauflösung ohne Nachverfolgung verwendet wird, verwenden wir bei der Generierung der Abfrageergebnisse eine eigenständige Änderungsnachverfolgung im Hintergrund, sodass jede Instanz sich nur einmal materialisiert. Da sich diese Änderungsnachverfolgung von der im Kontext unterscheidet, werden die Ergebnisse nicht vom Kontext nachverfolgt. Nachdem die Abfrage vollständig aufgezählt ist, verlässt die Änderungsnachverfolgung den Gültigkeitsbereich, und die Garbage Collection erfolgt den Anforderungen entsprechend.

var blogs = context.Blogs
    .AsNoTrackingWithIdentityResolution()
    .ToList();

Konfigurieren des Standardverhaltens für die Nachverfolgung

Wenn Sie das Nachverfolgungsverhalten für viele Abfragen ändern müssen, sollten Sie darüber nachdenken, stattdessen das Standardverhalten zu ändern:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying.Tracking;Trusted_Connection=True")
        .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}

Dadurch werden Abfragen standardmäßig nicht nachverfolgt. Sie können jedoch AsTracking hinzufügen, um bestimmte Abfragen nachzuverfolgen.

Nachverfolgung und benutzerdefinierte Projektionen

Selbst wenn der Ergebnistyp der Abfrage kein Entitätstyp ist, verfolgt EF Core im Ergebnis enthaltene Entitätstypen standardmäßig nach. In der folgenden Abfrage, die einen anonymen Typ zurückgibt, werden die Instanzen von Blog im Ergebnis nachverfolgt.

var blog = context.Blogs
    .Select(
        b =>
            new { Blog = b, PostCount = b.Posts.Count() });

Wenn das Resultset Entitätstypen enthält, die aus der LINQ-Komposition stammen, werden sie von EF Core nachverfolgt.

var blog = context.Blogs
    .Select(
        b =>
            new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });

Wenn das Resultset keine Entitätstypen enthält, wird keine Nachverfolgung ausgeführt. In der folgenden Abfrage geben wir einen anonymen Typ mit einigen Werten der Entität zurück (aber keine Instanzen des aktuellen Entitätstyps). Es sind keine aus der Abfrage stammenden nachverfolgten Entitäten vorhanden.

var blog = context.Blogs
    .Select(
        b =>
            new { Id = b.BlogId, b.Url });

EF Core unterstützt die Clientauswertung in der Projektion auf oberster Ebene. Wenn EF Core eine Entitätsinstanz für die Clientauswertung materialisiert, wird sie nachverfolgt. Da wir hier blog-Entitäten an die Clientmethode StandardizeURL übergeben, verfolgt EF Core auch die Bloginstanzen nach.

var blogs = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(
        blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
    .ToList();
public static string StandardizeUrl(Blog blog)
{
    var url = blog.Url.ToLower();

    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }

    return url;
}

EF Core verfolgt nicht die schlüssellosen Entitätsinstanzen nach, die im Ergebnis enthalten sind. EF Core verfolgt jedoch alle anderen Instanzen von Entitätstypen mit Schlüssel gemäß den oben aufgeführten Regeln nach.

Frühere Versionen

Vor Version 3.0 wies EF Core bei der Nachverfolgung einige Unterschiede auf. Wesentliche Unterschiede sind:

  • Wie auf der Seite Clientauswertung im Vergleich zur Serverauswertung erläutert, unterstützte EF Core vor Version 3.0 die Clientauswertung in einem beliebigen Teil der Abfrage. Die Clientauswertung verursachte Materialisierungen von Entitäten, die nicht Teil des Ergebnisses waren. Daher analysierte EF Core das Ergebnis, um zu ermitteln, was nachverfolgt werden soll. Dieser Entwurf wies wie folgt bestimmte Unterschiede auf:

    • Clientauswertung in der Projektion, die die Materialisierung verursachte, aber die materialisierte Entitätsinstanz nicht zurückgab, wurde nicht nachverfolgt. Im folgenden Beispiel wurden blog-Entitäten nicht nachverfolgt.

      var blogs = context.Blogs
          .OrderByDescending(blog => blog.Rating)
          .Select(
              blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
          .ToList();
      
    • In bestimmten Fällen wurden aus der LINQ-Komposition stammende Objekte von EF Core nicht nachverfolgt. Im folgenden Beispiel wurde Post nicht nachverfolgt.

      var blog = context.Blogs
          .Select(
              b =>
                  new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });
      
  • Wenn Abfrageergebnisse schlüssellose Entitätstypen enthielten, wurde die gesamte Abfrage nicht nachverfolgt. Dies bedeutet, dass Entitätstypen mit Schlüsseln, die sich im Ergebnis befanden, auch nicht nachverfolgt wurden.

  • EF Core führte in Abfragen ohne Nachverfolgung eine Identitätsauflösung durch. Schwache Verweise wurden verwendet, um bereits zurückgegebene Entitäten nachzuverfolgen. Wenn also ein Resultset dieselbe Entität mehrfach enthielt, erhielten Sie für jedes Vorkommen dieselbe Instanz. Auch wenn ein vorheriges Ergebnis mit derselben Identität den Gültigkeitsbereich verließ und eine Garbage Collection durchgeführt wurde, gab EF Core eine neue Instanz zurück.