Entity Framework: Der ChangeTracker

Wenn Sie ein Abonnement des Magazins 'DATENBANKENTWICKLER' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.

Entity Framework: Der ChangeTracker

Wenn Sie Daten etwa aus den Tabellen einer Datenbank in ein Entity Data Model geladen haben, finden Sie dort einige Funktionen für den Umgang mit den enthaltenen Daten vor. Ein sehr wichtiges Element ist dabei der ChangeTracker. Auch wenn Sie mit der SaveChanges-Methode automatisch alle Änderungen im Entity Data Model erkennen und diese in die Datenbank übertragen können, so treten doch Fälle auf, in denen Sie zuvor prüfen wollen, welche Änderungen überhaupt im Entity Data Model vorgenommen wurden – und ob diese in die Datenbank übernommen oder vielleicht sogar verworfen werden sollen.

Wenn Sie eine Anwendung bauen, wie wir sie in den letzten Ausgaben verwendet haben, dann bezieht diese ihre Daten aus den Tabellen eines SQL Servers und stellt diese in einem Entity Data Model bereit. Die Elemente des Entity Data Models binden Sie dann an die Benutzeroberfläche und die dort befindlichen Steuer­elemente.

Nun können nach dem Anzeigen der Daten in einem Fenster zum Bearbeiten verschiedene Fälle auftreten: Sie ändern ein oder mehrere Felder eines Datensatzes, fügen einen neuen Datensatz hinzu oder löschen einen Datensatz. Diese Änderungen liegen dann im Entity Data Model vor, aber werden nicht automatisch in die Datenbank übertragen. Das erledigen Sie erst, wenn Sie die SaveChanges-Methode des Datenbankkontextes aufrufen. SaveChanges ist effizient, denn es prüft, ob Änderungen vorliegen und speichert nur die geänderten Elemente in der Datenbank.

Was aber geschieht, wenn Sie beispielsweise erfahren wollen, ob an einem bestimmten Datensatz Änderungen vorgenommen wurden? Für ein Fenster mit Minimalanforderungen benötigen Sie diese Information nicht, denn Sie können die Änderungen ja einfach in der Datenbank speichern. Wenn Sie dem Benutzer jedoch ein paar weitere Features wie etwa eine Schaltfläche zum Verwerfen der Änderungen zur Verfügung stellen wollen, kann es schon interessant sein, ob der Benutzer überhaupt schon Änderungen am aktuellen Datensatz vorgenommen hat. Dabei könnten Sie die Information, ob der Datensatz bereits geändert wurde, etwa dazu nutzen, die Abbrechen- oder Verwerfen-Schaltfläche nur zu aktivieren, wenn der Benutzer bereits Änderungen vorgenommen hat – oder wenn er soeben einen neuen Datensatz angelegt hat, der ja mitunter auch verworfen werden könnte.

Und hier kommt der ChangeTracker von Entity Framework ins Spiel. Dabei handelt es sich um eine Klasse, die zum Beispiel den Zugriff auf alle Elemente mit einem bestimmten Status zulässt – beispielsweise geändert oder nicht geändert. In diesem Artikel schauen wir uns nun an, wie Sie mit dem ChangeTracker von Entity Framework arbeiten können.

Beispielprojekt

Als Beispielprojekt verwenden wir ein Projekt auf Basis der Vorlage Visual Basic|Windows Desktop|WPF-App namens ChangeTracker. Dieser haben wir ein Entity Data Model mit der Methode aus der Artikelreihe Von Access zu Entity Framework hinzugefügt und eine SQL Server-Datenbank damit erstellt. Um die SQL Server-Datenbank auf ihrem Rechner zu erstellen, rufen Sie die Anweisung Update-Database in der Paket-Manager-Konsole auf. Dadurch wird eine Datenbank auf Basis der in der Datei App.config angegebenen Verbindungszeichenfolge erstellt, die Sie gegebenenfalls noch anpassen können. Die verschiedenen Beispielcodes der folgenden Abschnitte finden Sie hinter den Ereignismethoden der Schaltflächen des Fensters MainWindow.xaml.

