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

Inhaltsverzeichnis
1 Einführung
2 Grundlagen der Sprachsyntax
3 Klassendesign
4 Weitere Datentypen
5 Multithreading
6 Collections und LINQ
7 Eingabe und Ausgabe
8 Anwendungen: Struktur und Installation
9 Code erstellen und debuggen
10 Einige Basisklassen
11 Windows-Anwendungen erstellen
12 Die wichtigsten Steuerelemente
13 Tastatur- und Mausereignisse
14 MDI-Anwendungen
15 Grafiken mit GDI+
16 Drucken
17 Entwickeln von Steuerelementen
18 Programmiertechniken
19 WPF – Grundlagen
20 Layoutcontainer
21 WPF-Steuerelemente
22 Konzepte von WPF
23 Datenbankverbindung mit ADO.NET
24 Datenbankabfragen mit ADO.NET
25 DataAdapter
26 Offline mit DataSet
27 Datenbanken aktualisieren
28 Stark typisierte DataSets
A Anhang: Einige Übersichten
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual Basic 2008 von Andreas Kuehnel, Stephan Leibbrandt
Das umfassende Handbuch
Buch: Visual Basic 2008

Visual Basic 2008
3., aktualisierte und erweiterte Auflage, geb., mit DVD
1.323 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1171-0
Pfeil 27 Datenbanken aktualisieren
Pfeil 27.1 Aktualisieren mit CommandBuilder
Pfeil 27.1.1 Parallelitätskonflikt
Pfeil 27.1.2 Aktualisierungsbefehle des DbCommandBuilders
Pfeil 27.1.3 Aktualisierungsoptionen des DbCommandBuilders
Pfeil 27.1.4 Vor- und Nachteile des DbCommandBuilders
Pfeil 27.2 Manuell gesteuerte Aktualisierungen
Pfeil 27.2.1 Manuelles Aktualisieren mit dem DataAdapter
Pfeil 27.2.2 Aktualisieren mit ExecuteNonQuery
Pfeil 27.3 Benutzer über fehlgeschlagene Aktualisierungen informieren
Pfeil 27.4 Konfliktverursachende Datenzeilen bei der Datenbank abfragen
Pfeil 27.5 DataSet mit der Datenbank synchronisieren
Pfeil 27.5.1 UpdatedRowSource in DbCommand
Pfeil 27.6 Hierarchische Änderungen an die Datenbank übermitteln
Pfeil 27.6.1 Datenbank auslesen
Pfeil 27.6.2 Änderung
Pfeil 27.6.3 Bestellung einfügen
Pfeil 27.6.4 Bestelldetails einfügen
Pfeil 27.6.5 Wiederherstellen der Datenbank


Galileo Computing - Zum Seitenanfang

27.4 Konfliktverursachende Datenzeilen bei der Datenbank abfragen topZur vorigen Überschrift

Damit nicht nur der Konfliktverursacher ermittelt wird, sondern auch ein Lösungsansatz gefunden werden kann, muss auch die Ursache analysiert werden. Warum wurde die Aktualisierung einer geänderten Datenzeile von der Datenbank abgelehnt? Beispielsweise könnte ein anderer Anwender seinerseits den Datensatz geändert haben, oder der Datensatz existiert gar nicht mehr, weil er in der Originaltabelle gelöscht worden ist. Erst mit genauerer Kenntnis der Ursache kann der Anwender unter Berücksichtigung der Konfliktsituation entscheiden, ob er seine Änderungen doch noch in die Datenbank schreiben oder verwerfen will.

Der Schlüssel zur Lösung ist aber nicht im aktuellen DataSet zu finden, sondern in der Datenbank selbst. Was wir brauchen, ist eine neue Originalversion der konfliktverursachenden Datenzeile. Jetzt hilft uns der DataAdapter 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

Hinweis
Nicht die abstrakte Klasse DbDataAdapter, sondern die providerspezifischen Klassen veröffentlichen die Ereignisse.


RowUpdating wird ausgelöst, bevor eine Zeile übermittelt wird, RowUpdated tritt unmittelbar nach der Übermittlung auf. Zur Konfliktanalyse untersuchen wir im Ereignishandler des RowUpdated-Ereignisses den zweiten Parameter vom Typ RowUpdatedEventArgs (siehe Tabelle 27.2).


Tabelle 27.2 Eigenschaften von »RowUpdatedEventArgs« (R = ReadOnly, U = auch in RowUpdatingEventArgs)

Eigenschaft Beschreibung

Command

Das beim Aufruf von Update ausgeführte Command

RU

Errors

Fehler, die während der Ausführung generiert wurden

U

RecordsAffected

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

R

Row

Die durch ein Update gesendete DataRow

RU

RowCount

Die Anzahl betroffener Zeilen

R

StatementType

Der Typ der ausgeführten SQL-Anweisung

