April 2016

Band 31, Nummer 4

Datenpunkte – Behandeln des Status getrennter Entitäten in EF

Von Julie Lerman

Julie LermanGetrennte Daten sind ein altes Problem, das es schon vor Entity Framework und, wenn wir schon dabei sind, bei den meisten Datenzugriffstools gab. Und seine Lösung war nie einfach. Der Server überträgt Daten, ohne zu wissen, was mit ihnen in der Client-App passiert, die diese angefordert hat, und ob sie zurückgegeben werden. Auf einmal tauchen einige Daten wieder in einer Anforderung auf. Doch sind es dieselben Daten? Was war mit ihnen in ihrer Abwesenheit los? Ist etwas mit ihnen passiert? Sind es komplett neue Daten? So viele Fragen!

Als .NET-Entwickler haben Sie wahrscheinlich Muster zur Lösung dieses Problems kennengelernt. Erinnern Sie sich an ADO.NET-DataSets? Diese enthielten nicht nur Ihre Daten, sondern kapselten sämtliche Änderungsstatusinformationen für jede Zeile und Spalte. Sie waren nicht begrenzt auf „wurde geändert“ oder „ist neu“, sondern auch die ursprünglichen Daten wurden beibehalten. Als wir mit dem Erstellen von ASMX-Webdiensten begannen, war es ganz einfach, ein Dataset zu serialisieren und zu übertragen. Wenn diese Nachricht an einen .NET-Client gerichtet war, konnte dieser Client das Dataset deserialisieren und Änderungen weiter nachverfolgen. Wenn es Zeit war, die Daten an den Dienst zurückzugeben, mussten diese nur erneut serialisiert und anschließend auf Serverseite zurück in ein Dataset deserialisiert werden, wobei alle nützlichen Änderungsverfolgungsinformationen intakt blieben, um mühelos dauerhaft in die Datenbank geschrieben zu werden. Es hat funktioniert. Und es war ganz einfach. Doch der Nachteil waren die enormen Datenmengen, die hin und her übertragen wurden. Nicht bloß die Datenbits, sondern die Struktur des serialisierten Datasets sorgten für überaus große XML-Dateien.

Die Größe der hin und her übertragenen serialisierten Nachricht war nur ein Problem. Das Tolle an Webdiensten war, dass Sie Dienste einer Vielzahl von Plattformen bereitstellen konnten. Doch die Nachricht selbst war nur für eine andere .NET-Anwendung von Bedeutung. 2005 schrieb Scott Hanselman einen großartigen Weckruf für das Problem mit dem episch-ironischen Titel „Returning DataSets from WebServices Is the Spawn of Satan and Represents All That Is Truly Evil in the World“ (bit.ly/1TlqcB8) (Das Zurückgeben von DataSets von Webdiensten ist die Ausgeburt der Hölle und stellt alles wirklich Böse in der Welt dar).

Alle diese Statusinformationen bei der Übertragung verschwanden, als DataSets von Entity Framework als primäres Datenzugriffstool in .NET ersetzt wurden. Anstatt mit den Daten gespeichert zu werden, wurden Änderungsverfolgungsinformationen (Ursprungswert, aktueller Wert, Status) von EF nun als Teil von „ObjectContext“ gespeichert. Doch noch bei der ersten Iteration von EF war eine serialisierte Entität eine schwerfällige Nachricht aufgrund der Notwendigkeit des Erbens vom EF-Typ „EntityObject“. Doch die hin und her übertragene Nachricht mit den Entitätsdaten hatte ihr Verständnis von Status verloren. Diejenigen von uns, die an das überladene DataSet gewohnt waren, waren fassungslos. Diejenigen, die bereits mit dem Umgang mit getrennten Statusinformationen vertraut waren, hatten einen anderen Grund zur Klage – die Anforderung der Basisklasse „EntityObject“. Dieses Problem sicherte sich letztlich die Aufmerksamkeit des EF-Teams (eine sehr vorteilhafte Wende der Ereignisse), und mit der nächsten Iteration, EF4, zeichnete sich EF durch die Unterstützung von POCO (Plain Old CLR Object) aus. Dies bedeutete, dass „ObjectContext“ den Status einer einfachen Klasse beibehalten konnten, ohne dass diese Klasse von „EntityObject“ erben muss.

