Freigeben über


Implementierung optimistischer Gleichzeitigkeit mit der SqlDataSource (VB)

von Scott Mitchell

PDF herunterladen

In diesem Tutorial erörtern wir die Grundlagen der optimistischen Parallelitätskontrolle und untersuchen dann, wie diese mit dem SqlDataSource-Steuerelement implementiert werden kann.

Einleitung

Im vorherigen Lernprogramm haben wir untersucht, wie Sie dem SqlDataSource-Steuerelement Funktionen zum Einfügen, Aktualisieren und Löschen hinzufügen. Kurz gesagt, um diese Features bereitzustellen, mussten wir die entsprechende INSERT, UPDATE, oder DELETE SQL-Anweisung in den Steuerelement-Eigenschaften InsertCommand, UpdateCommand oder DeleteCommand zusammen mit den entsprechenden Parametern in den InsertParameters, UpdateParameters und DeleteParameters Auflistungen angeben. Während diese Eigenschaften und Auflistungen manuell angegeben werden können, bietet die Schaltfläche "Erweitert" im Assistenten zum Konfigurieren der Datenquelle ein Kontrollkästchen "Generiere INSERT, UPDATE und DELETE-Anweisungen", mit dem diese Anweisungen basierend auf der SELECT-Anweisung automatisch erstellt werden.

Zusammen mit dem Kontrollkästchen "Generiere INSERT, UPDATE und DELETE-Anweisungen" enthält das Dialogfeld "Erweiterte Optionen zur SQL-Generierung" die Option "Optimistische Parallelität verwenden" (siehe Abbildung 1). Wenn überprüft, werden die WHERE-Klauseln in den automatisch generierten UPDATE- und DELETE-Anweisungen so geändert, dass die Aktualisierung oder Löschung nur ausgeführt wird, wenn die zugrunde liegenden Datenbankdaten seitdem der Benutzer die Daten zuletzt in das Grid geladen hat, nicht geändert wurden.

Sie können optimistische Parallelitätsunterstützung über das Dialogfeld

Abbildung 1: Sie können eine optimistische Parallelitätsunterstützung über das Dialogfeld "Erweiterte SQL-Generierungsoptionen" hinzufügen.

Zurück im Lernprogramm "Implementieren optimistischer Parallelität " haben wir die Grundlagen des optimistischen Parallelitätssteuerelements und dessen Hinzufügen zur ObjectDataSource untersucht. In diesem Tutorial besprechen wir die Grundlagen der optimistischen Sperrsteuerung und untersuchen anschließend, wie sie mithilfe der SqlDataSource implementiert werden kann.

Eine Zusammenfassung optimistischer Parallelität

Für Webanwendungen, die es mehreren gleichzeitigen Benutzern ermöglichen, dieselben Daten zu bearbeiten oder zu löschen, besteht die Möglichkeit, dass ein Benutzer versehentlich andere Änderungen überschreiben kann. Im Lernprogramm "Implementieren optimistischer Parallelität " habe ich das folgende Beispiel bereitgestellt:

Stellen Sie sich vor, dass zwei Benutzer, Jisun und Sam, beide eine Seite in einer Anwendung besuchten, die es Besuchern ermöglichte, Produkte über ein GridView-Steuerelement zu aktualisieren und zu löschen. Beide klicken auf die Schaltfläche "Bearbeiten" für "Chai" um die gleiche Zeit. Jisun ändert den Produktnamen in Chai Tea und klickt auf die Schaltfläche "Aktualisieren". Das Nettoergebnis ist eine UPDATE Anweisung, die an die Datenbank gesendet wird, die alle aktualisierbaren Felder des Produkts festlegt (obwohl Jisun nur ein Feld aktualisiert hat). ProductName Zu diesem Zeitpunkt verfügt die Datenbank über die Werte Chai Tea, die Kategorie Getränke, den Lieferanten Exotische Flüssigkeiten usw. für dieses bestimmte Produkt. Auf Sams Bildschirm wird im GridView jedoch weiterhin der Produktname in der bearbeitbaren Zeile als Chai angezeigt. Ein paar Sekunden nach dem Commit von Jisuns Änderungen aktualisiert Sam die Kategorie auf "Condiments" und klickt auf "Aktualisieren". Dies führt zu einer UPDATE Anweisung, die an die Datenbank gesendet wird und den Produktnamen auf Chai sowie die CategoryID auf die entsprechende Kategorie-ID für Gewürze festlegt, und so weiter. Jisuns Änderungen an dem Produktnamen wurden überschrieben.

