39.5 Komplexere Szenarien
Entitäten, die vom Objektkontext verwaltet werden, werden per Vorgabe zustandsüberwacht durch Schaltung eines korrespondierenden ObjectStateObject-Objekts. Wir haben uns diesem Thema in diesem Kapitel ausgiebig gewidmet. Allerdings waren die Szenarien sehr einfach, denn sie beschrieben im Grunde genommen nur Clientanwendungen, aus denen heraus direkt mit Hilfe des Entity Frameworks auf die Datenquelle zugegriffen wurde.
Interessanter wird es, wenn die Entitäten über Prozessgrenzen hinweg ausgetauscht werden müssen. Das wäre zum Beispiel in n-Tier-Anwendungen wie dem Webdienst der Fall, der die Clients mit Entitäten versorgt und bei dem die Clients die Entitäten bearbeiten und danach speichern können. Wie Sie wissen, muss dem Zustand der Entitäten beim Austausch über Prozessgrenzen hinweg besondere Aufmerksamkeit geschenkt werden. Wechselt nämlich eine Entität von einem Objektkontext zu einem anderen (und das wäre zum Aktualisieren in einer Webdienstanwendung ausgehend vom Client in Richtung des Webdienstes der Fall), dann muss die vom Client übermittelte Entität dem Objektkontext des Webdienstes mit der Methode Attach hinzugefügt werden. Schlecht ist, dass dabei der Zustand der Entität auf Unchanged wechselt, obwohl sie möglicherweise tatsächlich verändert worden ist.
Sehen wir uns daher abschließend in diesem Kapitel noch an, welche Möglichkeiten der Zustandsbeeinflussung wir auf eine Entität ausüben können und welche Konsequenzen das hat.
39.5.1 Die Methode »ChangeState«