Doch auch mit EF4 wurde das Problem des getrennten Status nicht behoben. EF hatte keine Ahnung vom Status einer Entität, die es nicht nachverfolgen konnte. Mit DataSets vertraute Entwickler erwarteten, dass EF dieselbe „magische“ Lösung bieten würde, und waren unglücklich, dass sie zwischen einer schlanken Nachricht und getrennten Änderungsverfolgung wählen mussten. In der Zwischenzeit haben Entwickler (so wie auch ich) viele Möglichkeiten zum Informieren des Servers darüber untersucht, was mit den Daten passiert ist, während sie unterwegs waren. Wenn überhaupt, konnten Sie die Daten erneut aus der Datenbank lesen und von EF vergleichen lassen, um festzustellen, was sich geändert hatte. Sie konnten Annahmen treffen wie z. B. „wenn der Identitätsschlüsselwert 0 ist, muss er neu sein“. Sie konnten mit den grundlegenden APIs herumspielen und Code schreiben, um Entdeckungen beim Status zu machen und entsprechend zu reagieren. Das habe ich in dieser Zeit oft gemacht, ohne allerdings zu einer zufriedenstellenden Lösung zu kommen.

Als EF4.1 mit seinem schlankeren DbContext herauskam, war dieser vom EF-Team mit einer besonderen Gabe versehen worden, nämlich der Fähigkeit, den Kontext ganz einfach über den Status der Entität zu informieren. Mithilfe einer Klasse, die von „DbContext“ erbt, können Sie Code wie diesen schreiben:

myContext.Entity(someEntity).State=EntityState.Modified;

Wenn „someEntity“ für den Kontext neu ist, wird der Kontext gezwungen, mit der Nachverfolgung der Entität zu beginnen und gleichzeitig ihren Status anzugeben. Mehr benötigt EF nicht, um zu wissen, welcher Typ von SQL-Befehl bei „SaveChanges“ erstellt werden soll. Im vorstehenden Beispiel wäre das Ergebnis ein UPDATE-Befehl. „Entry().State“ hilft nicht beim Problem, den Status zu kennen, wenn übertragene Daten eingehen. „Entry().State“ ermöglicht Ihnen aber die Implementierung eines hilfreichen Musters, das derzeit von vielen mit Entity Framework arbeitenden Entwicklern verwendet wird. Ich werde im weiteren Verlauf dieses Artikels weiter darauf eingehen.

Auch wenn die nächste Version von Entity Framework, EF Core (zuvor EF7 genannt), mehr Konsistenz für das Arbeiten mit getrennten Graphen bieten wird, sollte das Muster, das Sie in diesem Artikel kennenlernen, immer noch einen Wert in Ihrer Trickkiste haben.

Das Problem getrennter Daten eskaliert, wenn Graphen von Daten hin und her übertragen werden. Eines der größten Probleme ist, wenn diese Graphen Objekte mit gemischtem Status enthalten und der Server keine standardmäßige Möglichkeit hat, die verschiedenen Status von Entitäten zu erkennen, die er empfangen hat. Wenn Sie „DbSet.Add“ verwenden, werden die Entitäten alle standardmäßig als „Added“ markiert. Wenn Sie „DbSet.Attach“ verwenden, werden die Entitäten als „Unchanged“ markiert. Dies ist auch der Fall, wenn Daten aus der Datenbank stammten und eine Schlüsseleigenschaft aufgefüllt wurde. EF befolgt die Anweisungen, d. h. „Add“ oder „Attach“. EF Core wird uns eine „Update“-Methode bieten, die dasselbe Verhalten wie „Add“, „Attach“ und „Delete“ aufweist, aber die Entitäten als „Modified“ markiert. Eine zu beachtende Ausnahme ist, dass wenn „DbContext“ bereits eine Entität nachverfolgt, der bekannte Status der Entität nicht überschrieben wird. Doch bei einer getrennten App erwarte ich nicht, dass der Kontext irgendetwas nachverfolgt, bevor eine Verbindung mit den Daten hergestellt wird, die von einem Client zurückgegeben werden.

Testen des Standardverhaltens