Die ChangeTracker-Klasse

Die ChangeTracker-Klasse referenzieren über die gleichnamige Eigenschaft der Datenbankkontext-Klasse, die in unserem Beispielen meist dbContext genannt wird. Mit dieser Klasse können Sie den Zustand jedes der Elemente des aktuellen Datenbankkontexts nachverfolgen. Jeder der Einträge hat einen Wert für die Eigenschaft EntityState. Die möglichen Werte lauten:

  • Added: Hinzugefügtes Element, das noch nicht in der Datenbank existiert
  • Modified: Geändertes Element, dessen Änderungen noch nicht in die Datenbank übertragen wurden
  • Deleted: Gelöschtes Element, das noch in der Datenbank existiert
  • Unchanged: Nicht geändertes Element
  • Detached: Nicht verbundenes, also nicht zu einem DbSet gehörendes Element. Dieses wird auch nicht vom ChangeTracker verfolgt.

Beispiele zur ChangeTracker-Klasse

Wir schauen uns die verschiedenen Zustände an einfachen Beispielen an. Dazu fügen wir der Klasse MainWindow.xaml.vb zunächst die folgenden Namespaces hinzu:

Imports System.Data.Entity
Imports System.Data.Entity.Infrastructure

Danach erstellen wir die folgende Ereignismethode für die erste Schaltfläche btnUnveraendert. Hier füllen wir die Variable dbContext mit einer neuen Instanz des Datenbankkontextes und füllen das Objekt Kunde des Typs Kunde mit dem ersten Eintrag der Liste Kunden des Datenbankbankkontextes:

Private Sub btnUnveraendert_Click(sender As Object, e As RoutedEventArgs)
     Dim dbContext As BestellverwaltungContext
     Dim Element As EntityEntry
     Dim Kunde As Kunde
     dbContext = New BestellverwaltungContext
     Kunde = dbContext.Kunden.First()

Danach folgt der erste Zugriff auf den ChangeTracker. Dabei durchlaufen wir dessen Entries-Auflistung und referenzieren das aktuelle Element jeweils mit der Variablen Element, die übrigens den Typ EntityEntry aufweist (dafür auch der Verweis auf den Namespace). Anschließend geben wir für jeden Eintrag zwei Informationen aus – den Namen des Elementtyps und den Zustand. Um den Namen des Elementtyps zu erhalten, referenzieren wir zunächst die in Element enthaltene Entität mit der Eigenschaft Entity und ermitteln den Namen des Typs mit GetType().Name. Den Zustand dieses Eintrags liefert die Eigenschaft State des EntityEntry-Elements:

     For Each Element In dbContext.ChangeTracker.Entries
         Debug.Print("Elementname: " + Element.Entity.GetType().Name + " Status: " + Element.State.ToString())
     Next
End Sub

Das Ergebnis sieht schließlich wie in Bild 1 aus. Der Typ Kunde wird korrekt erkannt und auch der Status entspricht mit Unchanged den Erwartungen.

Ausgabe von Typ und Status einer Entität

Bild 1: Ausgabe von Typ und Status einer Entität

ChangeTracker verfolgt alle Elementtypen

Wir fügen eine Variable für ein Element eines weiteren Elementtypen namens Anrede hinzu und lesen auch hier den ersten Eintrag aus – in diesem Fall aus der Liste Anreden:

Dim Anrede As Anrede
...
Anrede = dbContext.Anreden.First()

Die Ausgabe im Ausgabefenster sieht nach dem erneuten Start der Anwendung dem Betätigen der Schaltfläche wie folgt aus:

Elementname: Kunde Status: Unchanged
Elementname: Anrede Status: Unchanged

Wir sehen also, dass die Entries-Auflistung der ChangeTracker-Klasse Elemente aller Typen umfasst – sie müssen nur in den Speicher geladen sein.

Ein Element löschen

