Lokale Daten

Wenn Sie eine LINQ-Abfrage direkt für ein DbSet ausführen, wird immer eine Abfrage an die Datenbank gesendet. Sie können jedoch mithilfe der DbSet.Local-Eigenschaft auf die Daten zugreifen, die sich derzeit im Arbeitsspeicher befinden. Sie können auch auf die zusätzlichen Informationen zugreifen, die EF über Ihre Entitäten mithilfe der Methoden „DbContext.Entry“ und „DbContext.ChangeTracker.Entries“ nachverfolgt. Die in diesem Thema dargestellten Techniken gelten jeweils für Modelle, die mit Code First und dem EF-Designer erstellt wurden.

Verwenden von „Local“ zum Anzeigen lokaler Daten

Die lokale Eigenschaft von DbSet bietet einfachen Zugriff auf die Entitäten des Satzes, die derzeit vom Kontext nachverfolgt werden und nicht als „Gelöscht“ markiert wurden. Der Zugriff auf die Local-Eigenschaft bewirkt niemals, dass eine Abfrage an die Datenbank gesendet wird. Dies bedeutet, dass sie normalerweise verwendet wird, nachdem eine Abfrage bereits ausgeführt wurde. Die Load-Erweiterungsmethode kann verwendet werden, um eine Abfrage auszuführen, damit der Kontext die Ergebnisse nachverfolgt. Beispiel:

using (var context = new BloggingContext())
{
    // Load all blogs from the database into the context
    context.Blogs.Load();

    // Add a new blog to the context
    context.Blogs.Add(new Blog { Name = "My New Blog" });

    // Mark one of the existing blogs as Deleted
    context.Blogs.Remove(context.Blogs.Find(1));

    // Loop over the blogs in the context.
    Console.WriteLine("In Local: ");
    foreach (var blog in context.Blogs.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }

    // Perform a query against the database.
    Console.WriteLine("\nIn DbSet query: ");
    foreach (var blog in context.Blogs)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }
}

Angenommen, wir hätten zwei Blogs in der Datenbank – „ADO.NET Blog“ mit einer BlogId von 1 und „The Visual Studio Blog“ mit einer BlogId von 2 – dann könnten wir die folgende Ausgabe erwarten:

In Local:
Found 0: My New Blog with state Added
Found 2: The Visual Studio Blog with state Unchanged

In DbSet query:
Found 1: ADO.NET Blog with state Deleted
Found 2: The Visual Studio Blog with state Unchanged

Dies veranschaulicht drei Punkte:

  • Der neue Blog „Mein neuer Blog“ ist in der lokalen Sammlung enthalten, obwohl er noch nicht in der Datenbank gespeichert wurde. Dieser Blog hat einen Primärschlüssel von Null, da die Datenbank noch keinen echten Schlüssel für die Entität generiert hat.
  • Der „ADO.NET Blog“ ist nicht in der lokalen Sammlung enthalten, obwohl er noch vom Kontext nachverfolgt wird. Dies liegt daran, dass wir es aus dem DbSet entfernt haben, wodurch es als gelöscht markiert wird.
  • Wenn DbSet zur Durchführung einer Abfrage verwendet wird, wird das zum Löschen markierte Blog (ADO.NET Blog) in die Ergebnisse aufgenommen und das neue Blog (Mein neues Blog), das noch nicht in der Datenbank gespeichert wurde, wird nicht in die Ergebnisse aufgenommen. Dies liegt daran, dass DbSet eine Abfrage für die Datenbank ausführt und die zurückgegebenen Ergebnisse immer den Inhalt der Datenbank widerspiegeln.

Verwenden von „Local“ zum Hinzufügen und Entfernen von Entitäten aus dem Kontext