Lassen Sie uns das Standardverhalten verdeutlichen, um das Problem herauszustellen. Zur Demonstration habe ich ein einfaches (im Download enthaltenes) Modell mit einigen verwandten Klassen erstellt: „Ninja“, „NinjaEquipment“ und „Clan“. Ein „Ninja“-Objekt kann eine Auflistung von „NinjaEquipment“-Objekten enthalten und einem einzelnen „Clan“-Objekt zugeordnet sein. Der folgende Test umfasst einen Graph mit einem neuen „Ninja“-Objekt und einem zuvor vorhandenen, nicht bearbeiteten „Clan“-Objekt. Beachten Sie, dass ich normalerweise „Ninja.ClanId“ einen Wert zuweisen würde, um Konfusion mit Referenzdaten zu vermeiden. Das Festlegen von Fremdschlüsseln anstelle von Navigationseigenschaften ist eine Vorgehensweise, durch die Sie viele Probleme vermeiden können, um zwar aufgrund der „magischen“ Fähigkeiten von EF beim Bestimmen von beziehungsübergreifenden Status. (Mehr dazu erfahren Sie in meiner Kolumne vom April 2013 [bit.ly/20XVxQi], „Warum fügt Entity Framework vorhandene Objekte erneut in meine Datenbank ein?“.) Doch ich schreibe den Code auf diese Weise, um das Verhalten von EF zu veranschaulichen. Beachten Sie, dass die Schlüsseleigenschaft „Id“ des „Clan“-Objekts aufgefüllt wurde, um anzugeben, dass es sich um zuvor vorhandene Daten handelt, die aus der Datenbank stammen:

[TestMethod]
public void EFDoesNotComprehendsMixedStatesWhenAddingUntrackedGraph() {
  var ninja = new Ninja();
  ninja.Clan = new Clan { Id = 1 };
  using (var context = new NinjaContext()) {
    context.Ninjas.Add(ninja);
    var entries = context.ChangeTracker.Entries();
    OutputState(entries);
    Assert.IsFalse(entries.Any(e => e.State != EntityState.Added));
  }
}

Meine „OutputState“-Methode durchläuft „DbEntityEntry“-Objekte, wobei der Kontext die Statusinformationen für jede nachverfolgte Entität beibehält und den Typ und Wert seines „State“ ausgibt.

Im Test emuliere ich das Szenario, dass ich (irgendwo) ein neues „Ninja“-Objekt erstellt und dem vorhandenen „Clan“-Objekt zugeordnet habe. Das „Clan“-Objekt enthält lediglich Referenzdaten, die nicht bearbeitet wurden. Dann erstelle ich einen neuen Kontext und verwende die „DbSet.Add“-Methode, um EF anzuweisen, diesen Graphen nachzuverfolgen. Ich erkläre, dass die nachverfolgten Entitäten ausschließlich „Added“ (also hinzugefügt) wurden. Nach dem Test stellt sich heraus, dass der Kontext nicht verstanden hat, dass „Clan“ den Status „Unchanged“ hatte. Die Testausgabe zeigt mir, dass EF meint, beide Entitäten seien „Added“:

Result StandardOutput:
Debug Trace:
EF6WebAPI.Models.Ninja:Added
EF6WebAPI.Models.Clan:Added

Bei Aufruf von „SaveChanges“ werden sowohl das „Ninja“-Objekt als auch das „Clan“-Objekt eingefügt, was zu einem Duplikat des „Clan“-Objekts führt. Wenn ich stattdessen die „DbSet.Attach“-Methode verwendet hätte, wären beide Entitäten als „Unchanged“ markiert worden, sodass „SaveChanges“ das neue „Ninja“-Objekt nicht in die Datenbank eingefügt hätte, was tatsächliche Problem mit der Persistenz von Daten verursacht.

Ein weiteres gängiges Szenario ist das Abrufen eines „Ninja“-Objekts und seines „Equipment“-Objekts aus der Datenbank und dessen Übergabe an einen Client. Der Client bearbeitet dann eines der Elemente des „Equipment“-Objekts und fügt ein neues hinzu. Der tatsächliche Status der Entitäten ist wie folgt: „Ninja“ ist „Unchanged“, ein Element von „Equipment“ ist „Modified“ und ein anderes ist „Added“. Ohne etwas Hilfe verstehen „DbSet.Add“ und „DbSet.Attach“ die sich ändernden Status nicht. Deshalb ist es jetzt Zeit, Hilfe zu leisten.

Informieren von EF über den Status der einzelnen Entitäten