Im nächsten Beispiel wollen wir untersuchen, was beim Löschen eines Eintrags geschieht. Dazu legen wir eine neue Schaltfläche namens btnGeloeschterEintrag hinzu, für deren Click-Ereignis wir die folgende Ereignismethode hinterlegen:

Private Sub btnGeloeschterEintrag_Click(sender As Object, e As RoutedEventArgs)
     Dim dbContext As BestellverwaltungContext
     Dim Bestellposition As Bestellposition
     Dim Element As DbEntityEntry
     dbContext = New BestellverwaltungContext
     Bestellposition = dbContext.Bestellpositionen.First()
     dbContext.Bestellpositionen.Remove(Bestellposition)
     For Each Element In dbContext.ChangeTracker.Entries
         MessageBox.Show("Elementname: " + Element.Entity.GetType().Name + vbCrLf + " Status: " _
             + Element.State.ToString())
     Next
End Sub

Hier laden wir nun das erste Element der Auflistung Bestellpositionen, da wir diese ohne Probleme löschen können – die Einträge haben keine Beziehungen, deren Restriktion ein Löschen verhindern könnte. Dann löschen wir dieses Element mit der Remove-Methode aus der Auflistung Bestellpositionen. Anschließend rufen wir wieder die gleiche For Each-Schleife über die Entries-Auflistung. Diesmal liefert die State-Eigenschaft den Wert Deleted.

Ein Element hinzufügen

Nun erstellen wir ein neues Kunde-Element, füllen seine Eigenschaften und fügen das Kunde-Element mit der Add-Methode zur Auflistung Kunden hinzu:

Kunde = New Kunde
With Kunde
     .Vorname = "André"
     .Nachname = "Minhorst"
     .AnredeID = 1
     .Strasse = "Borkhofer Str. 17"
     .PLZ = "47137"
     .Ort = "Duisburg"
     .Land = "Deutschland"
     .EMail = "andre@minhorst.com"
End With
dbContext.Kunden.Add(Kunde)
Element = dbContext.ChangeTracker.Entries.First
MessageBox.Show("Elementname: " + Element.Entity.GetType().Name + vbCrLf + " Status: " + Element.State.ToString())

Hier lautet der Wert von State nun Added.

Ein Element ändern

Wir legen noch eine neue Schaltfläche an, mit der wir ein bestehendes Element ändern. Dazu weisen wir dem Feld Vorname des Kunde-Elements einen anderen Wert zu. Da wir wissen, dass wir gerade nur einen Eintrag geändert haben, durchlaufen wir keine Schleife mehr, sondern greifen über die First-Eigenschaft auf das geänderte Element zu:

Kunde = dbContext.Kunden.First()
Kunde.Vorname = "André"
Element = dbContext.ChangeTracker.Entries.First
MessageBox.Show("Elementname: " + Element.Entity.GetType().Name + vbCrLf + " Status: " + Element.State.ToString())

In diesem Fall liefert die Eigenschaft State den Wert Modified.

Nicht verbundene Elemente

Es kann auch sein, dass Sie ein Element neu erstellen, aber dieses nicht mit der Add-Methode zur Auflistung des entsprechenden Elementtyps hinzufügen. Um zu untersuchen, welchen Zustand dieses Element hat und ob es vom ChangeTracker erfasst wird, müssen wir hier hier einen anderen Weg gehen. Wir finden nämlich, wie die zweite MessageBox zeigt, keinen Eintrag in der ChangeTracker-Liste Entries vor – das Ergebnis der Count-Eigenschaft ist 0. Um ein DbEntityEntry-Element auf Basis des neuen Kunde-Objekts zu erstellen, greifen wir nun nicht auf die Entries-Liste der ChangeTracker-Klasse zu, sondern auf die Entry-Eigenschaft von dbContext für das Kunde-Objekt. Für dieses erhalten wir dann als State den Wert Detached, also nicht verbunden:

Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.

Bitte geben Sie die Zeichenfolge in das nachfolgende Textfeld ein

Die mit einem * markierten Felder sind Pflichtfelder.

Ich habe die Datenschutzbestimmungen zur Kenntnis genommen.