Galileo Computing < openbook > Galileo 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

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 35 ADO.NET – Aktualisieren der Datenbank
Pfeil 35.1 Aktualisieren mit dem »CommandBuilder«
Pfeil 35.1.1 Die von »SqlCommandBuilder« generierten Aktualisierungsstatements
Pfeil 35.1.2 Konfliktsteuerung in einer Mehrbenutzerumgebung
Pfeil 35.1.3 Die Eigenschaft »ConflictOption« des »SqlCommandBuilders«
Pfeil 35.1.4 Die Eigenschaft »SetAllValues«
Pfeil 35.2 Manuell gesteuerte Aktualisierung
Pfeil 35.2.1 Eigene Aktualisierungslogik
Pfeil 35.2.2 Das Beispielprogramm
Pfeil 35.3 Konfliktanalyse
Pfeil 35.3.1 Den Benutzer über fehlgeschlagene Aktualisierungen informieren
Pfeil 35.3.2 Konfliktverursachende Datenzeilen bei der Datenbank abfragen
Pfeil 35.4 Neue Autoinkrementwerte abrufen

Galileo Computing - Zum Seitenanfang

35.3 KonfliktanalyseZur nächsten Überschrift

Stehen mehrere Datenzeilen zur Aktualisierung an, wird der DataAdapter versuchen, eine nach der anderen an die Datenbank zu senden. Wie im letzten Beispielprogramm zu sehen war, wird der DataAdapter eine DBConcurrencyException auslösen und die verbleibenden Änderungen nicht mehr an die Datenbank schicken. Das ist das Standardverhalten.

Sie können den DataAdapter anweisen, nach einem etwaigen Konflikt seine Aufgabe fortzusetzen und die verbleibenden Änderungen zu übermitteln. Dazu setzen Sie seine Eigenschaft ContinueUpdateOnError=true. Eine Exception wird in diesem Fall nicht ausgelöst. Stattdessen stehen Ihnen zwei andere Optionen zur Verfügung, mit den aufgetretenen Konflikten umzugehen:

  • Sie informieren den Benutzer lediglich, welche Datenzeilen nicht aktualisiert werden konnten.
  • Sie implementieren über die reine Information des fehlgeschlagenen Aktualisierungsversuchs hinaus auch eine Konfliktlösung. Dazu benötigt der Benutzer alle zur Verfügung stehenden Informationen, unter anderem auch diejenige, wie der neue aktuelle Inhalt der konfliktverursachenden Datenzeile in der Datenbank ist.

Beide Szenarien wollen wir uns nun ansehen.


Galileo Computing - Zum Seitenanfang

35.3.1 Den Benutzer über fehlgeschlagene Aktualisierungen informierenZur nächsten ÜberschriftZur vorigen Überschrift

Legen Sie vor dem Aufruf der Update-Methode die Eigenschaft ContinueUpdateOnError auf true fest, verursacht ein fehlgeschlagener Aktualisierungsversuch keine Ausnahme mehr. Stattdessen wird die Eigenschaft HasErrors des entsprechenden DataRow-Objekts auf true gesetzt, ebenso die gleichnamige Eigenschaft des DataSets und der DataTable. Eine DataRow hat eine Eigenschaft RowError. Diese enthält nach dem misslungenen Versuch eine Fehlermeldung.

Im folgenden Beispielprogramm wird der Einsatz der Eigenschaften ContinueUpdateOnError, HasErrors und RowError gezeigt. Das Beispielprogramm setzt dasjenige des vorhergehenden Abschnitts fort und ergänzt nur noch die notwendigen Passagen.

// Beispiel: ..\Kapitel 35\HasErrorsSample

static void Main(string[] args) {
[...]

// Simulation eines Konflikts

Console.Write("Konflikt simulieren ...");
Console.ReadLine();

// Datenbank aktualisieren

da.ContinueUpdateOnError = true;
da.Update(ds);
if (ds.HasErrors) {
string text = "Folgende Zeilen konnten nicht aktualisiert werden:";
foreach (DataRow row in ds.Tables[0].Rows)
if (row.HasErrors) {
Console.WriteLine(text);
Console.WriteLine("ID: {0}, Fehler: {1}", row["ProductID"], row.RowError);
}
}
else
Console.WriteLine("Die Aktualisierung war erfolgreich.");
Console.ReadLine();
}

Listing 35.5 Code des Beispielprogramms »HasErrorsSample«

Nach dem Aufruf von Update auf den DataAdapter wird zuerst mit

if (ds.HasErrors)