RU

Status

Der Zustand vom Typ der Enumeration UpdateStatus

U

TableMapping

Das durch ein Update gesendete DataTableMapping

RU



Hinweis
Die Datenprovider leiten die Klasse RowUpdatedEventArgs ab. Die abgeleitete Klasse hat den Namen des Datenproviders als Präfix (Sql, OleDb, Odbc, Oracle).


Die Eigenschaft Status kann die Werte aus Tabelle 27.3 annehmen:


Tabelle 27.3 Konstanten der Enumeration »UpdateStatus«

Konstante Beschreibung

Continue

DataAdapter soll mit der Verarbeitung von Zeilen fortfahren.

ErrorsOccured

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

SkipAllRemainingRows

Keine Aktualisierung der aktuellen und aller restlichen Zeilen

SkipCurrentRow

Keine Aktualisierung der aktuellen Zeile


Wie nutzen wir nun das Ereignis?

Beide Ereignisse werden unabhängig vom Erfolg der Synchronisation der Datenbank ausgelöst. Wir müssen also selbst feststellen, ob die Aktualisierung einer Datenzeile zu einem Konflikt geführt hat. In diesem Fall hat die Eigenschaft Status des RowUpdatedEventArgs-Objekts den Enumerationswert UpdateStatus.ErrorsOccured.

Sub RowUpdated(object sender, SqlRowUpdatedEventArgs ev) 
  If ev.Status = UpdateStatus.ErrorsOccurred Then 
    ... 
  End If 
End Sub

Alle konfliktverursachenden Datenzeilen können in einem DataSet zusammengefasst werden, das sich nach der Aktualisierungsoperation aller geänderten Datenzeilen auswerten lässt. Dazu müssen die aktuellen Daten der konfliktverursachenden Datenzeilen aus der Originaldatenbank gelesen werden. Mit den dann vorliegenden aktuellen Daten haben wir eine Basis für eine Konfliktlösung.

Im Ereignishandler des RowUpdated-Ereignisses wird deshalb eine parametrisierte Abfrage spezifiziert, die gegen die Datenbank abgesetzt wird. Dabei wird im Suchkriterium der WHERE-Klausel nur der Primärschlüssel der konfliktverursachenden Datenzeile als Parameter verwendet. Die Zeile liefert uns die Eigenschaft Row des RowUpdatedEventArgs-Parameters. Abgefragt werden von der Datenbank die Inhalte aller Spalten, die im Zusammenhang mit der Aktualisierung stehen.

Dim cmd As DbCommand = New SqlCommand() 
cmd.CommandText = "SELECT ProductID, ProductName, " & _ 
   "UnitPrice, Discontinued FROM Products WHERE ProductID = @ID" 
cmd.Connection = con 
cmd.Parameters.Add(New SqlParameter("@ID", SqlDbType.Int, 4, "ProductID")) 
daConflict.SelectCommand = cmd 
daConflict.SelectCommand.Parameters(0).Value = ev.Row("ProductID")

daConflict ist hierbei die Referenz auf ein DbDataAdapter-Objekt. Er füllt ein DataSet, in dem nur die nun aktuellen Inhalte der konfliktverursachenden Datenzeilen enthalten sind. Nennen wir dieses DataSet »Konflikt-Dataset«.

Beim Aktualisieren einer Datenzeile können zwei verschiedene Ausnahmen auftreten:

  • DbException
  • DBConcurrencyException

DbException tritt auf, wenn die Datenbank einen Fehler zurückgibt. Das ist 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. Dann ist die Anzahl der aktualisierten Datenzeilen 0.

Die Eigenschaft Errors des RowUpdatedEventArgs-Objekts speichert eine aufgetretene Ausnahme und ist Nothing, wenn keine Fehler aufgetreten sind. Damit kann mit Errors der Fehlertyp ermittelt werden, der nicht unbedingt mit der Aktualisierungslogik zusammenhängen muss (zum Beispiel Netzwerkfehler).

Wenn außerdem der oben beschriebene DataAdapter mit Fill eine Zeile aus der Datenbank lesen kann, liegt der Fehler in Errors an der doppelten Verwendung desselben Primärschlüssels, da die Parametrisierung des SelectCommands auf die zu aktualisierende Zeile zugeschnitten ist und der Befehl nur null oder eine Zeile aus der Datenbank beziehen kann.

Console.WriteLine("Fehler vom Typ {}.", ev.Errors.GetType().Name) 
If ev.Errors.GetType() Is GetType(DbException) AndAlso _ 
  daConflict.Fill(dsConflict) = 1 Then 
    Console.WriteLine("Der Primärschlüssel existiert bereits.") 
End If

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

  • Ein anderer Anwender hat den Datensatz zwischenzeitlich geändert.
  • Der Datensatz wurde von einem anderen Anwender gelöscht.

