Wie kann ich mit ADO.NET Data Services Änderungsverfolgung realisieren und Datenkonflikte lösen?
Das Problem Änderungsverfolgung
Änderungen an Objekten eines DataContexts müssen dem Context über die Methode UpdateObject mitgeteilt werden. Man kann aber nicht immer feststellen, wann Änderungen an Eigenschaften eines Objekts vorgenommen wurden; beispielsweise, wenn das Objekt an ein DataGrid gebunden ist.
In diesem How-to-Guide wird eine Möglichkeit vorgestellt, wie ein DataContext eine Änderungsverfolgung realisieren kann, sodass beim Aufruf von SubmitChanges alle Änderungen übertragen werden.
Lösung
Für jede Eigenschaft einer Entity erstellen die ADO.NET Data Services die Partiellen Methoden OnPropertyChanging und OnPropertyChanged:
partial class Customers
{ ...
public string Name { get; set; }
partial void OnNameChanging(string value);
partial void OnNameChanged();
...}
- Erstellen Sie für jede Entity eine separate partielle Klasse um die Entity erweitern zu können.
- Fügen Sie dieser Klasse ein Event hinzu, das ausgelöst werden soll, wenn sich eine Eigenschaft geändert hat.
- Implementieren Sie die partielle Methode OnPropertyChanged, die das Event auslöst.
- Erstellen Sie eine separate partielle Klasse für den DataContext.
- Implementieren Sie Handler für die Events ReadingEntity und WritingEntity, in denen Sie einen Handler für das erstellte Event PropertyChanged hinzufügen bzw. entfernen.
- In der Methode, die das PropertyChanged Event behandelt, rufen Sie UpdateObject auf.
partial class Customers
{
public delegate void PropertyChangedHandler(object sender, EventArgs e);
public event PropertyChangedHandler PropertyChanged;
partial void OnNameChanged() { PropertyChanged(this, null); }
... }
partial class MyDataContext
{
partial void OnContextCreated() {
this.ReadingEntity += new EventHandler<MReadingWritingEntityEventArgs>(ReadingEntity);
this.WritingEntity += new EventHandler<ReadingWritingEntityEventArgs>(WritingEntity);
}
private void ReadingEntity(object sender, ReadingWritingEntityEventArgs e) {
((Customers)e.Entity).Property += new Customers.PropertyChangedHandler(PropChanged);
}
private void WritingEntity(object sender, ReadingWritingEntityEventArgs e) {
((Customers)e.Entity).Property -= new Customers.PropertyChangedHandler(PropChanged);
}
private void PropChanged(object sender, EventArgs e) {
this.UpdateObject(sender);
} ... }
Das Problem Konfliktlösung
Wenn mehrere Clients zeitgleich auf denselben Datenbestand zugreifen, kann es passieren, dass der eine Änderungen des anderen zufällig überschreibt. In diesem How-to-Guide wird gezeigt, was ADO.NET Data Services bietet um solche Konflikte zu erkennen, und eine Möglichkeit vorgestellt Konflikte zu lösen.
Im Datenmodell kann für eine Tabellenspalte die Eigenschaft „Parallelitätsmodus“ auf Fixed gesetzt werden (siehe Abbildung 1) und wird beim Übertragen via REST zusätzlich als ETag übermittelt. Weicht der Wert des ETags von dem Spaltenwert in der Datenquelle ab, wird ein Konflikt vermutet und eine Exception ausgelöst.

Abbildung 1: „Parallelitätsmodus“ auf Fixed gesetzt
- Fangen Sie die mögliche Exception beim Aufruf von SubmitChanges ab.
- Die ChangeOperationResponse innerhalb der Exception liefert Informationen über die Entities, welche den Konflikt verursacht haben.
- Rufen Sie diese Entities in einem neuen Kontext ab. Dabei erhalten Sie auch den aktuellen ETag.
- Lösen Sie den Konflikt, in dem Sie ihn beispielsweise dem Benutzer visuell darstellen und so eine Zusammenführung der Versionen ermöglichen.
- Detachen Sie die lokale Entity vom Kontext und Attachen Sie die Entity, in welcher der Konflikt gelöst wurde, mit dem aktuellen ETag.
- Rufen Sie erneut SubmitChanges auf.
Nächste Schritte
Sowohl die Änderungsverfolgung als auch die Konfliktlösung sind aufwändig zu implementieren. Daher haben wir eine Vorlagendatei erstellt, die den nötigen Code automatisch generiert.
Um die Features Änderungsverfolgung und Konfliktlösung zu verwenden, kopieren Sie die Datei DataServiceExtension.tt in Ihr Projekt und klicken Sie im Projektmappen-Explorer auf „Alle Vorlagen Transformieren“ (siehe Abbildung 2). Für jede Service Reference in Ihrem Projekt werden die nötigen Klassen erstellt und Sie können diese direkt verwenden.

Abbildung 2: Alle Vorlagen Transformieren
Zur Konfliktlösung gehen Sie wie folgt vor:
List<Conflict> conflicts = ctx.SaveChangesWithErrorDetection();
foreach(var conflict in conflicts) {
// hier Code zur Konfliktlösung einfügen (localObject oder databaseObject bearbeiten)
// Festlegen, welches Objekt übernommen werden soll
conflict.ResolveAction = ResolveActions.LoadEntityFromServer;
}
ctx.ResolveConflicts(conflicts);
ctx.SubmitChanges();
Die Klasse Conflict bietet Ihnen die Möglichkeit auf die Entity zuzugreifen, wie sie momentan lokal vorhanden ist und auf die aktuelle Variante, wie sie in der Datenbank vorliegt. Sie können nun deren Eigenschaften vergleichen, Ihrem Benutzer anzeigen und zusammenführen. Im Anschluss können Sie für jeden Konflikt festlegen, welche Variante übernommen werden soll. Das lokale Objekt oder die Variante der Datenbank.