das DataSet dahingehend untersucht, ob tatsächlich ein Konflikt vorliegt. HasErrors ist false, wenn die Datenbank die Änderungen angenommen hat. true signalisiert hingegen, dass wir alle Datenzeilen in der Tabelle des DataSets durchlaufen müssen, um die konfliktverursachenden Zeilen zu finden. Der Code wird fündig, wenn er auf eine Datenzeile mit HasErrors=true trifft.

foreach (DataRow row in ds.Tables[0].Rows)
if (row.HasErrors) {
[...]
}

Jetzt können wir reagieren. Im einfachsten Fall lassen wir uns zumindest die ID des betreffenden »Übeltäters« ausgeben – so wie in diesem Beispiel. Sie können die Information natürlich auch dazu benutzen, dem Benutzer die Möglichkeit zu geben, die Konfliktursache zu beseitigen, denn der Verursacher ist ermittelt.


Galileo Computing - Zum Seitenanfang

35.3.2 Konfliktverursachende Datenzeilen bei der Datenbank abfragenZur vorigen Überschrift

Meist genügt es nicht, nur zu wissen, wer Konfliktverursacher ist. Es wird darüber hinaus auch eine Lösung angestrebt. Dies bedarf aber einer genaueren Analyse der Umstände, die zu einem Konflikt führen können. Dabei sind vier Situationen zu beachten:

  • Ein Anwender versucht, eine Datenzeile mit einem neuen Primärschlüssel hinzuzufügen, der bereits in der Tabelle existiert.
  • Ein Anwender versucht, einen Datensatz zu ändern, den ein zweiter Benutzer zuvor geändert hat.
  • Es wird versucht, eine Datenzeile zu ändern, die zwischenzeitlich gelöscht worden ist.
  • Es wird versucht, eine Datenzeile zu löschen, die bereits gelöscht ist. In der Regel wird man aber diesem Konflikt keine Beachtung schenken müssen.

Wird versucht, einen bereits vorhandenen Primärschlüssel für einen neuen Datensatz ein zweites Mal zu vergeben, scheint die Lösung des Problems noch recht einfach zu sein: Es muss nur ein anderer Primärschlüssel vergeben werden. Aber das könnte eine falsche Entscheidung sein. Können Sie denn sicherstellen, dass nicht zwei Anwender versuchen, den gleichen Datensatz zur Tabelle hinzuzufügen? Falls Sie diese Situation nicht berücksichtigen, liegen im schlimmsten Fall zwei identische Datensätze vor.

Um eine präzise Konfliktlösung der beiden anderen relevanten Konflikte zu ermöglichen, fehlen uns Informationen, die nur in der Datenbank zu finden sind. Was wir brauchen, ist eine neue Originalversion der konfliktverursachenden Datenzeile.

Der DataAdapter hilft uns an dieser Stelle weiter. Er löst nämlich für jede zu aktualisierende Datenzeile zwei Ereignisse aus, wenn anstehende Änderungen über die Methode Update an die Datenbank übermittelt werden:

  • RowUpdating
  • RowUpdated

RowUpdating wird ausgelöst, bevor eine Zeile übermittelt wird, RowUpdated tritt unmittelbar nach der Übermittlung auf.

Für unsere Lösung interessiert uns natürlich nur das Ereignis RowUpdated, dessen zweiter Parameter vom Typ SqlRowUpdatedEventArgs uns mit allen Informationen versorgt, die wir zur Konfliktanalyse und der anschließenden Konfliktlösung benötigen. In Tabelle 35.2 sind die Eigenschaften des EventsArgs-Parameters des RowUpdated-Ereignisses aufgeführt.

Tabelle 35.2 Die Eigenschaften des »SqlRowUpdatedEventArgs«-Objekts

Eigenschaft Beschreibung

Command

Ruft das beim Aufruf von Update ausgeführte SqlCommand ab.

Errors

Ruft alle Fehler ab, die während der Ausführung generiert wurden.

RecordsAffected

Ruft die Anzahl der durch die Ausführung der SQL-Anweisung geänderten, eingefügten oder gelöschten Zeilen ab.

Row

Ruft die durch ein Update gesendete DataRow ab.

StatementType

Ruft den Typ der ausgeführten SQL-Anweisung ab.

Status

Ruft einen Wert der Enumeration UpdateStatus ab oder legt diesen fest.

TableMapping

Ruft das durch ein Update gesendete DataTableMapping ab.

Der Vollständigkeit halber folgt jetzt auch noch die Tabelle mit den Membern der Enumeration UpdateStatus, die von der Eigenschaft Status des SqlRowUpdatedEventArgs-Objekts offengelegt wird.

