Freigeben über


Implementieren von optimistischer Parallelität mit dem SqlDataSource-Steuerelement (VB)

von Scott Mitchell

PDF herunterladen

In diesem Tutorial überprüfen wir die Grundlagen der Steuerung für optimistische Parallelität und untersuchen dann, wie sie mithilfe des SqlDataSource-Steuerelements implementiert wird.

Einführung

Im vorherigen Tutorial haben wir uns mit dem Hinzufügen von Funktionen zum Einfügen, Aktualisieren und Löschen des SqlDataSource-Steuerelements befasst. Kurz gesagt, um diese Features bereitzustellen, mussten wir die entsprechende , oder SQL-Anweisung in den Eigenschaften des Steuerelements InsertCommand, UpdateCommandoder zusammen DeleteCommand mit den entsprechenden Parametern in den InsertParametersAuflistungen , UpdateParametersund DeleteParameters angeben.DELETEUPDATEINSERT Diese Eigenschaften und Auflistungen können zwar manuell angegeben werden, aber die Schaltfläche Erweitert des Datenquellenkonfigurations-Assistenten bietet das Kontrollkästchen Anweisungen generieren INSERT, UPDATEund , mit DELETE dem diese Anweisungen automatisch basierend auf der SELECT -Anweisung erstellt werden.

Neben dem Kontrollkästchen Anweisungen generierenINSERTUPDATE, und DELETE enthält das Dialogfeld Erweiterte SQL-Generierungsoptionen die Option Optimistische Parallelität verwenden (siehe Abbildung 1). Wenn diese Option aktiviert ist, werden die WHERE -Klauseln in der automatisch generierten UPDATE - und DELETE -Anweisung so geändert, dass sie nur dann aktualisiert oder gelöscht werden, wenn die zugrunde liegenden Datenbankdaten seit dem letzten Laden der Daten in das Raster nicht geändert wurden.

Sie können die Unterstützung für optimistische Parallelität über das Dialogfeld Erweiterte SQL-Generierungsoptionen hinzufügen.

Abbildung 1: Unterstützung für optimistische Parallelität über das Dialogfeld Erweiterte SQL-Generierungsoptionen

Im Tutorial Implementieren optimistischer Parallelität haben wir die Grundlagen der Steuerung für die optimistische Parallelität untersucht und wie sie der ObjectDataSource hinzugefügt wird. In diesem Tutorial retuschieren wir die Grundlagen der Steuerung der optimistischen Parallelität und untersuchen dann, wie sie mithilfe von SqlDataSource implementiert wird.

Eine Zusammenfassung der optimistischen Parallelität

Bei Webanwendungen, die es mehreren gleichzeitigen Benutzern ermöglichen, dieselben Daten zu bearbeiten oder zu löschen, besteht die Möglichkeit, dass ein Benutzer versehentlich änderungen eines anderen überschreibt. Im Tutorial 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 besuchen, die es Besuchern ermöglicht, Produkte über ein GridView-Steuerelement zu aktualisieren und zu löschen. Beide klicken ungefähr zur gleichen Zeit auf die Schaltfläche Bearbeiten für Chai. 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 enthält die Datenbank die Werte Chai Tea, die Kategorie Getränke, den Lieferanten Exotic Liquids und so weiter für dieses spezielle Produkt. Der Bildschirm "GridView" auf Sam zeigt jedoch weiterhin den Produktnamen in der bearbeitbaren GridView-Zeile als Chai an. Einige Sekunden nach dem Commit von Jisuns Änderungen aktualisiert Sam die Kategorie in Condiments und klickt auf Aktualisieren. Dies führt zu einer UPDATE Anweisung, die an die Datenbank gesendet wird, die den Produktnamen auf Chai, die CategoryID auf die entsprechende Condiments-Kategorie-ID usw. festlegt. Jisuns Änderungen am Produktnamen wurden überschrieben.

Abbildung 2 veranschaulicht diese Interaktion.

Wenn zwei Benutzer gleichzeitig einen Datensatz aktualisieren, besteht die Möglichkeit, dass ein Benutzer den anderen überschreibt.