Mit der Methode ChangeState, die auf ein ObjectStateEntry-Objekt aufgerufen wird, können Sie den Zustand einer Entität auf Added, Deleted, Modified oder Unchanged festlegen. Im folgenden Codefragment wird zum Beispiel der Zustand einer Entität auf Added gesetzt.
using (NorthwindEntities context = new NorthwindEntities())
{
var prod = context.Products.First();
ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(prod);
ose.ChangeState(EntityState.Added);
[...]
}
Man muss sich natürlich darüber bewusst sein, dass die manuelle Zustandsänderung Folgen hat. In diesem Fall weist die Entität danach keine Originalwerte mehr auf. Wird SaveChanges anschließend aufgerufen, wird die Entität in die Datenbank geschrieben, weil der Zustand von Unchanged abweicht. Im schlimmsten Fall hätten Sie dann dasselbe Produkt zweimal in der Tabelle Products.
Ändern Sie den Zustand einer unveränderten Entität in Modified, hätte das nicht so gravierende Auswirkungen. Allerdings wird nun jede Eigenschaft der Entität als Modified gekennzeichnet, was zu einem UPDATE-Statement führt, in dem alle Eigenschaften aufgeführt sind. Nur eine Änderung in den Zustand Deleted hat keine negativen Konsequenzen, da der Effekt derselbe ist wie beim Aufruf der Methode DeleteObject.
Unsere Betrachtungen bezogen sich bis hierher auf ursprünglich unveränderte Entitäten. Bei der manuellen Zustandsänderung mit ChangeState ist noch eine weitere Situation zu betrachten. Diese stellt sich ein, wenn der Zustand einer Entität Modified ist und dann auf Unchanged gesetzt wird. In diesem Fall werden alle Current-Werte in die korrespondierenden Originalwerte geschrieben – obwohl man im ersten Moment vermuten würde, der Vorgang müsste sich genau andersherum abspielen.
Summa summarum sollten Sie also die Methode ChangeState nur mit Bedacht aufrufen und sich immer der möglichen Folgen bewusst sein.
39.5.2 Die Methoden »ApplyCurrentChanges« und »ApplyOriginalChanges«
Ein Entitätsobjekt ist fest mit seinem Objektkontext verbunden. Darüber hinaus darf eine Entität auch nur zu einem Objektkontext gehören. Das führt dazu, dass der folgende Code zu einer Ausnahme führt, weil versucht wird, die Entität über den Methodenaufruf von DoSomething einem anderen Objektkontext, hier context2, zu übergeben.
static void Main(string[] args)
{
using (NorthwindEntities context1 = new NorthwindEntities())
{
var prod = context1.Products.First();
prod.ProductName = "Currywurst";
DoSomething(prod);
}
}
static void DoSomething(Product product)
{
using (NorthwindEntities context2 = new NorthwindEntities())
{
context2.Attach(product);
}
}
Es gibt nur eine Möglichkeit, diesen Fehler zu vermeiden, indem man vor dem Aufruf der Methode DoSomething in Main die Entität mit Detach beim ersten Objektkontext deregistriert. Damit lautet der Code in Main wie folgt:
using (NorthwindEntities context1 = new NorthwindEntities())
{
var prod = context1.Products.First();
prod.ProductName = "Currywurst";
context1.Detach(prod);
DoSomething(prod);
}
Widmen wir unser Augenmerk nun dem Code in der Methode DoSomething. Da wir die Entität mit Attach dem Objektkontext context2 hinzufügen, wird der Zustand der Current- und Original-Werte anschließend identisch sein. Dabei geht aber nicht die Änderung der Eigenschaft ProductName verloren. Stattdessen wird der neue Wert (»Currywurst«) an OriginalValue übergeben, wie sich sehr einfach durch Ergänzung der Methode DoSomething beweisen lässt.
static void DoSomething(Product prod)
{
using (NorthwindEntities context2 = new NorthwindEntities())
{
context2.Attach(prod);
ObjectStateEntry ose = context2.ObjectStateManager.GetObjectStateEntry(prod);
Console.WriteLine("Current: {0}", ose.CurrentValues.GetValue(1));
Console.WriteLine("Original: {0}\n", ose.OriginalValues.GetValue(1));
}
}
Mit dieser Erkenntnis müssen wir feststellen, in einem Dilemma zu stecken. Soll nämlich DoSomething dazu dienen, die Entitätsänderung durch Aufruf der Methode SaveChanges in die Datenbank zu schreiben, wird kein UPDATE-Statement erzeugt, weil der Zustand Unchanged lautet.
In dieser Situation hilft uns die Methode ApplyOriginalValues weiter, die entweder auf den Objektkontext oder ein EntitySet aufgerufen werden kann. Sie können die Methode dann benutzen, wenn die zustandsverfolgte Entität zwar die richtigen Current-Werte aufweist, aber die Originalwerte nicht stimmen. Das ist in unserem Beispiel genau der Fall. Sehen wir uns zunächst an, wie die Methode DoSomething implementiert werden muss.
using (NorthwindEntities context2 = new NorthwindEntities())
{
// Original-Entität aus der Datenbank abrufen
var dbprod = context2.Products
.Where(p => p.ProductID == prod.ProductID).Single();
context2.Detach(dbprod);
// Entität zum Objektkontext hinzufügen
context2.Attach(prod);
Console.WriteLine("Zustand (vorher): {0}", prod.EntityState);
// Originalwerte neu festlegen
context2.Products.ApplyOriginalValues(dbprod);
// Ausgabe
ObjectStateEntry ose = context2.ObjectStateManager.GetObjectStateEntry(prod);
Console.WriteLine("Zustand (nachher): {0}", prod.EntityState);
Console.WriteLine("Current: {0}", ose.CurrentValues.GetValue(1));
Console.WriteLine("Original: {0}\n", ose.OriginalValues.GetValue(1));
}
ApplyOriginalChanges benötigt die aktuellen Werte für die übergebene Entität. Dazu wird der Methode ein anderes Objekt übergeben, das die erforderlichen Werte enthält. Dieses Objekt wird als Erstes in der Methode DoSomething von der Datenbank abgerufen.
In einem Objektkontext kann sich zu einem gegebenen Zeitpunkt immer nur ein Objekt mit einer bestimmten Schlüsselinformation befinden. Da wir wissen, dass wir auch die geänderte Entität zur Zustandsverwaltung hinzufügen müssen, wird die von der Datenbank bezogene Entität dbprod beim Objektkontext deregistriert. Dass sich dabei der Zustand in Detached ändert und alle Originalwerte verloren gehen, hat keinen Einfluss. Letztendlich sind die erforderlichen Informationen immer noch unter CurrentValues vorhanden.
Nach dem Aufruf von ApplyOriginalValues hat die Entität alle benötigten Originalwerte, und der Zustand hat sich in Modified geändert. Damit wäre das Objekt so weit vorbereitet, die Änderung am ProductName erfolgreich in die Datenbank zu schreiben.
Sehr ähnlich arbeitet auch die Methode ApplyCurrentValues. Während ApplyOriginalValues aber die Daten eines Objekts an die Originalwerte einer Entität übergibt, sind es mit ApplyCurrentValues die Current-Werte der Entität.
Den Programmcode zu diesem Beispiel finden Sie auf der Buch-DVD unter \Beispiele\Kapitel 39\SeveralObjectContexts.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.