Tabelle 35.3 Die Member der Enumeration »UpdateStatus«

Member Beschreibung

Continue

Der DataAdapter soll mit der Verarbeitung von Zeilen fortfahren.

ErrorsOccured

Der Ereignishandler meldet, dass die Aktualisierung als Fehler behandelt werden soll.

SkipAllRemainingRows

Die aktuelle Zeile und alle restlichen Zeilen sollen nicht aktualisiert werden.

SkipCurrentRow

Die aktuelle Zeile soll nicht aktualisiert werden.

Wie können wir nun das Ereignis zu unserem Nutzen einsetzen?

Es gilt zunächst herauszufinden, ob das Update einer Datenzeile zu einem Konflikt geführt hat. Hierzu prüfen wir, ob die Eigenschaft Status des SqlRowUpdatedEventArgs-Objekts den Enumerationswert UpdateStatus.ErrorsOccured aufweist.

private void da_RowUpdated(object sender, SqlRowUpdatedEventArgs e) {
if (e.Status == UpdateStatus.ErrorsOccurred) {
[...]
}
}

Listing 35.6 Prüfen, ob ein Fehler beim Aktualisieren aufgetreten ist

Um zu einer Konfliktlösung zu kommen, werden die konfliktverursachenden Datenzeilen in ihrer neuen, aktuellen Originalversion aus der Datenbank benötigt. Deshalb wird innerhalb des Ereignishandlers eine erneute Abfrage an die Datenbank geschickt und der Primärschlüssel der konfliktverursachenden Datenzeile als Filter benutzt. Da uns das EventArgs-Objekt in seiner Eigenschaft Row die Referenz auf die entsprechende Datenzeile mitteilt, stellt das kein Problem dar.

Sinnvollerweise stellt man ein eigenes DataSet-Objekt für alle abgefragten Datenzeilen zur Verfügung (hier: dsConflict), ebenso einen separaten SqlDataAdapter (hier: daConflict). Der Aufruf der Fill-Methode bewirkt, dass entweder genau eine Datenzeile das Ergebnis des Aufrufs bildet oder keine. Die Fill-Methode teilt uns über ihren Rückgabewert die Anzahl der Datensätze mit, die die Ergebnismenge bilden. Der Rückgabewert ist ganz entscheidend, um festzustellen, welche Ursache der Konflikt hat. Sehen wir uns nun zuerst das entsprechende Codefragment zu dem Gesagten an:

DataSet dsConflict = new DataSet();
SqlDataAdapter daConflict = new SqlDataAdapter();

[...]

private void da_RowUpdated(object sender, SqlRowUpdatedEventArgs e) {
if (e.Status == UpdateStatus.ErrorsOccurred) {
SqlCommand cmdConflict = new SqlCommand();
string sql = "SELECT ProductID, ProductName, UnitPrice, " +
"UnitsInStock FROM Products WHERE ProductID = " +
e.Row["ProductID"];
cmdConflict = new SqlCommand();
cmdConflict.Connection = con;
cmdConflict.CommandText = sql;
daConflict.SelectCommand = cmdConflict;
int result = daConflict.Fill(dsConflict);
}
}

Listing 35.7 Abrufen der konfliktverursachenden Datenzeile aus der Datenbank

Grundsätzlich können beim Aktualisieren einer Datenzeile zwei verschiedene Exceptions auftreten:

  • SqlException
  • DBConcurrencyException

SqlException beschreibt Ausnahmen, die der SQL Server zurückgibt. Das wäre beispielsweise der Fall, wenn ein Datensatz mit einem Primärschlüssel hinzugefügt wird, der in der Tabelle bereits existiert.

DBConcurrencyException hingegen wird ausgelöst, wenn eine Parallelitätsverletzung vorliegt. Das ist der Fall, wenn die Anzahl der aktualisierten Datenzeilen 0 ist. Um festzustellen, welche Ausnahme ausgelöst worden ist, brauchen Sie nur den in der Eigenschaft Errors des SqlRowUpdatedEventArgs-Objekts enthaltenen Ausnahmetyp zu untersuchen.

if (e.Errors.GetType() == typeof(SqlException)) {
[...]
}
else if (e.Errors.GetType() == typeof(DBConcurrencyException)) {
[...]
}

Anschließend kommt es zur Auswertung der Anzahl der Datensätze, die in der Variablen result stehen. Es kann sich nur um die Zahl 0 oder 1 handeln, woraus weitere Rückschlüsse gezogen werden können.