Abbildung 2: Wenn zwei Benutzer gleichzeitig einen Datensatz aktualisieren, besteht die Möglichkeit, dass ein Benutzer den anderen überschreibt (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Um zu verhindern, dass sich dieses Szenario entwickelt, muss eine Form der Parallelitätssteuerung implementiert werden. Optimistische Parallelität Der Fokus dieses Tutorials basiert auf der Annahme, dass zwar hin und wieder Parallelitätskonflikte auftreten, aber in der überwiegenden Mehrheit der Zeit solche Konflikte nicht auftreten. Wenn also ein Konflikt auftritt, informiert die Optimistische Parallelitätssteuerung den Benutzer einfach darüber, dass seine Änderungen nicht gespeichert werden können, da ein anderer Benutzer die gleichen Daten geändert hat.

Hinweis

Für Anwendungen, bei denen davon ausgegangen wird, dass es viele Parallelitätskonflikte gibt oder solche Konflikte nicht tolerierbar sind, kann stattdessen die pessimistische Parallelitätssteuerung verwendet werden. Eine ausführlichere Erläuterung zur pessimistischen Parallelitätssteuerung finden Sie im Tutorial Implementieren optimistischer Parallelität.

Die Steuerung der optimistischen Parallelität funktioniert, indem sichergestellt wird, dass der datensatz, der aktualisiert oder gelöscht wird, dieselben Werte aufweist wie beim Starten des Aktualisierungs- oder Löschprozesses. Wenn Sie beispielsweise in einem bearbeitbaren GridView auf die Schaltfläche Bearbeiten klicken, werden die Werte des Datensatzes aus der Datenbank gelesen und in TextBoxes und anderen Websteuerelementen angezeigt. Diese ursprünglichen Werte werden von GridView gespeichert. Später, nachdem der Benutzer seine Änderungen vorgenommen und auf die Schaltfläche Aktualisieren klickt, muss die UPDATE verwendete Anweisung die ursprünglichen Werte sowie die neuen Werte berücksichtigen und den zugrunde liegenden Datenbankdatensatz nur aktualisieren, wenn die ursprünglichen Werte, die der Benutzer bearbeitet hat, mit den Werten identisch sind, die sich noch in der Datenbank befinden. Abbildung 3 zeigt diese Abfolge von Ereignissen.

Damit das Aktualisieren oder Löschen erfolgreich ist, müssen die ursprünglichen Werte den aktuellen Datenbankwerten entsprechen.

Abbildung 3: Damit das Update oder löschen erfolgreich ist, müssen die ursprünglichen Werte gleich den aktuellen Datenbankwerten sein (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Es gibt verschiedene Ansätze für die Implementierung optimistischer Parallelität (siehe Peter A. BrombergsOptimistische Parallelitätsaktualisierungslogik für eine kurze Übersicht über eine Reihe von Optionen). Die von SqlDataSource (sowie von den in unserer Datenzugriffsebene verwendeten ADO.NET Typisierten DataSets) wird die WHERE -Klausel erweitert, um einen Vergleich aller ursprünglichen Werte einzuschließen. 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 @ProductName Parameter und @UnitPrice enthalten die vom Benutzer eingegebenen neuen Werte, während @original_ProductName und @original_UnitPrice die Werte enthalten, die beim Klicken auf die Schaltfläche Bearbeiten ursprünglich in gridView geladen wurden:

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

Wie wir in diesem Tutorial sehen werden, ist das Aktivieren der Steuerung für optimistische Parallelität mit SqlDataSource so einfach wie das Aktivieren 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 die Designer, und legen Sie dessen ID -Eigenschaft auf festProductsDataSourceWithOptimisticConcurrency. Klicken Sie als Nächstes im Smarttag des Steuerelements auf den Link Datenquelle konfigurieren. Wählen Sie auf dem ersten Bildschirm des Assistenten aus, um mit dem NORTHWINDConnectionString zu arbeiten, und klicken Sie auf Weiter.

Auswählen von

Abbildung 4: Auswählen von "Arbeiten mit" (Klicken Sie hier, um dasNORTHWINDConnectionString Bild in voller Größe anzuzeigen)

In diesem Beispiel fügen wir eine GridView hinzu, mit der Benutzer die Products Tabelle bearbeiten können. Wählen Sie daher auf dem Bildschirm Select-Anweisung konfigurieren die Products Tabelle aus der Dropdownliste aus, und wählen Sie die ProductIDSpalten , ProductName, UnitPriceund Discontinued aus, wie in Abbildung 5 dargestellt.

Geben Sie in der Tabelle Products die Spalten ProductID, ProductName, UnitPrice und Discontinued zurück.

Abbildung 5: Geben Sie aus der Products Tabelle die ProductIDSpalten , ProductName, UnitPriceund Discontinued zurück (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Klicken Sie nach dem Auswählen der Spalten auf die Schaltfläche Erweitert, um das Dialogfeld Erweiterte SQL-Generierungsoptionen zu öffnen. Aktivieren Sie die Kontrollkästchen Anweisungen generierenINSERTUPDATE, , und DELETE verwenden Sie optimistische Parallelität, und klicken Sie auf OK (einen Screenshot finden Sie zurück zu Abbildung 1). Schließen Sie den Assistenten ab, indem Sie auf Weiter und dann auf Fertig stellen klicken.

Nachdem Sie den Assistenten zum Konfigurieren von Datenquellen abgeschlossen haben, nehmen Sie sich einen Moment Zeit, um die resultierenden DeleteCommand Eigenschaften und UpdateCommand die Auflistungen und DeleteParametersUpdateParameters zu untersuchen. Die einfachste Möglichkeit besteht darin, auf die Registerkarte Quelle in der unteren linken Ecke zu 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>

Auf ähnliche Weise sollten die Eigenschaft und DeleteParameters die DeleteCommand Auflistung 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 zum Erweitern der WHERE Klauseln der UpdateCommand Eigenschaften und DeleteCommand (und Hinzufügen der zusätzlichen Parameter zu den jeweiligen Parameterauflistungen) werden durch Auswählen der Option Optimistische Parallelität verwenden zwei weitere Eigenschaften angepasst:

Wenn das Datenwebsteuerelement die SqlDataSource-Methode Update() oder Delete() die -Methode aufruft, werden die ursprünglichen Werte übergeben. Wenn die SqlDataSource-Eigenschaft ConflictDetection auf CompareAllValuesfestgelegt 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 von Datenquellen verwendet original_{0} und benennt jeden ursprünglichen Parameter in den UpdateCommand Eigenschaften und DeleteCommand und UpdateParameters und DeleteParameters Auflistungen entsprechend.

Hinweis

Da wir die Einfügefunktionen des SqlDataSource-Steuerelements nicht verwenden, können Sie die Eigenschaft und ihre InsertCommandInsertParameters Auflistung entfernen.

Korrektes Behandeln vonNULLWerten

Leider funktionieren die vom Assistenten zum Konfigurieren von Datenquellen automatisch generierten erweiterten UPDATE - und DELETE -Anweisungen bei Verwendung der optimistischen Parallelität nicht mit Datensätzen, die Werte enthalten NULL . Um zu sehen, warum, betrachten Sie unsere SqlDataSource-Ressourcen:UpdateCommand

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 Werte enthalten NULL . Wenn ein bestimmter Datensatz über einen NULL Wert für UnitPriceverfügt, wird der WHERE Klauselteil [UnitPrice] = @original_UnitPriceimmer zu False ausgewertet, da NULL = NULL immer False zurückgibt. Daher können Datensätze, die Werte enthaltenNULL, nicht bearbeitet oder gelöscht werden, da die UPDATE -Anweisungsklauseln WHERE und DELETE keine Zeilen zurückgeben, die aktualisiert oder gelöscht werden sollen.

Hinweis

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

Um dies zu beheben, müssen wir die Klauseln in den WHEREUpdateCommand Eigenschaften und DeleteCommand für alle Spalten, die Werte aufweisen NULL können, manuell aktualisieren. Wechseln Sie [ColumnName] = @original_ColumnName im Allgemeinen zu:

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

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

Wenn Sie dies auf unser Beispiel anwenden, werden die folgenden geänderten UpdateCommand - und DeleteCommand -Werte angezeigt:

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 eines GridView-Steuerelements mit Bearbeitungs- und Löschoptionen

Nachdem sqlDataSource so konfiguriert ist, dass sie optimistische Parallelität unterstützt, müssen Sie der Seite, die dieses Parallelitätssteuerelement verwendet, nur ein Datenwebsteuerelement hinzufügen. Fügen Sie für dieses Tutorial ein GridView-Objekt hinzu, das sowohl Bearbeitungs- als auch Löschfunktionen bereitstellt. Ziehen Sie hierzu ein GridView-Objekt aus der Toolbox auf die Designer, und legen Sie auf ID festProducts. Binden Sie es über das Smarttag von GridView an das ProductsDataSourceWithOptimisticConcurrency in Schritt 1 hinzugefügte SqlDataSource-Steuerelement. Aktivieren Sie abschließend die Optionen Bearbeiten aktivieren und Löschen aktivieren im Smarttag.

Binden der GridView an die SqlDataSource und Aktivieren von Bearbeitung und Löschen

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

Nachdem Sie gridView hinzugefügt haben, konfigurieren Sie die Darstellung, indem Sie boundField ProductID entfernen, die ProductName BoundField-Eigenschaft in HeaderText Product ändern und die UnitPrice BoundField-Eigenschaft 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 es sich um einen ordnungsgemäß formatierten numerischen Wert ist). Im Tutorial Anpassen der Datenänderungsschnittstelle finden Sie ausführlichere Informationen zum Anpassen der Bearbeitungsschnittstelle von GridView.

Hinweis

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

Nachdem Sie diese Änderungen am GridView vorgenommen haben, sollte das deklarative Markup GridView und SqlDataSource 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 das optimistische Parallelitätssteuerelement in Aktion zu sehen, öffnen Sie zwei Browserfenster, und laden Sie die OptimisticConcurrency.aspx Seite in beide. Klicken Sie in beiden Browsern auf die Schaltflächen Bearbeiten für das erste Produkt. Ändern Sie in einem Browser den Produktnamen, und klicken Sie auf Aktualisieren. Der Browser postback, und gridView kehrt in den Vorbearbeitungsmodus zurück und zeigt den neuen Produktnamen für den gerade bearbeiteten Datensatz an.

Ändern Sie im zweiten Browserfenster den Preis (behalten Sie jedoch den Produktnamen als ursprünglichen Wert bei), und klicken Sie auf Aktualisieren. Beim Postback kehrt das Raster in den Vorbearbeitungsmodus zurück, aber die Änderung des Preises wird nicht aufgezeichnet. Der zweite Browser zeigt den gleichen Wert wie der erste den neuen Produktnamen mit dem alten Preis an. Die im zweiten Browserfenster vorgenommenen Änderungen gingen verloren. Darüber hinaus gingen die Änderungen eher leise verloren, da es keine Ausnahme oder Meldung gab, die darauf hindeutete, dass gerade eine Parallelitätsverletzung aufgetreten ist.

Die Änderungen im zweiten Browserfenster sind im Hintergrund verloren gegangen.

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

Der Grund, warum die Änderungen des zweiten Browsers nicht übernommen wurden, lag daran, dass die UPDATE Anweisung s-Klausel WHERE alle Datensätze herausgefiltert hat und sich daher auf keine Zeilen auswirkte. Sehen wir uns die UPDATE Anweisung noch einmal 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 die UPDATE wirkt sich nicht auf Datensätze aus.

Hinweis

Löschen funktioniert auf die gleiche Weise. Wenn zwei Browserfenster geöffnet sind, bearbeiten Sie zunächst ein bestimmtes Produkt mit einem, und speichern Sie dann die Änderungen. Nachdem Sie die Änderungen in dem einen Browser gespeichert haben, klicken Sie auf die Schaltfläche Löschen für dasselbe Produkt im anderen. Da die ursprünglichen Werte in der DELETE Anweisung s-Klausel WHERE nicht übereinstimmen, schlägt das löschen im Hintergrund fehl.

Aus sicht des Endbenutzers im zweiten Browserfenster kehrt das Raster nach dem Klicken auf die Schaltfläche Aktualisieren in den Vorbearbeitungsmodus zurück, aber die Änderungen gingen verloren. Es gibt jedoch kein visuelles Feedback, dass ihre Änderungen nicht haften bleiben. Wenn Änderungen eines Benutzers aufgrund einer Parallelitätsverletzung verloren gehen, sollten wir diesen im Idealfall benachrichtigen und das Raster möglicherweise im Bearbeitungsmodus behalten. Sehen wir uns an, wie dies erreicht wird.

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 warnen, fügen Sie oben auf der Seite ein Label Web-Steuerelement mit dem Namen ConcurrencyViolationMessage 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 wiederholen Sie dann Ihr Update oder Löschen. Legen Sie die Eigenschaft des Label-Steuerelements CssClass auf Warning fest. Dies ist eine CSS-Klasse, die in Styles.css definiert ist, die Text in einer roten, kursiven, fetten und großen Schriftart anzeigt. Legen Sie schließlich die Eigenschaften Bezeichnung s Visible und EnableViewState auf fest False. Dadurch wird die Bezeichnung ausgeblendet, mit Ausnahme der Postbacks, bei denen die Eigenschaft explizit auf Truefestgelegt Visible wird.

Hinzufügen eines Beschriftungssteuerelements zur Seite zum Anzeigen der Warnung

Abbildung 8: Hinzufügen eines Beschriftungssteuerelements zur Seite, um die Warnung anzuzeigen (Klicken Sie hier, um das vollständige Bild anzuzeigen)

Beim Ausführen eines Updates oder Löschens werden die GridView-Ereignishandler RowUpdated und RowDeleted -Ereignishandler ausgelöst, nachdem die Datenquellensteuerung das angeforderte Update oder Löschen ausgeführt hat. Anhand dieser Ereignishandler können wir bestimmen, 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 die RowUpdated - und RowDeleted -Ereignisse, 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 legen die Label-Eigenschaft VisibleTrueauf festConcurrencyViolationMessage, wenn sie gleich 0 ist. RowUpdated Im Ereignishandler weisen wir die GridView außerdem an, im Bearbeitungsmodus zu bleiben, indem wir die KeepInEditMode Eigenschaft auf true festlegen. Dabei müssen wir die Daten neu an das Raster binden, damit die Daten des anderen Benutzers in die Bearbeitungsschnittstelle geladen werden. Dies wird durch Aufrufen der GridView-Methode DataBind() erreicht.

Wie Abbildung 9 zeigt, wird bei diesen beiden Ereignishandlern eine sehr auffällige Meldung angezeigt, wenn eine Parallelitätsverletzung auftritt.

Eine Nachricht wird im Angesicht einer Parallelitätsverletzung angezeigt.

Abbildung 9: Eine Meldung wird im Gesicht einer Parallelitätsverletzung angezeigt (Klicken Sie hier, um das bild in voller Größe anzuzeigen)

Zusammenfassung

Beim Erstellen einer Webanwendung, bei der mehrere gleichzeitige Benutzer dieselben Daten bearbeiten, ist es wichtig, Optionen für die Parallelitätssteuerung in Betracht zu ziehen. Standardmäßig verwenden die ASP.NET Datenwebsteuerelemente und Datenquellensteuerelemente kein Parallelitätssteuerelement. Wie wir in diesem Tutorial gesehen haben, ist die Implementierung der optimistischen Parallelitätssteuerung mit SqlDataSource relativ schnell und einfach. SqlDataSource übernimmt die meisten Aufgaben beim Hinzufügen von erweiterten WHERE Klauseln zu den automatisch generierten UPDATE Und DELETE -Anweisungen, aber es gibt einige Feinheiten bei der Behandlung von NULL Wertspalten, wie im Abschnitt Richtig behandeln von NULL Werten erläutert.

Dieses Tutorial schließt unsere Untersuchung der SqlDataSource ab. In unseren restlichen Tutorials wird die Arbeit mit Daten mithilfe der ObjectDataSource und der mehrstufigen Architektur wieder hergestellt.

Viel Spaß beim Programmieren!

Zum Autor

Scott Mitchell, Autor von sieben ASP/ASP.NET-Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft-Webtechnologien. Scott arbeitet als unabhängiger Berater, Trainer und Autor. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Stunden. Er kann unter mitchell@4GuysFromRolla.comoder über seinen Blog erreicht werden, der unter http://ScottOnWriting.NETzu finden ist.