Der einfachste Weg, EF beim Verstehen des ordnungsgemäßen Status jeder Entität in einem Graph zu helfen, ist eine aus vier Teilen bestehende Lösung:

  1. Definieren einer Enumeration, die mögliche Objektstatus darstellt.
  2. Erstellen einer Schnittstelle mit einer „ObjectState“-Eigenschaft, die von der Enumeration definiert wird.
  3. Implementieren der Schnittstelle in die Domänenentitäten.
  4. Überschreiben von „DbContext SaveChanges“, um den Objektstatus zu lesen und EF zu informieren.

EF bietet die „EntityState“-Enumeration mit den Enumeratoren „Unchanged“, „Added“, „Modified“ und „Deleted“. Ich erstelle eine andere Enumeration, die von meinen Domänenklassen verwendet werden soll. Diese imitiert diese vier Status, habe aber keine Bindungen mit den Entity Framework-APIs:

public enum ObjectState
{
  Unchanged,
  Added,
  Modified,
  Deleted
}

„Unchanged“ kommt zuerst und ist deshalb die Standardeinstellung. Wenn Sie die Werte angeben möchten, vergewissern Sie sich, dass „Unchanged“ auf null (0) festgelegt wird.

Als Nächstes erstelle ich eine Schnittstelle zum Verfügbarmachen einer Eigenschaft zum Nachverfolgen des Status von Objekten mithilfe dieser Enumeration. Sie können nach Wunsch auch eine Basisklasse erstellen oder diese zu einer Basisklasse hinzufügen, die Sie bereits nutzen:

public interface IObjectWithState
{
  ObjectState State { get; set; }
}

Diese „State“-Eigenschaft wird nur im Arbeitsspeicher verwendet und muss nicht persistent in der Datenbank gespeichert werden. Ich habe „NinjaContext“ so aktualisiert, dass sichergestellt ist, dass die Eigenschaft für Objekte ignoriert wird, die sie implementieren:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
  modelBuilder.Types<IObjectWithState>().Configure(c => c.Ignore(p=>p.State));
}

Nach der Definition der Schnittstelle kann ich sie in meine Klassen implementieren, z. B. in die in Abbildung 1 gezeigte „Ninja“-Klasse.

Abbildung 1: Ninja-Klasse, die „IObjectState“ implementiert

public class Ninja : IObjectWithState
{
  public Ninja() {
    EquipmentOwned = new List<NinjaEquipment>();
  }
  public int Id { get; set; }
  public string Name { get; set; }
  public bool ServedInOniwaban { get; set; }
  public Clan Clan { get; set; }
  public int ClanId { get; set; }
  public List<NinjaEquipment> EquipmentOwned { get; set; }
  public ObjectState State { get; set; }
}

Bei meiner als „Unchanged“ definierten „ObjectState“-Enumeration beginnt jedes „Ninja“-Objekt mit „Unchanged“. Mit der „Ninja“-Klasse arbeitende Programmierer sind zuständig für das bedarfsabhängige Festlegen des „State“-Werts.

Wenn es ein Problem ist, dass der Client den Status festlegen soll, kann ein weiterer Ansatz, der von Domain-Driven Design-Praktiken beeinflusst ist, sicherstellen, dass das „Ninja“-Objekt mehr in sein Verhalten und seinen Status einbezogen wird. Abbildung 2 zeigt eine wesentlich umfassender definierte Version der „Ninja“-Klasse. Beachten Sie Folgendes:

  • Die „Create“-Factorymethoden legen beide „State“ auf „Added“ fest.
  • Ich habe die Setter der Eigenschaften ausgeblendet.
  • Ich habe Methoden zum Ändern von Eigenschaften erstellt, bei denen „State“ auf „Modified“ festgelegt wird, wenn es sich nicht um ein neues „Ninja“-Objekt handelt (d. h. „State“ nicht bereits auf „Added“ festgelegt ist).

Abbildung 2: Umfassender definierte „Ninja“-Klasse