Betrachten wir zuerst den Fall, dass Errors ein SqlException-Objekt enthält.

if (e.Errors.GetType() == typeof(SqlException)) {
if (result == 1)
Console.WriteLine("Der PS existiert bereits.");
}

Ist in diesem Fall der Inhalt von result 1, handelt es sich um den Versuch, einen neuen Datensatz mit einem Primärschlüssel hinzuzufügen, wobei der Primärschlüssel in der Originaltabelle bereits vergeben ist. Beschreibt result die Zahl 0, liegt ein anderer Datenbankfehler vor.

Handelt es sich um den Ausnahmetyp DBConcurrencyException, wurde der Versuch, die Änderung an einer Datenzeile in die Originaltabelle zu schreiben, abgelehnt. Die Parallelitätsverletzung kann zwei Ursachen haben:

  • Ein anderer Anwender hat den Datensatz zwischenzeitlich geändert. Der Inhalt der Variablen result muss in diesem Fall die Zahl 1 sein. Mit anderen Worten: Der Datensatz existiert noch.
  • Wird der Inhalt von result mit der Zahl 0 beschrieben, wurde der Datensatz von einem anderen Anwender gelöscht. Der entsprechende Primärschlüssel existiert nicht mehr.

Der else if-Zweig muss demnach wie folgt codiert werden:

[...]
else if (e.Errors.GetType() == typeof(DBConcurrencyException)) {

// ist Anzahl=1 -> anderer Benutzer hat DS geändert

if (result == 1)
Console.WriteLine("Ein anderer User hat den Datensatz geändert.");
else
Console.WriteLine("Datensatz existiert nicht in der Datenbank.");
}

Das folgende Beispielprogramm zeigt den Code im Zusammenhang. Ausgangspunkt sei wieder das Beispielprogramm ManuelleAktualisierung, das entsprechend ergänzt wird. Neben der Implementierung des Ereignishandlers des RowUpdated-Ereignisses wird nach der Aktualisierung auch das DataSet mit allen konfliktverursachenden Datenzeilen abgefragt und ausgegeben.

// Beispiel: .. \Kapitel 35\KonfliktAnalyse

class Program {
static DataSet dsConflict = new DataSet();
static SqlDataAdapter daConflict = new SqlDataAdapter();
static SqlConnection con = new SqlConnection();

static void Main(string[] args) {
[...]

// Datenbank aktualisieren

da.ContinueUpdateOnError = true;
da.RowUpdated += new SqlRowUpdatedEventHandler(da_RowUpdated);
da.Update(ds);

// Konflikt-DataSet abrufen

if (dsConflict.Tables.Count > 0) {
Console.WriteLine("\n{0,-5}{1,-35}{2,-12}{3}",
"ID", "ProductName", "UnitPrice", "UnitsInStock");
Console.WriteLine(new string('-', 65));
foreach (DataRow item in dsConflict.Tables[0].Rows)
Console.WriteLine("{0,-5}{1,-35}{2,-12}{3}",
item["ProductID"], item["ProductName"],
item["UnitPrice"], item["UnitsInStock"]);
}
Console.ReadLine();
}

static void da_RowUpdated(object sender, SqlRowUpdatedEventArgs e) {
if (e.Status == UpdateStatus.ErrorsOccurred) {
SqlCommand cmdConflict = new SqlCommand();
string sql = "SELECT ProductID, ProductName, UnitPrice, UnitsInStock FROM
Products WHERE ProductID = " + e.Row["ProductID"];
cmdConflict = new SqlCommand();
cmdConflict.Connection = con;
cmdConflict.CommandText = sql;
daConflict.SelectCommand = cmdConflict;
int result = daConflict.Fill(dsConflict);
if (e.Errors.GetType() == typeof(SqlException)) {

// Prüfen, ob es einen DS mit einem bestimmten PS gibt

if (result == 1)
Console.WriteLine("Der PS existiert bereits.");
}
else if (e.Errors.GetType() == typeof(DBConcurrencyException)) {

// ist Anzahl=1 -> anderer Benutzer hat DS geändert

if (result == 1)
Console.WriteLine("Ein anderer User hat den Datensatz geändert.");
else
Console.WriteLine("Datensatz existiert nicht in der Datenbank.");
}
}

}
}

Listing 35.8 Analyse bei einem Konflikt



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual C# 2012

Visual C# 2012
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: C++ Handbuch






 C++ Handbuch


Zum Katalog: C/C++






 C/C++


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
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.


[Rheinwerk Computing]

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