Die Unterscheidung der Fälle geschieht wieder mit dem Ergebnis der Fill-Methode des oben eingeführten DataAdapters. Ist der Rückgabewert 0, konnte keine entsprechende Zeile in der Datenbank gefunden werden: Die Zeile wurde gelöscht. Wir ergänzen die If-Bedingung um einen Else If-Zweig.

Else If ev.Errors.GetType() Is GetType(DBConcurrencyException) Then 
  If daConflict.Fill(dsConflict) = 1 Then 
    Console.WriteLine("Ein anderer Nutzer hat den Datensatz geändert.") 
  Else 
    Console.WriteLine("Datensatz existiert nicht in der Datenbank.") 
  End If

Fassen wir nun den Ereignishandler des RowUpdated-Ereignisses zusammen:

Sub Änderung(sender As Object, ev As RowUpdatedEventArgs) 
  If ev.Status = UpdateStatus.ErrorsOccurred Then

    Dim cmd As DbCommand = New SqlCommand() 
    cmd.CommandText = "SELECT ProductID, ProductName, " & _ 
       "UnitPrice, Discontinued FROM Products WHERE ProductID = @ID" 
    cmd.Connection = con 
    cmd.Parameters.Add(New SqlParameter("@ID",SqlDbType.Int,4,"ProductID"))

    daConflict.SelectCommand = cmd 
    daConflict.SelectCommand.Parameters(0).Value = ev.Row("ProductID")

    ' Fehleranalyse 
    Console.WriteLine("Fehler vom Typ {}.", ev.Errors.GetType().Name) 
    If ev.Errors.GetType() Is GetType(DbException) AndAlso _ 
      daConflict.Fill(dsConflict) = 1 Then 
        Console.WriteLine("Der Primärschlüssel existiert bereits.") 
    Else If ev.Errors.GetType() Is GetType(DBConcurrencyException) Then 
      If daConflict.Fill(dsConflict) = 1 Then 
        Console.WriteLine("Ein anderer Nutzer hat den Datensatz geändert.") 
      Else 
        Console.WriteLine("Datensatz existiert nicht in der Datenbank.") 
      End If 
    End If 
  End If 
End Sub

Wie Sie mit dem Inhalt des DataSets umgehen, das den aktuellen Stand der konfliktverursachenden Zeilen enthält, richtet sich nach den Bedürfnissen des Kunden, der die Anwendung einsetzt. Die Lösung kann sehr unterschiedlich ausfallen und dabei auch noch sehr komplex sein. Im folgenden zusammengefassten Beispielprogramm werden nur das neue Original und die Daten der konfliktverursachenden Datenzeile an der Konsole ausgegeben.


'...\ADO\Aktualisierung\Konflikt

Option Strict On 
Imports System.Data.Common, System.Data.SqlClient 
Namespace ADO 
  Module Ereignisse 
    Dim con As DbConnection = New SqlConnection() 
    Dim daConflict As DbDataAdapter = New SqlDataAdapter() 
    Dim dsConflict As New DataSet() 
    Sub Test() 
      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 
      AddHandler CType(da, SqlDataAdapter).RowUpdated, AddressOf Änderung 
      da.Update(ds)

      ' Konflikte ausgeben 
      If dsConflict.Tables.Count = 0 Then Return 
      Console.WriteLine("{0} konfliktverursachende Datenzeile(n)", _ 
                        dsConflict.Tables(0).Rows.Count) 
      For Each cRow As DataRow In dsConflict.Tables(0).Rows 
        Dim rows() As DataRow = ds.Tables(0).Select( _ 
          "ProductID = " & cRow("ProductID").ToString()) 
        Console.WriteLine("{0,-10}{1}", "ProductID", "ProductName") 
        Console.WriteLine("Original :  {0,-10}{1}", cRow(0), cRow(1)) 
        Console.WriteLine("Erfolglos: {0,-10}{1}", rows(0)(0), rows(0)(1)) 
        Console.WriteLine(New String("-"c, 50)) 
      Next

      Console.ReadLine() 
    End Sub 
    Sub Änderung(sender As Object, ev As RowUpdatedEventArgs) ... 
    Function CreateUpdateCommand(ByVal con As DbConnection) As DbCommand ... 
  End Module 
End Namespace

Wenn Sie das Beispielprogramm ausprobieren, sollten Sie zuerst sicherstellen, dass es den Artikel Chai auch tatsächlich gibt (wir haben bereits sehr viel mit dieser Datenzeile experimentiert). Wegen des in der Tabelle Products definierten autoinkrementellen Primärschlüssels kann das Programm allerdings keinen doppelten Primärschlüssel simulieren, der von der Datenbank vergeben wird.



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 Basic 2008
Visual Basic 2008
Jetzt bestellen


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

 Buchempfehlungen
Zum Katalog: Visual Basic 2012






 Visual Basic 2012


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
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