Abbildung 2 veranschaulicht diese Interaktion.

Wenn zwei Benutzer gleichzeitig einen Datensatz aktualisieren, besteht die Gefahr, dass die Änderungen eines Benutzers die des anderen überschreiben.

Abbildung 2: Wenn zwei Benutzer gleichzeitig einen Datensatz aktualisieren, besteht das Potenzial für Änderungen eines Benutzers, um die anderen zu überschreiben (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Um zu verhindern, dass sich dieses Szenario entwickeln kann, muss eine Form der Parallelitätssteuerung implementiert werden. Optimistische Parallelität ist der Schwerpunkt dieses Lernprogramms. Sie basiert auf der Annahme, dass es zwar gelegentlich zu Parallelkonflikten kommen kann, diese aber in der überwiegenden Mehrzahl der Fälle nicht auftreten. Wenn ein Konflikt auftritt, informiert das optimistische Parallelitätssteuerelement den Benutzer daher einfach darüber, dass seine Änderungen nicht gespeichert werden können, da ein anderer Benutzer dieselben Daten geändert hat.

Hinweis

Für Anwendungen, bei denen davon ausgegangen wird, dass es viele Parallelitätskonflikte gibt oder wenn solche Konflikte nicht tolerierbar sind, kann stattdessen pessimistische Parallelitätskontrolle verwendet werden. Weitere Informationen zur pessimistischen Parallelitätssteuerung finden Sie im Lernprogramm "Implementieren optimistischer Parallelität ".

Die optimistische Parallelitätssteuerung funktioniert, indem sichergestellt wird, dass der Datensatz, der aktualisiert oder gelöscht wird, dieselben Werte aufweist wie beim Start des Aktualisierungs- oder Löschvorgangs. Wenn Sie beispielsweise auf die Schaltfläche "Bearbeiten" in einer bearbeitbaren GridView klicken, werden die Datensatzwerte aus der Datenbank gelesen und in TextBoxes und anderen Websteuerelementen angezeigt. Diese ursprünglichen Werte werden von GridView gespeichert. Nachdem die Benutzerin ihre Änderungen vorgenommen hat und auf die Schaltfläche "Aktualisieren" klickt, muss die UPDATE verwendete Anweisung die ursprünglichen sowie die neuen Werte berücksichtigen und den zugrunde liegenden Datenbankdatensatz nur aktualisieren, wenn die ursprünglichen Werte, mit deren Bearbeitung die Benutzerin begonnen hat, identisch mit den Werten sind, die sich noch in der Datenbank befinden. Abbildung 3 zeigt diese Abfolge von Ereignissen.

Damit die Aktualisierung oder Löschvorgang erfolgreich ist, müssen die Ursprünglichen Werte den aktuellen Datenbankwerten entsprechen.

Abbildung 3: Damit die Aktualisierung oder Löschvorgang erfolgreich ist, müssen die ursprünglichen Werte den aktuellen Datenbankwerten entsprechen (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Es gibt verschiedene Ansätze zur Implementierung optimistischer Parallelität (siehe Peter A. Brombergsoptimistische Parallelitätsaktualisierungslogik für einen kurzen Blick auf eine Reihe von Optionen). Die von der SqlDataSource verwendete Technik (sowie die in unserer Datenzugriffsebene verwendeten ADO.NET Typed DataSets) erweitern die WHERE Klausel so, dass sie einen Vergleich aller ursprünglichen Werte enthält. Die folgende UPDATE Anweisung aktualisiert z. B. den Namen und den Preis eines Produkts nur, wenn die aktuellen Datenbankwerte den Werten entsprechen, die beim Aktualisieren des Datensatzes in GridView ursprünglich abgerufen wurden. Die Parameter @ProductName und @UnitPrice enthalten die neuen Werte, die vom Benutzer eingegeben wurden, während @original_ProductName und @original_UnitPrice die Werte enthalten, die ursprünglich in die GridView geladen wurden, als auf die Schaltfläche "Bearbeiten" geklickt wurde.

UPDATE Products SET
    ProductName = @ProductName,
    UnitPrice = @UnitPrice
WHERE
    ProductID = @original_ProductID AND
    ProductName = @original_ProductName AND
    UnitPrice = @original_UnitPrice

Wie wir in dieser Anleitung sehen werden, ist das Aktivieren der optimistischen Synchronisationssteuerung mit der SqlDataSource so einfach wie das Anklicken eines Kontrollkästchens.

Schritt 1: Erstellen einer SqlDataSource, die optimistische Parallelität unterstützt

Öffnen Sie zunächst die OptimisticConcurrency.aspx Seite aus dem SqlDataSource Ordner. Ziehen Sie ein SqlDataSource-Steuerelement aus der Toolbox auf den Designer, indem Sie dessen ID-Eigenschaft auf ProductsDataSourceWithOptimisticConcurrency konfigurieren. Klicken Sie als Nächstes auf den Link "Datenquelle konfigurieren" im Smarttag des Steuerelements. Wählen Sie auf dem ersten Bildschirm des Assistenten aus, ob Sie mit dem NORTHWINDConnectionString arbeiten möchten, und klicken Sie auf "Weiter".

Wählen Sie die Arbeit mit der NORTHWINDConnectionString

Abbildung 4: Wählen Sie die Arbeit mit dem NORTHWINDConnectionString (Klicken Sie, um das Bild in voller Größe zu sehen)

In diesem Beispiel fügen wir eine GridView hinzu, mit der Benutzer die Products Tabelle bearbeiten können. Wählen Sie daher im Bildschirm "Anweisung auswählen" die Products Tabelle aus der Dropdownliste sowie die Spalten ProductID, ProductName, UnitPrice und Discontinued, wie in Abbildung 5 dargestellt.

Geben Sie in der Tabelle

Abbildung 5: Aus der Products Tabelle geben Sie die ProductID, ProductName, UnitPrice und Discontinued Spalten zurück (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Klicken Sie nach der Auswahl der Spalten auf die Schaltfläche "Erweitert", um das Dialogfeld "Erweiterte SQL-Generationsoptionen" anzuzeigen. Überprüfen Sie die Kontrollkästchen "Generiere INSERT, UPDATE und DELETE Anweisungen" sowie "Optimistische Parallelität verwenden" und klicken Sie auf "OK" (siehe Abbildung 1 für einen Screenshot). Schließen Sie den Assistenten ab, indem Sie auf "Weiter" und dann auf "Fertig stellen" klicken.

Nach Abschluss des Assistenten zum Konfigurieren von Datenquellen nehmen Sie sich einen Moment Zeit, um die resultierenden DeleteCommand- und UpdateCommand-Eigenschaften sowie die DeleteParameters- und UpdateParameters-Auflistungen zu untersuchen. Am einfachsten können Sie dazu auf die Registerkarte "Quelle" in der unteren linken Ecke klicken, um die deklarative Syntax der Seite anzuzeigen. Dort finden Sie einen UpdateCommand Wert von:

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued

Mit sieben Parametern in der UpdateParameters Auflistung:

<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ...>
    <DeleteParameters>
      ...
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </UpdateParameters>
    ...
</asp:SqlDataSource>

Ebenso sollten die DeleteCommand-Eigenschaft und die DeleteParameters-Sammlung wie folgt aussehen:

DELETE FROM [Products]
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ...>
    <DeleteParameters>
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </DeleteParameters>
    <UpdateParameters>
        ...
    </UpdateParameters>
    ...
</asp:SqlDataSource>

Zusätzlich zur Erweiterung der Klauseln der WHERE und UpdateCommand Eigenschaften (und zum Hinzufügen der zusätzlichen Parameter zu den jeweiligen Parameterauflistungen) passt die Auswahl der Option "Optimistische Parallelität verwenden" zwei weitere Eigenschaften an:

Wenn das Daten-Websteuerelement die Update() oder Delete() Methode von SqlDataSource aufruft, übergibt es die ursprünglichen Werte. Wenn die SqlDataSource-Eigenschaft ConflictDetection auf CompareAllValues festgelegt ist, werden diese ursprünglichen Werte dem Befehl hinzugefügt. Die OldValuesParameterFormatString Eigenschaft stellt das Benennungsmuster bereit, das für diese ursprünglichen Wertparameter verwendet wird. Der Assistent zum Konfigurieren der Datenquelle verwendet original_{0} und benennt jeden ursprünglichen Parameter entsprechend zu den Eigenschaften UpdateCommand und DeleteCommand sowie den Sammlungen UpdateParameters und DeleteParameters.

Hinweis

Da wir die Einfügefunktionen von SqlDataSource-Steuerelement nicht nutzen, können Sie problemlos die InsertCommand-Eigenschaft und ihre InsertParameters-Sammlung entfernen.

Korrekte Verarbeitung vonNULLWerten

Leider funktionieren die automatisch generierten erweiterten UPDATE und DELETE Anweisungen vom Assistenten zum Konfigurieren von Datenquellen bei Verwendung von optimistischer Parallelität nicht mit Datensätzen, die NULL Werte enthalten. Um zu sehen, warum sollten Sie sich unsere SqlDataSource s UpdateCommandansehen:

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued

Die UnitPrice Spalte in der Products Tabelle kann NULL Werte enthalten. Wenn ein bestimmter Datensatz einen Wert von NULL für UnitPrice hat, wird der Klauselabschnitt WHERE[UnitPrice] = @original_UnitPrice als False bewertet, da immer False zurückgibt. Daher können Datensätze, die Werte enthalten NULL , nicht bearbeitet oder gelöscht werden, da die UPDATE Klauseln und DELETE Anweisungen WHERE keine Zeilen zum Aktualisieren oder Löschen zurückgeben.

Hinweis

Dieser Fehler wurde zuerst im Juni 2004 in SqlDataSource erzeugt falsche SQL-Anweisungen gemeldet und soll in der nächsten Version von ASP.NET behoben werden.

Um dies zu beheben, müssen wir die WHERE Klauseln sowohl in den UpdateCommand als auch in den DeleteCommand Eigenschaften für alle Spalten, die NULL Werte haben können, manuell aktualisieren. Im Allgemeinen ändern Sie [ColumnName] = @original_ColumnName zu:

(
   ([ColumnName] IS NULL AND @original_ColumnName IS NULL)
     OR
   ([ColumnName] = @original_ColumnName)
)

Diese Änderung kann direkt über das deklarative Markup, über die UpdateQuery- oder DeleteQuery-Optionen aus dem Eigenschaftenfenster oder über die Registerkarten UPDATE und DELETE in der Option "Benutzerdefinierte SQL-Anweisung oder gespeicherte Prozedur angeben" im Assistenten zum Konfigurieren von Datenquellen erfolgen. Auch hier muss diese Änderung für jede Spalte in der UpdateCommand und DeleteCommandWHERE-Klausel vorgenommen werden, die NULL-Werte enthalten kann.

Wenn dies auf unser Beispiel angewendet wird, ergeben sich die folgenden geänderten Werte für UpdateCommand und DeleteCommand:

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
        OR ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued
DELETE FROM [Products]
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
        OR ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued

Schritt 2: Hinzufügen einer GridView mit Bearbeitungs- und Löschoptionen

Da SqlDataSource für die Unterstützung optimistischer Parallelität konfiguriert ist, bleibt nur übrig, der Seite ein Datenwebsteuerelement hinzuzufügen, das diese Parallelitätssteuerung nutzt. Lassen Sie uns in diesem Lernprogramm eine GridView hinzufügen, die sowohl Bearbeitungs- als auch Löschfunktionen bereitstellt. Um dies zu erreichen, ziehen Sie ein GridView aus der Toolbox auf den Designer und legen Sie ID auf Products. Binden Sie das Smarttag von GridView an das in Schritt 1 hinzugefügte ProductsDataSourceWithOptimisticConcurrency SqlDataSource-Steuerelement. Überprüfen Sie abschließend die Optionen "Bearbeiten aktivieren" und "Löschen aktivieren" aus dem Smarttag.

Binden der GridView an SqlDataSource und Aktivieren des Bearbeitens und Löschens

Abbildung 6: Binden der GridView an die SqlDataSource und Aktivieren der Bearbeitung und Löschung (Klicken, um das Bild in voller Größe anzuzeigen)

Konfigurieren Sie nach dem Hinzufügen der GridView ihre Darstellung, indem Sie das ProductID BoundField entfernen, die ProductName BoundField-Eigenschaft HeaderText in "Product" ändern und das UnitPrice BoundField so aktualisieren, dass die HeaderText Eigenschaft einfach "Price" ist. Im Idealfall erweitern wir die Bearbeitungsschnittstelle, um einen RequiredFieldValidator für den ProductName Wert und einen CompareValidator für den UnitPrice Wert einzuschließen (um sicherzustellen, dass er ein ordnungsgemäß formatierter numerischer Wert ist). Im Lernprogramm zum Anpassen der Datenänderungsschnittstelle finden Sie ausführlichere Informationen zum Anpassen der Bearbeitungsschnittstelle von GridView.

Hinweis

Der Ansichtszustand von GridView muss aktiviert werden, da die ursprünglichen Werte, die von GridView an die SqlDataSource übergeben werden, im Ansichtszustand gespeichert sind.

Nachdem Sie diese Änderungen an GridView vorgenommen haben, sollte das deklarative Markup "GridView" und "SqlDataSource" ähnlich wie folgt aussehen:

<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ConflictDetection="CompareAllValues"
    ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
    DeleteCommand=
        "DELETE FROM [Products]
         WHERE [ProductID] = @original_ProductID
         AND [ProductName] = @original_ProductName
         AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
              OR ([UnitPrice] = @original_UnitPrice))
         AND [Discontinued] = @original_Discontinued"
    OldValuesParameterFormatString=
        "original_{0}"
    SelectCommand=
        "SELECT [ProductID], [ProductName], [UnitPrice], [Discontinued]
         FROM [Products]"
    UpdateCommand=
        "UPDATE [Products]
         SET [ProductName] = @ProductName, [UnitPrice] = @UnitPrice,
            [Discontinued] = @Discontinued
         WHERE [ProductID] = @original_ProductID
         AND [ProductName] = @original_ProductName
         AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
            OR ([UnitPrice] = @original_UnitPrice))
        AND [Discontinued] = @original_Discontinued">
    <DeleteParameters>
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </UpdateParameters>
</asp:SqlDataSource>
<asp:GridView ID="Products" runat="server"
    AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ProductsDataSourceWithOptimisticConcurrency">
    <Columns>
        <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" HeaderText="Price"
            SortExpression="UnitPrice" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>

Um die optimistische Parallelsteuerung in Aktion zu sehen, öffnen Sie zwei Browserfenster und laden Sie die OptimisticConcurrency.aspx Seite in beiden. Klicken Sie auf die Schaltflächen "Bearbeiten" für das erste Produkt in beiden Browsern. Ändern Sie in einem Browser den Produktnamen, und klicken Sie auf "Aktualisieren". Der Browser wird postback und GridView kehrt in den Vorbearbeitungsmodus zurück und zeigt den neuen Produktnamen für den soeben bearbeiteten Datensatz an.

Ändern Sie im zweiten Browserfenster den Preis (aber behalten Sie den Produktnamen als ursprünglichen Wert bei), und klicken Sie auf "Aktualisieren". Bei postback kehrt das Raster zum Vorbearbeitungsmodus zurück, die Änderung des Preises wird jedoch nicht aufgezeichnet. Der zweite Browser zeigt denselben Wert wie der erste an: den Namen des neuen Produkts mit dem alten Preis. Die im zweiten Browserfenster vorgenommenen Änderungen gingen verloren. Darüber hinaus gingen die Änderungen recht unauffällig verloren, da keine Ausnahme oder Benachrichtigung angezeigt wurde, dass gerade eine Verletzung der Nebenläufigkeit aufgetreten ist.

Die Änderungen im zweiten Browserfenster gingen unbemerkt verloren

Abbildung 7: Die Änderungen im zweiten Browserfenster wurden im Hintergrund verloren (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Der Grund dafür, dass die Änderungen im zweiten Browser nicht übernommen wurden, war, dass die Klausel der UPDATEWHERE Anweisung alle Datensätze herausgefiltert hat und daher keine Zeilen beeinflusst hat. Sehen wir uns die UPDATE Anweisung erneut an:

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL) OR
        ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued

Wenn das zweite Browserfenster den Datensatz aktualisiert, stimmt der in der WHERE Klausel angegebene ursprüngliche Produktname nicht mit dem vorhandenen Produktnamen überein (da er vom ersten Browser geändert wurde). Daher gibt die Anweisung [ProductName] = @original_ProductName False zurück, und dies UPDATE wirkt sich nicht auf Datensätze aus.

Hinweis

Das Löschen funktioniert auf die gleiche Weise. Beginnen Sie mit zwei geöffneten Browserfenstern, indem Sie ein bestimmtes Produkt mit einem bearbeiten und dann die Änderungen speichern. Klicken Sie nach dem Speichern der Änderungen im einen Browser auf die Schaltfläche "Löschen" für dasselbe Produkt im anderen. Da die ursprünglichen Werte in der DELETE Anweisung WHERE-Klausel nicht übereinstimmen, schlägt der Löschvorgang lautlos fehl.

Aus Sicht des Endbenutzers im zweiten Browserfenster, nachdem sie auf die Schaltfläche "Aktualisieren" geklickt haben, kehrt das Raster zum Vorbearbeitungsmodus zurück, aber ihre Änderungen gingen verloren. Es gibt jedoch kein visuelles Feedback, dass ihre Änderungen nicht beibehalten wurden. Idealerweise, wenn die Änderungen eines Benutzers aufgrund einer Parallelitätsverletzung verloren gehen, würden wir den Benutzer benachrichtigen und vielleicht die Tabelle im Bearbeitungsmodus behalten. Schauen wir uns an, wie dies erreicht werden kann.

Schritt 3: Ermitteln, wann eine Parallelitätsverletzung aufgetreten ist

Da eine Parallelitätsverletzung die vorgenommenen Änderungen ablehnt, wäre es schön, den Benutzer zu benachrichtigen, wenn eine Parallelitätsverletzung aufgetreten ist. Um den Benutzer zu benachrichtigen, fügen wir oben auf der Seite ConcurrencyViolationMessage ein Bezeichnungswebsteuerelement hinzu, dessen Text Eigenschaft die folgende Meldung anzeigt: Sie haben versucht, einen Datensatz zu aktualisieren oder zu löschen, der gleichzeitig von einem anderen Benutzer aktualisiert wurde. Überprüfen Sie die Änderungen des anderen Benutzers und führen Sie dann entweder Ihr Update erneut durch oder löschen Sie es. Legen Sie die Eigenschaft des Steuerelements "Label" auf CssClass "Warning" fest. Diese CSS-Klasse, die in Styles.css definiert ist, zeigt den Text in einer großen, roten, kursiven und fetten Schrift an. Setzen Sie schließlich die Label-Eigenschaften Visible und EnableViewState auf False. Dadurch wird das Label ausgeblendet, außer bei jenen Rücksendungen, bei denen wir die Visible-Eigenschaft explizit auf True setzen.

Fügen Sie ein Beschriftungssteuerelement zur Seite hinzu, um die Warnung anzuzeigen

Abbildung 8: Hinzufügen eines Bezeichnungssteuerelements zur Seite zum Anzeigen der Warnung (Klicken, um das Bild in voller Größe anzuzeigen)

Beim Ausführen eines Aktualisierungsvorgangs oder einer Löschung werden die GridView-Ereignishandler RowUpdated und RowDeleted ausgelöst, nachdem die Steuerung der Datenquelle die angeforderte Aktualisierung oder Löschung durchgeführt hat. Wir können anhand dieser Ereignishandler ermitteln, wie viele Zeilen von dem Vorgang betroffen waren. Wenn null Zeilen betroffen sind, möchten wir die ConcurrencyViolationMessage Bezeichnung anzeigen.

Erstellen Sie einen Ereignishandler für beide Ereignisse RowUpdated und RowDeleted und fügen Sie den folgenden Code hinzu:

Protected Sub Products_RowUpdated(sender As Object, e As GridViewUpdatedEventArgs) _
    Handles Products.RowUpdated
    If e.AffectedRows = 0 Then
        ConcurrencyViolationMessage.Visible = True
        e.KeepInEditMode = True
        ' Rebind the data to the GridView to show the latest changes
        Products.DataBind()
    End If
End Sub
Protected Sub Products_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _
    Handles Products.RowDeleted
    If e.AffectedRows = 0 Then
        ConcurrencyViolationMessage.Visible = True
    End If
End Sub

In beiden Ereignishandlern überprüfen wir die e.AffectedRows Eigenschaft, und wenn sie gleich 0 ist, setzen wir die ConcurrencyViolationMessage Label-Eigenschaft Visible auf True. RowUpdated Im Ereignishandler weisen wir auch das GridView an, im Bearbeitungsmodus zu bleiben, indem die KeepInEditMode Eigenschaft auf "true" festgelegt wird. Dabei müssen wir die Daten erneut an das Raster binden, damit die Daten des anderen Benutzers in die Bearbeitungsoberfläche geladen werden. Dies wird durch Aufrufen der GridView s-Methode DataBind() erreicht.

Wie in Abbildung 9 dargestellt, wird bei diesen beiden Ereignishandlern eine sehr erkennbare Meldung angezeigt, wenn eine Parallelitätsverletzung auftritt.

Eine Meldung wird bei einer Parallelitätsverletzung angezeigt.

Abbildung 9: Wegen einer Parallelitätsverletzung wird eine Meldung angezeigt (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Zusammenfassung

Beim Erstellen einer Webanwendung, bei der mehrere gleichzeitige Benutzer möglicherweise dieselben Daten bearbeiten, ist es wichtig, parallele Steuerungsoptionen zu berücksichtigen. Standardmäßig verwenden die ASP.NET Datenwebsteuerelemente und Datenquellensteuerelemente keine Parallelitätssteuerelemente. Wie wir in dieser Anleitung gesehen haben, ist die Implementierung der optimistischen Parallelausführungssteuerung mit dem SqlDataSource relativ schnell und einfach. Die SqlDataSource übernimmt den Großteil der Vorarbeit für das Hinzufügen erweiterter WHERE Klauseln zu den automatisch generierten UPDATE und DELETE Anweisungen. Allerdings gibt es einige Subtilitäten bei der Behandlung der Wertspalten NULL, wie im Abschnitt "Richtiges Handling von NULL-Werten" beschrieben.

Dieses Lernprogramm schließt unsere Untersuchung der SqlDataSource ab. Unsere verbleibenden Tutorials werden sich wieder mit der Datenverarbeitung mithilfe der ObjectDataSource und der mehrschichtigen Architektur befassen.

Glückliche Programmierung!

Zum Autor

Scott Mitchell, Autor von sieben ASP/ASP.NET Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft Web Technologies zusammen. Scott arbeitet als unabhängiger Berater, Trainer und Schriftsteller. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Stunden. Er kann bei mitchell@4GuysFromRolla.comerreicht werden.