Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort zur 6. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Das Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Strukturen und Enumerationen
7 Fehlerbehandlung und Debugging
8 Auflistungsklassen (Collections)
9 Generics – Generische Datentypen
10 Weitere C#-Sprachfeatures
11 LINQ
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 XML
15 Multithreading und die Task Parallel Library (TPL)
16 Einige wichtige .NET-Klassen
17 Projektmanagement und Visual Studio 2012
18 Einführung in die WPF und XAML
19 WPF-Layout-Container
20 Fenster in der WPF
21 WPF-Steuerelemente
22 Elementbindungen
23 Konzepte von WPF
24 Datenbindung
25 Weitere Möglichkeiten der Datenbindung
26 Dependency Properties
27 Ereignisse in der WPF
28 WPF-Commands
29 Benutzerdefinierte Controls
30 2D-Grafik
31 ADO.NET – Verbindungsorientierte Objekte
32 ADO.NET – Das Command-Objekt
33 ADO.NET – Der SqlDataAdapter
34 ADO.NET – Daten im lokalen Speicher
35 ADO.NET – Aktualisieren der Datenbank
36 Stark typisierte DataSets
37 Einführung in das ADO.NET Entity Framework
38 Datenabfragen des Entity Data Models (EDM)
39 Entitätsaktualisierung und Zustandsverwaltung
40 Konflikte behandeln
41 Plain Old CLR Objects (POCOs)
Stichwort

Download:
- Beispiele, ca. 62,4 MB

Jetzt Buch bestellen
Ihre Meinung?

Spacer
Visual C# 2012 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2012

Visual C# 2012
Rheinwerk Computing
1402 S., 6., aktualisierte und erweiterte Auflage 2013, geb., mit DVD
49,90 Euro, ISBN 978-3-8362-1997-6
Pfeil 40 Konflikte behandeln
Pfeil 40.1 Allgemeine Betrachtungen
Pfeil 40.1.1 Das pessimistische Sperren
Pfeil 40.1.2 Das optimistische Sperren
Pfeil 40.2 Konkurrierende Zugriffe mit dem Entity Framework
Pfeil 40.2.1 Das Standardverhalten des Entity Frameworks
Pfeil 40.2.2 Das Aktualisierungsverhalten mit »Fixed« beeinflussen
Pfeil 40.2.3 Auf die Ausnahme »OptimisticConcurrencyException« reagieren
Pfeil 40.2.4 Das »ClientWins«-Szenario
Pfeil 40.2.5 Das »StoreWins«-Szenario

Rheinwerk Computing - Zum Seitenanfang

40.2 Konkurrierende Zugriffe mit dem Entity FrameworkZur nächsten Überschrift


Rheinwerk Computing - Zum Seitenanfang

40.2.1 Das Standardverhalten des Entity FrameworksZur nächsten ÜberschriftZur vorigen Überschrift

Das Entity Framework unterstützt nur das optimistische Sperren, nicht aber das pessimistische. Das Entity Framework speichert Objektänderungen in der Datenbank, ohne die Parallelität zu überprüfen. Sehen wir uns zuerst das Standardverhalten an, wenn mehrere Benutzer gleichzeitig dieselbe Datenzeile aktualisieren. Dazu dient das folgende Listing.

using (NorthwindEntities context = new NorthwindEntities())
{
var query = context.Products.First(p => p.ProductID == 1);
query.ProductName = "Kuchen";
// Konflikt simulieren
Console.WriteLine("2. User simulieren ...");
Console.ReadLine();
// Änderungen speichern
context.SaveChanges();
Console.WriteLine("DB aktualisiert.");
}

Listing 40.1 Das Standardverhalten bei konkurrierenden Zugriffen

Wir besorgen uns die erste Datenzeile aus der Tabelle Products und ändern die Eigenschaft ProductName. Das Listing erlaubt es, einen zweiten Benutzer zu simulieren. Dazu können Sie beispielsweise in Visual Studio das Fenster Server-Explorer öffnen und, falls nicht schon vorhanden, eine Verbindung zur Datenbank Northwind herstellen. Öffnen Sie dann die Tabelle Products, und editieren Sie die erste Datenzeile. Dabei spielt es keine Rolle, ob Sie die Spalte ProductName editieren oder eine andere. Am Ende wird, nach Fortsetzung des Konsolenprogramms, die Änderung des Produktbezeichners aus der Anwendung heraus in »Kuchen« erfolgreich verlaufen. Es liegt kein Konflikt vor.