public class Ninja : IObjectWithState
{
  public static RichNinja CreateIndependent(string name, 
   bool servedinOniwaban) {
    var ninja = new Ninja(name, servedinOniwaban);
    ninja.State = ObjectState.Added;
    return ninja;
  }
  public static Ninja CreateBoundToClan(string name,
    bool servedinOniwaban, int clanId) {
    var ninja = new Ninja(name, servedinOniwaban);
    ninja.ClanId = clanId;
    ninja.State = ObjectState.Added;
    return ninja;
  }
  public Ninja(string name, bool servedinOniwaban) {
    EquipmentOwned = new List<NinjaEquipment>();
    Name = name;
    ServedInOniwaban = servedinOniwaban;
  }
  // EF needs parameterless ctor for queries
  private Ninja(){}
  public int Id { get; private set; }
  public string Name { get; private set; }
  public bool ServedInOniwaban { get; private set; }
  public Clan Clan { get; private set; }
  public int ClanId { get; private set; }
  public List<NinjaEquipment> EquipmentOwned { get; private set; }
  public ObjectState State { get; set; }
  public void ModifyOniwabanStatus(bool served) {
    ServedInOniwaban = served;
    SetModifedIfNotAdded();
  }
  private void SetModifedIfNotAdded() {
    if (State != ObjectState.Added) {
      State = ObjectState.Modified;
    }
  }
  public void SpecifyClan(Clan clan) {
    Clan = clan;
    ClanId = clan.Id;
    SetModifedIfNotAdded();
  }
  public void SpecifyClan(int id) {
    ClanId = id;
    SetModifedIfNotAdded();
  }
  public NinjaEquipment AddNewEquipment(string equipmentName) {
    return NinjaEquipment.Create(Id, equipmentName);
  }
  public void TransferEquipmentFromAnotherNinja(NinjaEquipment equipment) {
    equipment.ChangeOwner(this.Id);
  }
  public void EquipmentNoLongerExists(NinjaEquipment equipment) {
    equipment.State = ObjectState.Deleted;
  }
}

Ich habe auch den „NinjaEquipment“-Typ umfangreicher gestaltet. Sie können erkennen, dass ich davon in den „Equipment“-Methoden „AddNew“, „Transfer“ und „NoLongerExists“ profitiere. Die Änderung stellt sicher, dass die Fremdschlüssel, die zurück auf das „Ninja“-Objekt zeigen, ordnungsgemäß persistent gespeichert werden. Außerdem wird zerstörtes Equipment gemäß den Geschäftsregeln für diese bestimmte Domäne vollständig aus der Datenbank gelöscht. Das Nachverfolgen von Beziehungsänderungen beim Wiederverbinden von Graphen mit EF ist ein wenig komplizierter, weshalb es mir gefällt, dass ich eine strenge Kontrolle über die Beziehungen auf Domänenebene bewahren kann. Die „ChangeOwner“-Methode legt beispielsweise „State“ auf „Modified“ fest:

public NinjaEquipment ChangeOwner(int newNinjaId) {
  NinjaId = newNinjaId;
  State = ObjectState.Modified;
  return this;
}

Wenn nun der Client den Status explizit festlegt oder Klassen wie diese (oder ähnlich programmierte Klassen in den Sprachen auf dem Client) auf Clientseite verwendet, ist der Status der Objekte definiert, die zurück an die API oder den Dienst übergeben werden.

Nun ist es Zeit, diesen clientseitigen Status in serverseitigem Code zu nutzen.

Nachdem ich das Objekt oder den Objektgraphen mit dem Kontext verbunden habe, muss der Kontext den Status jedes Objekts lesen. Diese „ConvertState“-Methode verwendet eine „ObjectState“-Enumeration und gibt die passende „EntityState“-Enumeration zurück:

public static EntityState ConvertState(ObjectState state) {
  switch (state) {
    case ObjectState.Added:
      return EntityState.Added;
    case ObjectState.Modified:
      return EntityState.Modified;
    case ObjectState.Deleted:
      return EntityState.Deleted;
    default:
      return EntityState.Unchanged;
  }
}

Als Nächstes benötige ich eine Methode in der „NinjaContext“-Klasse zum Durchlaufen der Entitäten (kurz bevor EF die Daten speichert) und Aktualisieren des Verständnisses des Kontexts des Status jeder Entität gemäß der „State“-Eigenschaft des Objekts. Die hier gezeigte Methode heißt „FixState“:

public class NinjaContext : DbContext
{
  public DbSet<Ninja> Ninjas { get; set; }
  public DbSet<Clan> Clans { get; set; }
  public void FixState() {
    foreach (var entry in ChangeTracker.Entries<IObjectWithState>()) {
      IObjectWithState stateInfo = entry.Entity;
      entry.State = DataUtilities.ConvertState(stateInfo.State);
    }
  }
}

