27.3 Benutzer über fehlgeschlagene Aktualisierungen informieren 

Vielleicht sind Sie auf die Idee gekommen, mit dem Beispielprogramm ZuFuss einen Parallelitätskonflikt zu simulieren. Sie werden dann festgestellt haben, dass im Konfliktfall keine Ausnahme ausgelöst wird. Zudem werden auch alle nach dem Konfliktfall anstehenden Änderungen in der Datenbank vorgenommen.
Ganz anders operiert der DbDataAdapter, der die Zeilen einzeln an die Datenbank schickt. Im Konfliktfall löst er eine DBConcurrencyException aus. Die verbleibenden Änderungen werden nicht mehr an die Datenbank geschickt. So ist das Standardverhalten.
Die folgende Eigenschaft weist den DataAdapter an, nach einem Konflikt seine Aufgabe fortzusetzen und auch die verbleibenden Änderungen zu übermitteln.
da.ContinueUpdateOnError = True
Das hat weitreichende Konsequenzen, denn nun 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 auch eine Eigenschaft RowError. Diese enthält nach dem misslungenen Versuch eine Fehlermeldung.
Im folgenden Beispielcode wird der Einsatz der Eigenschaften ContinueUpdateOnError, HasErrors und RowError gezeigt. Das nötige Command-Objekt wird von der schon bekannten Methode CreateUpdateCommand generiert.
Für eine korrekte Arbeitsweise setzt das Beispiel voraus, dass die Products-Datenbank in ihrem Ursprungszustand ist. Zuerst werden die Daten von der Datenbank eingelesen. Eine Datenzeile wird in der Datenbank geändert, um einen Konflikt zu provozieren. Danach wird die gleiche Zeile lokal geändert und die Datenbank synchronisiert. Danach werden Fehler protokolliert.
'...\ADO\Aktualisierung\Konflikt |
Option Strict On
Imports System.Data.Common, System.Data.SqlClient
Namespace ADO
Module Konflikt
Sub Test()
Dim con As DbConnection = New SqlConnection()
con.ConnectionString = "Data Source=(local);" & _
"Initial Catalog=Northwind;Integrated Security=sspi"
Dim cmd As DbCommand = New SqlCommand()
cmd.CommandText = "SELECT ProductID, ProductName, " & _
"UnitPrice, Discontinued FROM Products"
cmd.Connection = con
Dim ds As New DataSet()
Dim da As DbDataAdapter = New SqlDataAdapter()
da.SelectCommand = cmd
da.Fill(ds)
' Konflikt simulieren
cmd.CommandText = _
"UPDATE Products SET ProductName='Tee' WHERE ProductID=1"
con.Open() : cmd.ExecuteNonQuery() : con.Close()
' Änderungen durchführen
ds.Tables(0).Select("ProductID=1")(0)("ProductName") = "Kräutertee"
da.UpdateCommand = CreateUpdateCommand(con)
da.ContinueUpdateOnError = True
da.Update(ds)
' auf Konflikte prüfen
If ds.HasErrors Then
For Each row As DataRow In ds.Tables(0).Rows
If row.HasErrors Then Console.WriteLine( _
"ID: {0}, Fehler: {1}", row("ProductID"), row.RowError)
Next
Else
Console.WriteLine("Aktualisierung erfolgreich.")
End If
Console.ReadLine()
End Sub
Function CreateUpdateCommand(ByVal con As DbConnection) As DbCommand ...
End Module
End Namespace
Nach dem Aufruf von Update auf den DataAdapter wird zuerst mit
If ds.HasErrors Then ...
getestet, ob überhaupt ein Konflikt im DataSet vorliegt. True signalisiert, dass irgendeine Datenzeilenaktualisierung fehlgeschlagen ist und dass wir alle Datenzeilen in der Tabelle des DataSets durchlaufen müssen, um die konfliktverursachenden Zeilen zu finden.
For Each row As DataRow In ds.Tables(0).Rows
If row.HasErrors Then ...
Next
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 Chance zu geben, die Konfliktursache zu beseitigen, denn der Verursacher ist ermittelt.
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.