Sehen wir uns an, welches SQL-Statement vom Entity Framework gegen die Datenbank abgesetzt wird.

exec sp_executesql N'update [dbo].[Products]
set [ProductName] = @0
where ([ProductID] = @1)
',N'@0 nvarchar(40),@1 int',@0=N'Kuchen',@1=1

Die alles entscheidende WHERE-Klausel enthält nur die Angabe der Primärschlüsselspalte. Solange die Datenzeile in der Datenbank nicht gelöscht worden ist, wird die Aktualisierung zu einem erfolgreichen Abschluss führen.


Rheinwerk Computing - Zum Seitenanfang

40.2.2 Das Aktualisierungsverhalten mit »Fixed« beeinflussenZur nächsten ÜberschriftZur vorigen Überschrift

Nehmen wir an, die beiden Benutzer A und B würden gleichzeitig dieselbe Datenzeile editieren. Dabei müssen Sie aber sicherstellen, dass eine Änderung von Benutzer A im Feld ProductName nicht blindlings von Benutzer B überschrieben wird. Um das zu gewährleisten, muss die Eigenschaft ProductName in die WHERE-Klausel mit aufgenommen werden. Diese Forderung lässt sich sehr einfach umsetzen, wenn man die Eigenschaft ConcurrencyMode (in der deutschen Version von Visual Studio leider in Parallelitätsmodus übersetzt) der Entitätseigenschaft ProductName im Eigenschaftsfenster des EDM-Designers auf Fixed einstellt (siehe Abbildung 40.1). Bei der Verwendung dieses Attributs wird die Datenbank vom Entity Framework vor dem Speichern von Änderungen auf Änderungen hin geprüft.

Abbildung

Abbildung 40.1 Setzen des Parallelitätsmodus einer Eigenschaft

Diese Eigenschaftsänderung bewirkt auch eine Anpassung der Beschreibung der Eigenschaft ProductName im konzeptionellen Modell:

<Property Name="ProductName" Type="String" Nullable="false" MaxLength="40" 
Unicode="true" FixedLength="false" ConcurrencyMode="Fixed" />

Führen Sie das Listing 40.1 mit dieser Änderung noch einmal aus und simulieren den konkurrierenden Zugriff auf ProductName, kommt es zu einer Ausnahme vom Typ OptimisticConcurrencyException. Interessanter ist für uns aber in diesem Moment zunächst das SQL-Statement, das gegen die Datenbank abgesetzt wird:

exec sp_executesql N'update [dbo].[Products]
set [ProductName] = @0
where (([ProductID] = @1) and ([ProductName] = @2))
',N'@0 nvarchar(40),@1 int,@2 nvarchar(40)',@0=N'Kuchen',@1=1,@2=N'Chai'

Es ist zu erkennen, dass die Einstellung Fixed der Eigenschaft ProductName dafür gesorgt hat, dass die Spalte in die WHERE-Klausel aufgenommen wird. Das gilt nicht nur für eine Aktualisierung mit UPDATE, sondern auch dann, wenn eine Datenzeile mit DELETE gelöscht werden soll.

Möchten wir, dass jede Änderung eines anderen Benutzers zu der Ausnahme führt, müssen wir alle Eigenschaften der Entität entsprechend auf Fixed einstellen. In solchen Fällen ist es besser, sich spätestens jetzt Gedanken über eine Timestamp-Spalte in der Tabelle zu machen.


Rheinwerk Computing - Zum Seitenanfang

40.2.3 Auf die Ausnahme »OptimisticConcurrencyException« reagierenZur nächsten ÜberschriftZur vorigen Überschrift

Um auf die Ausnahme OptimisticConcurrencyException des Listings 40.1 zu reagieren, benötigen wir einen entsprechenden try-catch-Block. Zumindest die ausnahmeauslösende Methode SaveChanges muss hier innerhalb des try-Blocks codiert werden.