Ich habe erwägt, „FixState“ innerhalb von „SaveChanges“ aufzurufen, damit eine vollständige Automatisierung erfolgt, doch das kann in verschiedenen Szenarien zu Nebenwirkungen führen. Wenn Sie beispielsweise „IObjectState“-Entitäten in einer angeschlossenen Anwendung verwenden, die den lokalen Status nicht festlegt, setzt „FixState“ alle Entitäten stets auf „Unchanged“ zurück. Es ist besser, sie als eine Methode zu belassen, die explizit ausgeführt wird. In „Programming Entity Framework: DbContext“, einem Buch, das ich zusammen mit Rowan Miller verfasst habe, diskutieren wir einige zusätzliche Randfälle, die ggf. von Interesse sind.

Nun erstelle ich eine neue Version des vorherigen Tests, die diese neuen Features nutzt, einschließlich der umfassender definierten Versionen meiner Klassen im Test. Der neue Test gibt vor, dass EF gemischte Status für ein ganz neues „Ninja“-Objekt versteht, das an ein vorhandenes „Clan“-Objekt gebunden ist. Die Testmethode gibt „EntityState“ vor und nach dem Aufrufen von „NinjaContext.FixState“ aus:

[TestMethod]
public void EFComprehendsMixedStatesWhenAddingUntrackedGraph() {
  var ninja = Ninja.CreateIndependent("julie", true);
  ninja.SpecifyClan(new  Clan { Id = 1, ClanName = "Clan from database" });
  using (var context = new NinjaContext()) {
    context.Ninjas.Add(ninja);
    var entries = context.ChangeTracker.Entries();
    OutputState(entries);
    context.FixState();
    OutputState(entries);
    Assert.IsTrue(entries.Any(e => e.State == EntityState.Unchanged));
}

Nach erfolgtem Test zeigt die Ausgabe, dass die „FixState“-Methode den ordnungsgemäßen Status auf das „Clan“-Objekt angewendet hat. Wenn ich jetzt „SaveChanges“ aufrufen würde, würde dieses „Clan“-Objekt nicht versehentlich erneut in die Datenbank eingefügt werden:

Debug Trace:
Before:EF6Model.RichModels.Ninja:Added
Before:EF6Model.RichModels.Clan:Added
After:EF6Model.RichModels.Ninja:Added
After:EF6Model.RichModels.Clan:Unchanged

Durch Verwenden dieses Musters lässt sich auch das zuvor erörterte Problem mit dem „Ninja“-Graphen beheben, bei dem das „Ninja“-Objekt ggf. nicht bearbeitet wurde und beliebige Änderungen am „Equipment“-Objekt (Einfüge-, Änderungs- und Löschvorgänge) nicht erkannt wurden. Abbildung 3 zeigt einen Test zum Prüfen, ob EF ordnungsgemäß bestimmt, ob einer der Einträge geändert wurde.

Abbildung 3: Testen des Status untergeordneter Elemente in einem Graph

[TestMethod]
public void MixedStatesWithExistingParentAndVaryingChildrenisUnderstood() {
  // Arrange
    var ninja = Ninja.CreateIndependent("julie", true);
    var pNinja =new PrivateObject(ninja);
    pNinja.SetProperty("Id", 1);
    var originalOwnerId = 99;
    var equip = Create(originalOwnerId, "arrow");
  // Act
    ninja.TransferEquipmentFromAnotherNinja(equip);
    using (var context = new NinjaContext()) {
      context.Ninjas.Attach(ninja);
      var entries = context.ChangeTracker.Entries();
      OutputState(entries);
      context.FixState();
      OutputState(entries);
  // Assert 
    Assert.IsTrue(entries.Any(e => e.State == EntityState.Modified));
  }
}

Nach erfolgtem Test zeigt die Ausgabe, dass die ursprüngliche „Attach“-Methode bewirkt hat, dass alle Objekt mit „Unchanged“ markiert werden. Nach Aufrufen von „FixState“ hat das „Ninja“-Objekt den Status „Unchanged“ (was weiterhin richtig ist), doch das „Equipment“-Objekt wurde auf „Modified“ festgelegt:

Debug Trace:
Before:EF6Model.RichModels.Ninja:Unchanged
Before:EF6Model.RichModels.NinjaEquipment:Unchanged
After:EF6Model.RichModels.Ninja:Added
After:EF6Model.RichModels.NinjaEquipment:Modified

Was gilt für EF Core?

Selbst nach meinem Wechsel zu EF Core behalte ich dieses Muster in meiner Toolbox. Beim Lösen der Probleme durch getrennte Graphen sind große Fortschritte erfolgt, zumeist in Richtung Bereitstellung konsistenter Muster. In EF Core wird beim Festlegen des Status mit der „DbContext.Entry().State“-Eigenschaft stets nur der Status des Stamms des Graphen festgelegt. Dies ist in vielen Szenarien vorteilhaft. Darüber hinaus gibt es mit „TrackGraph“ eine neue Methode zum „Ablaufen des Graphen“, die jede enthaltene Entität berücksichtigt und auf jede Methode eine angegebene Funktion anwendet. Die einfachste Funktion ist diejenige, die einfach den Status festlegt:

context.ChangeTracker.TrackGraph(Samurai_GK,
  e => e.Entry.State = EntityState.Added);

Stellen Sie sich vor, dass diese Funktion die zuvor erwähnte „FixState“-Methode zum Anwenden des EF-Status basierend auf dem auf Clientseite festgelegten „ObjectState“ nutzt.

Umfangreichere Domänenmodelle vereinfachen das Steuern des Status auf dem Client

Während ich das Erstellen der umfangreicheren Domänenklassen bevorzuge, die den Status bedarfsabhängig aktualisieren, können Sie dieselben Resultate mit einfachen CRUD-Klassen erzielen, solange der Client, der die Klassen verwendet, die Status explizit festlegt. Bei einer manuellen Methode müssen Sie allerdings geänderten Beziehungen mehr Aufmerksamkeit widmen und sicherstellen, dass Änderungen von Fremdschlüsseln berücksichtigt werden.

Ich nutze dieses Muster seit Jahren und habe es in Büchern, auf Konferenzen, bei Kunden und in meinen Pluralsight-Kursen vorgestellt. Zudem weiß ich, dass es für viele Softwarelösungen gern gewählt wird. Unabhängig davon, ob Sie EF5 oder EF6 nutzen oder sich auf EF Core vorbereiten, sollte dieses Muster Ihre Probleme im Zusammenhang mit Ihren getrennten Daten beseitigen.

Self-Tracking Entities

Ein weiteres Feature von EF4.1 war eine T4-Vorlage zum Generieren von „Self-Tracking Entities“ (Entitäten mit Selbstnachverfolgung), die diese neu freigegebenen POCOs auf Normalmaß zurückstutzten. Self-Tracking Entities wurden spezifisch für Szenarien entwickelt, in denen WCF-Dienst e(Windows Communication Foundation) .NET-Clients Daten bereitstellten. Ich war nie ein Fan von Self-Tracking Entities und war froh, als sie heimlich, still und leise aus EF entfernt wurden. Doch einige Entwickler haben darauf gesetzt. Und es gibt verschiedene APIs, die Ihnen diese Vorteile bieten. Tony Sneed hat beispielsweise eine schlankere Implementierung mit dem Namen „Trackable Entities“ entwickelt, die Sie unter trackableentities.github.io finden. IdeaBlade (ideablade.com) ist mit seinem Vorzeigeprodukt DevForce, das EF-Unterstützung bietet, sehr erfolgreich beim Lösen von Problemen mit getrennten Daten. IdeaBlade hat zudem die kostenlosen Open-Source-Produkte Breeze.js und Breeze# entwickelt, die eine Nachverfolgung des Status auf Client- und Serverseite bieten. Über Breeze habe ich zuvor in dieser Kolumne in den Ausgaben Dezember 2012(bit.ly/1WpN0z3) und April 2014 (bit.ly/1Ton1Kg) geschrieben.


Julie Lerman ist Microsoft MVP, .NET-Mentorin und Unternehmensberaterin. Sie lebt in den Bergen von Vermont. Sie hält bei User Groups und Konferenzen in der ganzen Welt Vorträge zum Thema Datenzugriff und anderen .NET-Themen. Julie Lerman führt unter thedatafarm.com/blog einen Blog. Sie ist die Autorin von „Programming Entity Framework“ sowie der Ausgaben „Code First“ und „DbContext“ (alle bei O’Reilly Media erschienen). Folgen Sie ihr auf Twitter: @julielerman, und sehen Sie sich ihre Pluralsight-Kurse unter juliel.me/PS-Videos an.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Rowan Miller