Die Eigenschaft „Local“ für DbSet gibt ObservableCollection mit Ereignissen zurück, die so eingebunden werden, dass sie mit dem Inhalt des Kontexts synchronisiert bleibt. Dies bedeutet, dass Entitäten entweder aus der lokalen Auflistung oder dem DbSet hinzugefügt oder entfernt werden können. Das bedeutet auch, dass Abfragen, die neue Entitäten in den Kontext bringen, dazu führen, dass die lokale Sammlung mit diesen Entitäten aktualisiert wird. Beispiel:

using (var context = new BloggingContext())
{
    // Load some posts from the database into the context
    context.Posts.Where(p => p.Tags.Contains("entity-framework")).Load();  

    // Get the local collection and make some changes to it
    var localPosts = context.Posts.Local;
    localPosts.Add(new Post { Name = "What's New in EF" });
    localPosts.Remove(context.Posts.Find(1));  

    // Loop over the posts in the context.
    Console.WriteLine("In Local after entity-framework query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }

    var post1 = context.Posts.Find(1);
    Console.WriteLine(
        "State of post 1: {0} is {1}",
        post1.Name,  
        context.Entry(post1).State);  

    // Query some more posts from the database
    context.Posts.Where(p => p.Tags.Contains("asp.net")).Load();  

    // Loop over the posts in the context again.
    Console.WriteLine("\nIn Local after asp.net query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }
}

Angenommen, wir hatten einige Beiträge, die mit „entity-framework“ und „asp.net“ markiert wurden, könnte die Ausgabe etwa wie folgt aussehen:

In Local after entity-framework query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
State of post 1: EF Beginners Guide is Deleted

In Local after asp.net query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
Found 4: ASP.NET Beginners Guide with state Unchanged

Dies veranschaulicht drei Punkte:

  • Der neue Beitrag „What's New in EF“, der der lokalen Sammlung hinzugefügt wurde, wird vom Kontext im Status „Hinzugefügt“ nachverfolgt. Sie wird daher beim Aufrufen von SaveChanges in die Datenbank eingefügt.
  • Der Beitrag, der aus der lokalen Sammlung entfernt wurde (EF-Anfängerhandbuch) wird nun im Kontext als gelöscht markiert. Es wird daher aus der Datenbank gelöscht, wenn SaveChanges aufgerufen wird.
  • Der zusätzliche Beitrag (ASP.NET-Anfängerhandbuch) in den Kontext mit der zweiten Abfrage wird automatisch der lokalen Auflistung hinzugefügt.

Eine letzte Anmerkung zu „Local“ ist, dass es sich um eine ObservableCollection-Leistung handelt, die nicht für eine große Anzahl von Entitäten geeignet ist. Wenn Sie sich daher mit Tausenden von Entitäten in Ihrem Kontext befassen, ist es möglicherweise nicht ratsam, „Local“ zu verwenden.

Verwenden von „Local“ für die WPF-Datenbindung

Die Eigenschaft „Local“ für DbSet kann direkt für die Datenbindung in einer WPF-Anwendung verwendet werden, da es sich um eine Instanz von ObservableCollection handelt. Wie in den vorherigen Abschnitten beschrieben, bedeutet dies, dass sie automatisch mit dem Inhalt des Kontexts synchronisiert bleibt und der Inhalt des Kontexts automatisch mit dem Kontext synchronisiert bleibt. Beachten Sie, dass Sie die Sammlung „Local“ vorab mit Daten auffüllen müssen, damit eine Bindung vorhanden ist, da „Local“ niemals eine Datenbankabfrage verursacht.

Dies ist kein geeigneter Ort für ein vollständiges WPF-Datenbindungsbeispiel, aber die wichtigsten Elemente sind:

  • Bindungsquelle einrichten
  • An die Eigenschaft „Local“ Ihres Satzes binden
  • Füllen Sie „Local“ mithilfe einer Abfrage für die Datenbank auf.

WPF-Bindung an Navigationseigenschaften

Wenn Sie Master-/ Detaildatenbindung durchführen, möchten Sie die Detailansicht möglicherweise an eine Navigationseigenschaft einer Ihrer Entitäten binden. Eine einfache Möglichkeit, dies zu bewerkstelligen, besteht darin, eine ObservableCollection für die Navigationseigenschaft zu verwenden. Beispiel:

public class Blog
{
    private readonly ObservableCollection<Post> _posts =
        new ObservableCollection<Post>();

    public int BlogId { get; set; }
    public string Name { get; set; }

    public virtual ObservableCollection<Post> Posts
    {
        get { return _posts; }
    }
}

Verwenden von „Local“ zum Bereinigen von Entitäten in SaveChanges

In den meisten Fällen werden Entitäten, die aus einer Navigationseigenschaft entfernt wurden, nicht automatisch im Kontext als gelöscht markiert. Wenn Sie beispielsweise ein Post-Objekt aus der Blog.Posts-Auflistung entfernen, wird dieser Beitrag nicht automatisch gelöscht, wenn SaveChanges aufgerufen wird. Wenn er gelöscht werden muss, müssen Sie diese hängenden Entitäten möglicherweise finden und sie vor dem Aufrufen von SaveChanges oder als Teil eines außer Kraft gesetzten SaveChanges als gelöscht markieren. Beispiel:

public override int SaveChanges()
{
    foreach (var post in this.Posts.Local.ToList())
    {
        if (post.Blog == null)
        {
            this.Posts.Remove(post);
        }
    }

    return base.SaveChanges();
}

Im obigen Code wird die lokale Auflistung verwendet, um alle Beiträge zu finden und alle Beiträge zu markieren, die keinen Blogverweis als gelöscht haben. Der ToList-Aufruf ist erforderlich, da andernfalls die Auflistung vom Remove-Aufruf während der Aufzählung geändert wird. In den meisten anderen Situationen können Sie die Eigenschaft „Local“ direkt abfragen, ohne zuerst ToList zu verwenden.

Verwenden der lokalen und ToBindingList-Datenbindung für Windows Forms

Windows Forms unterstützt keine vollständige Datenbindung mithilfe von ObservableCollection direkt. Sie können jedoch weiterhin die DbSet Local-Eigenschaft für die Datenbindung verwenden, um alle Vorteile zu erhalten, die in den vorherigen Abschnitten beschrieben sind. Dies wird durch die ToBindingList-Erweiterungsmethode erreicht, die eine IBindingList-Implementierung erstellt, die von der ObservableCollection „Local“ unterstützt wird.

Dies ist kein geeigneter Ort für ein vollständiges Windows Forms-Datenbindungsbeispiel, aber die wichtigsten Elemente sind:

  • Einrichten einer Objektbindungsquelle
  • Binden Sie sie mithilfe von Local.ToBindingList() an die Eigenschaft „Local“ Ihres Satzes
  • Auffüllen von „Local“ mithilfe einer Abfrage für die Datenbank

Abrufen detaillierter Informationen zu nachverfolgten Entitäten

Viele Beispiele in dieser Reihe verwenden die Entry-Methode, um eine DbEntityEntry-Instanz für eine Entität zurückzugeben. Dieses Entry-Objekt dient dann als Ausgangspunkt zum Sammeln von Informationen über die Entität, z. B. den aktuellen Zustand, sowie zum Ausführen von Vorgängen für die Entität, z. B. das explizite Laden einer verwandten Entität.

Die Entries-Methoden geben DbEntityEntry-Objekte für viele oder alle Entitäten zurück, die vom Kontext nachverfolgt werden. Auf diese Weise können Sie Informationen sammeln oder Vorgänge für viele Entitäten anstelle eines einzelnen Eintrags ausführen. Beispiel:

using (var context = new BloggingContext())
{
    // Load some entities into the context
    context.Blogs.Load();
    context.Authors.Load();
    context.Readers.Load();

    // Make some changes
    context.Blogs.Find(1).Title = "The New ADO.NET Blog";
    context.Blogs.Remove(context.Blogs.Find(2));
    context.Authors.Add(new Author { Name = "Jane Doe" });
    context.Readers.Find(1).Username = "johndoe1987";

    // Look at the state of all entities in the context
    Console.WriteLine("All tracked entities: ");
    foreach (var entry in context.ChangeTracker.Entries())
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Find modified entities of any type
    Console.WriteLine("\nAll modified entities: ");
    foreach (var entry in context.ChangeTracker.Entries()
                              .Where(e => e.State == EntityState.Modified))
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Get some information about just the tracked blogs
    Console.WriteLine("\nTracked blogs: ");
    foreach (var entry in context.ChangeTracker.Entries<Blog>())
    {
        Console.WriteLine(
            "Found Blog {0}: {1} with original Name {2}",
            entry.Entity.BlogId,  
            entry.Entity.Name,
            entry.Property(p => p.Name).OriginalValue);
    }

    // Find all people (author or reader)
    Console.WriteLine("\nPeople: ");
    foreach (var entry in context.ChangeTracker.Entries<IPerson>())
    {
        Console.WriteLine("Found Person {0}", entry.Entity.Name);
    }
}

Sie werden feststellen, dass wir eine Author- und Reader-Klasse in das Beispiel einführen – beide Klassen implementieren die IPerson-Schnittstelle.

public class Author : IPerson
{
    public int AuthorId { get; set; }
    public string Name { get; set; }
    public string Biography { get; set; }
}

public class Reader : IPerson
{
    public int ReaderId { get; set; }
    public string Name { get; set; }
    public string Username { get; set; }
}

public interface IPerson
{
    string Name { get; }
}

Nehmen wir an, wir haben die folgenden Daten in der Datenbank:

Blog mit BlogId = 1 und Name = „ADO.NET Blog“
Blog mit BlogId = 2 und Name = „Der Visual Studio-Blog“
Blog mit BlogId = 3 und Name = „.NET Framework Blog“
Autor mit AuthorId = 1 und Name = „Joe Bloggs“
Leser mit ReaderId = 1 and Name = „John Doe“

Die Ausgabe aus der Ausführung des Codes wäre:

All tracked entities:
Found entity of type Blog with state Modified
Found entity of type Blog with state Deleted
Found entity of type Blog with state Unchanged
Found entity of type Author with state Unchanged
Found entity of type Author with state Added
Found entity of type Reader with state Modified

All modified entities:
Found entity of type Blog with state Modified
Found entity of type Reader with state Modified

Tracked blogs:
Found Blog 1: The New ADO.NET Blog with original Name ADO.NET Blog
Found Blog 2: The Visual Studio Blog with original Name The Visual Studio Blog
Found Blog 3: .NET Framework Blog with original Name .NET Framework Blog

People:
Found Person John Doe
Found Person Joe Bloggs
Found Person Jane Doe

Diese Beispiele veranschaulichen mehrere Punkte:

  • Die Entries-Methoden geben Einträge für Entitäten in allen Zuständen zurück, einschließlich „Deleted“. Vergleichen Sie dies mit „Local“, das gelöschte Entitäten ausschließt.
  • Einträge für alle Entitätstypen werden zurückgegeben, wenn die nicht generische Entries-Methode verwendet wird. Wenn die generische Entries-Methode verwendet wird, werden Einträge nur für Entitäten zurückgegeben, die Instanzen des generischen Typs sind. Dies wurde oben verwendet, um Einträge für alle Blogs abzurufen. Es wurde auch verwendet, um Einträge für alle Entitäten abzurufen, die IPerson implementieren. Dies zeigt, dass der generische Typ kein tatsächlicher Entitätstyp sein muss.
  • LINQ to Objects kann verwendet werden, um die zurückgegebenen Ergebnisse zu filtern. Dies wurde oben verwendet, um Entitäten eines beliebigen Typs zu finden, solange sie geändert werden.

Beachten Sie, dass DbEntityEntry-Instanzen immer eine Nicht-Null-Entität enthalten. Relationship-Einträge und Stub-Einträge werden nicht als DbEntityEntry-Instanzen dargestellt, sodass sie nicht gefiltert werden müssen.