using (NorthwindEntities context = new NorthwindEntities())
{
var query = context.Products.First(p => p.ProductID == 1);
query.ProductName = "Kuchen";
// Konflikt simulieren
Console.WriteLine("2. User simulieren ...");
Console.ReadLine();
// Änderungen speichern
try
{
context.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
return;
}
Console.WriteLine("DB aktualisiert.");
}

Listing 40.2 Behandlung der Ausnahme »OptimisticConcurrencyException«

Damit behandeln wir zwar die aufgetretene Ausnahme, aber eine Lösung des Konflikts haben wir noch nicht erreicht. Wie könnte die Lösung überhaupt aussehen?

Grundsätzlich stehen Ihnen im Entity Framework zwei allgemeine Lösungsansätze zur Verfügung:

  • Entweder der Benutzer setzt seine Änderungen gegenüber den Änderungen in der Datenbank durch, die ein anderer Benutzer gemacht hat. Dieser Ansatz wird als ClientWins bezeichnet.
  • Die Änderungen des Benutzers werden verworfen. Dieser Ansatz heißt StoreWins.

Wir wollen uns nun diese beiden Konzepte genauer ansehen.


Rheinwerk Computing - Zum Seitenanfang

40.2.4 Das »ClientWins«-SzenarioZur nächsten ÜberschriftZur vorigen Überschrift

Das ClientWins-Szenario arbeitet nach dem folgenden Prinzip: Tritt ein Parallelitätskonflikt auf, werden im ersten Schritt die neuen aktuellen Werte der betreffenden Datenzeile bei der Datenbank abgefragt und zu den neuen Originalwerten der Entität im Objektkontext. Die Current-Werte, die auch die Änderungen durch den Benutzer beinhalten, bleiben unverändert. Zudem werden alle Eigenschaften als Modified gekennzeichnet.

Zur Aktualisierung der Werte der konfliktverursachenden Entität veröffentlicht der Objektkontext die Methode Refresh, die zwei Parameter definiert. Dem ersten Parameter wird entweder die Option RefreshMode.ClientWins oder RefreshMode.StoreWins übergeben. Damit wird festgelegt, wie die Werte der Entität weiter behandelt werden. Der zweite Parameter erwartet die Referenz auf die konfliktverursachende Entität.

Im folgenden Listing wird das ClientWins-Szenario genutzt, um bei einem auftretenden Konflikt die Änderungen des Benutzers gegenüber den zuvor erfolgten Änderungen durchzusetzen.

using (NorthwindEntities context = new NorthwindEntities())
{
var query = (context.Products).FirstOrDefault(p => p.ProductID == 1);
query.ProductName = "Kuchen";
// Konflikt simulieren
Console.WriteLine("2. User simulieren ...");
Console.ReadLine();
try
{
// Änderungen speichern
context.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
context.Refresh(RefreshMode.ClientWins, ex.StateEntries[0].Entity);
context.SaveChanges();
}
Console.WriteLine("DB aktualisiert.");
}

Listing 40.3 Konfliktlösung mit dem »ClientWins«-Ansatz

Im catch-Zweig wird mit der ersten Anweisung zunächst die konfliktverursachende Entität ermittelt. Die entsprechende Information wird durch die Eigenschaft StateEntries des Exception-Objekts bereitgestellt, bei der es sich um eine schreibgeschützte Collection von ObjectStateEntry-Objekten handelt. Danach wird auf den Objektkontext dessen Methode Refresh aufgerufen unter Bekanntgabe der Option RefreshMode.ClientWins.

Die Refresh-Methode sorgt dafür, dass die in dem Moment aktuellen Werte der konfliktverursachenden Datenzeile aus der Datenbank abgerufen werden und an OriginalValues des ObjectStateEntry-Objekts der konfliktverursachenden Entität eingetragen werden. Nun kann erneut die Methode SaveChanges aufgerufen werden. Da nun in der WHERE-Klausel die in dem Moment tatsächlich vorliegenden Werte zur Identifizierung des Datensatzes herangezogen werden, wird die Aktualisierung nun gelingen. Der Client hat sich gegenüber den zuvor erfolgten Änderungen durchgesetzt.

Sie können sich das ansehen, wenn Sie den catch-Zweig des Listings 40.3 wie nachfolgend gezeigt ergänzen:

catch (OptimisticConcurrencyException ex)
{
context.Refresh(RefreshMode.ClientWins, ex.StateEntries[0].Entity);
// Ausgabe der Current-Werte des ObjectStateEntry-Objekts
Console.WriteLine("Current\n" + new string('-', 50));
DbDataRecord actual = errorEntry.CurrentValues;
for (int i = 0; i < actual.FieldCount - 1; i++)
Console.WriteLine("{0,-35}{1}", actual.GetName(i), actual.GetValue(i));
// Ausgabe der Current-Werte des ObjectStateEntry-Objekts
DbDataRecord orig = errorEntry.OriginalValues;
Console.WriteLine("\nOriginal\n" + new string('-', 50));
for (int i = 0; i < orig.FieldCount - 1; i++)
Console.WriteLine("{0,-35}{1}", orig.GetName(i), orig.GetValue(i));
// Daten speichern
context.SaveChanges();
}

Listing 40.4 Änderung des catch-Zweiges aus Listing 40.3

Hinweis

Vielleicht stellen Sie sich die Frage nach dem Unterschied zu dem Szenario, in dem der Konflikt komplett ignoriert wird (die WHERE-Klausel enthält zur Identifizierung der zu ändernden Datenzeile in der Datenbank nur den Primärschlüssel). Die Antwort ist in den Spalten zu finden, die an die Datenbank übermittelt werden. Wird der Parallelitätskonflikt ignoriert, werden im SQL-Aktualisierungsstatement mit SET nur die Felder (Eigenschaften) angegeben, die tatsächlich durch den Benutzer verändert worden sind. Beim Updaten nach dem Aufruf

der Refresh-Methode mit der Option RefreshMode.ClientWins hingegen werden jedoch alle Felder angegeben. Damit werden natürlich alle Änderungen, die ein Benutzer zuvor an einer Datenzeile vorgenommen hat, überschrieben.

Wiederholter Aufruf der Methode »SaveChanges«

Wird im catch-Zweig SaveChanges erneut aufgerufen, besteht die Gefahr, dass erneut eine Ausnahme ausgelöst wird. Auch darauf muss reagiert werden, damit die Anwendung nicht unplanmäßig durch einen nicht behandelten Fehler beendet wird. Verfolgt man den Ablauf weiter, müsste man sehr viele ineinander verschachtelte try-catch-Zweige programmieren.

Das ist natürlich eine schlechte Lösung, ohne dass am Ende die Gewähr besteht, dass eine beliebige Anzahl aufeinander folgender Ausnahmen behandelt werden kann. Hier gibt es einen besseren Lösungsansatz, wenn man die von der Klasse ObjectContext geerbte Methode SaveChanges überschreibt. Bezogen auf unser Entity Data Model, in dem die Klasse NorthwindEntities den Objektkontext beschreibt, müsste eine partielle Klasse bereitgestellt werden, innerhalb deren die Methode SaveChanges rekursiv aufgerufen wird.

Das folgende Codefragment zeigt das Prinzip des rekursiven Aufrufs von SaveChanges.

public partial class NorthwindEntities {
public override int SaveChanges(SaveOptions options)
{
try {
return base.SaveChanges(options);
}
catch (OptimisticConcurrencyException ex) {
Refresh(RefreshMode.ClientWins, ex.StateEntries[0].Entity);
return SaveChanges(options);
}
catch (UpdateException ex) {
throw ex;
}
}
}

Rheinwerk Computing - Zum Seitenanfang

40.2.5 Das »StoreWins«-SzenarioZur vorigen Überschrift

Im zweiten denkbaren Konfliktbehandlungsszenario werden die Benutzerdaten im Objektkontext durch die aktuellen Daten aus der Datenbank ersetzt. Dabei gehen natürlich auch sämtliche Änderungen des Benutzers verloren. So wenig verlockend dieses Szenario im ersten Moment auch klingt, es kann bei einigen Anwendungen durchaus die beste Lösung darstellen. Zwar muss der Benutzer alle seine Daten neu eingeben, aber das kann natürlich auch von der Anwendung übernommen werden – falls die Benutzeränderungen vorher gesichert worden sind.



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.

<< zurück
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Visual C# 2012

Visual C# 2012
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Rheinwerk-Shop: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: C/C++






 C/C++


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern