Share via


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

von Scott Mitchell

PDF herunterladen

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

Einführung

Im vorherigen Tutorial 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, - oder DELETE SQL-Anweisung in den Steuerelementeigenschaften s InsertCommand, UpdateCommandoder DeleteCommand sowie die entsprechenden Parameter in den InsertParametersAuflistungen , UpdateParametersund DeleteParameters angeben. UPDATE Während diese Eigenschaften und Auflistungen manuell angegeben werden können, bietet die Schaltfläche "Datenquellen konfigurieren" des Assistenten "Erweitert" das Kontrollkästchen "Anweisungen generierenINSERTUPDATEDELETE", mit dem diese Anweisungen automatisch basierend auf der SELECT -Anweisung erstellt werden.

Zusammen mit dem Kontrollkästchen Anweisungen generieren INSERT, UPDATEund DELETE enthält das Dialogfeld Erweiterte SQL-Generierungsoptionen die Option Optimistische Parallelität verwenden (siehe Abbildung 1). Wenn dies 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 nicht geändert wurden, seit der Benutzer die Daten zuletzt in das Raster geladen hat.

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

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

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

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 angegeben:

Stellen Sie sich vor, zwei Benutzer, Jisun und Sam, besuchten beide eine Seite in einer Anwendung, die es Besuchern ermöglichte, 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 hat die Datenbank die Werte Chai Tea, die Kategorie Getränke, der Lieferant Exotic Liquids usw. 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 an die Datenbank gesendeten Anweisung, die den Produktnamen auf Chai, die auf die CategoryID 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 die anderen Änderungen überschreibt.

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

Damit sich dieses Szenario nicht entfaltet, muss eine Form der Parallelitätssteuerung implementiert werden. Optimistische Parallelität Der Fokus dieses Tutorials basiert auf der Annahme, dass es zwar hin und wieder zu Parallelitätskonflikten kommen kann, 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 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 die pessimistische Parallelitätssteuerung verwendet werden. Im Tutorial Implementieren optimistischer Parallelität finden Sie eine ausführlichere Diskussion über pessimistische Parallelitätssteuerung.

Die optimistische Parallelitätssteuerung funktioniert, indem sichergestellt wird, dass der zu aktualisierende oder gelöschte Datensatz die gleichen Werte aufweist wie beim Starten des Aktualisierungs- oder Löschvorgangs. Wenn Sie beispielsweise in einer 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 plus 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 Ereignissequenz.

Damit update or delete erfolgreich ist, müssen die ursprünglichen Werte gleich den aktuellen Datenbankwerten sein.

Abbildung 3: Damit das Update oder Löschen 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, um optimistische Parallelität zu implementieren (siehe Peter A. BrombergsOptimistische Parallelitätsaktualisierungslogik für einen kurzen Blick auf eine Reihe von Optionen). Die von sqlDataSource (sowie von den in unserer Datenzugriffsebene verwendete ADO.NET Typisierte DataSets) erweitert die WHERE Klausel, um einen Vergleich aller ursprünglichen Werte aufzunehmen. Die folgende UPDATE Anweisung aktualisiert beispielsweise 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 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 diesem Tutorial sehen werden, ist das Aktivieren einer optimistischen Parallelitätssteuerung 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 seine 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 im Assistenten aus, mit dem NORTHWINDConnectionString zu arbeiten, und klicken Sie auf Weiter.

Wählen Sie aus, mit dem NORTHWINDConnectionString zu arbeiten.

Abbildung 4: Wählen Sie aus, mit dem zu arbeiten (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 in der Products Tabelle die ProductIDSpalten , ProductName, , UnitPriceund Discontinued zurück (Klicken Sie, 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 (Screenshot finden Sie zurück zu Abbildung 1). Schließen Sie den Assistenten ab, indem Sie auf Weiter und dann auf Fertig stellen klicken.

Nehmen Sie sich nach Abschluss des Assistenten Datenquellen konfigurieren einen Moment Zeit, um die resultierenden DeleteCommand Eigenschaften und UpdateCommand die DeleteParameters Auflistungen und UpdateParameters 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>

In ähnlicher Weise sollten die DeleteCommand Eigenschaft und DeleteParameters 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 Parametersammlungen) werden durch Auswählen der Option Optimistische Parallelität verwenden zwei weitere Eigenschaften angepasst:

Wenn das Datenwebsteuerelement die SqlDataSource-Methode Update() oder Delete() -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 Datenquellen konfigurieren verwendet original_{0} und benennt jeden ursprünglichen Parameter in den UpdateCommand Eigenschaften und DeleteCommand und UpdateParametersDeleteParameters Auflistungen entsprechend.

Hinweis

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

Korrekte Verarbeitung vonNULLWerten

Leider funktionieren die vom Assistenten Datenquellen konfigurieren automatisch generierten UPDATEDELETE erweiterten und -Anweisungen bei verwendung von optimistischer Parallelität nicht mit Datensätzen, die Werte enthalten NULL . Um zu sehen, warum, sehen Sie sich unsere SqlDataSource an 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 aufweisen NULL . Wenn ein bestimmter Datensatz über einen NULL Wert für UnitPriceverfügt, wird der WHERE Klauselteil [UnitPrice] = @original_UnitPriceimmer auf False ausgewertet, da NULL = NULL immer False zurückgegeben wird. Daher können Datensätze, die Werte enthalten NULL , nicht bearbeitet oder gelöscht werden, da die UPDATE - und DELETE -Anweisungsklauseln WHERE keine Zeilen zurückgeben, die aktualisiert oder gelöscht werden sollen.

Hinweis

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

Um dies zu beheben, müssen wir die Klauseln in den WHERE Eigenschaften und DeleteCommand für alle Spalten, die UpdateCommand Werte enthalten 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 Angeben einer benutzerdefinierten SQL-Anweisung oder gespeicherten Prozedur im Assistenten Datenquelle konfigurieren vorgenommen werden. Auch diese Änderung muss 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, ergeben sich die folgenden geänderten UpdateCommand Werte und DeleteCommand Werte:

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 Den Optionen "Bearbeiten" und "Löschen"

Wenn SqlDataSource für die Unterstützung optimistischer Parallelität konfiguriert ist, bleibt nur noch das Hinzufügen eines Datenwebsteuerelements zu der Seite, die dieses Parallelitätssteuerelement verwendet. Fügen Sie für dieses Tutorial eine GridView hinzu, die sowohl Bearbeitungs- als auch Löschfunktionen bietet. Ziehen Sie dazu eine GridView aus der Toolbox auf die Designer, und legen Sie sie ID auf 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 über das Smarttag.

Binden Sie die GridView an die SqlDataSource, und aktivieren Sie das Bearbeiten und Löschen.

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

Nachdem Sie gridView hinzugefügt haben, konfigurieren Sie dessen Darstellung, indem Sie boundField ProductID entfernen, die ProductName BoundField-Eigenschaft in HeaderText Product ändern und das UnitPrice BoundField so aktualisieren, dass seine HeaderText Eigenschaft einfach Price ist. Im Idealfall wird die Bearbeitungsoberfläche erweitert, 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). Weitere Informationen zum Anpassen der Bearbeitungsschnittstelle von GridView finden Sie im Tutorial Anpassen der Datenänderungsschnittstelle .

Hinweis

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

Nachdem Sie diese Änderungen an GridView vorgenommen haben, sollte das deklarative Markup GridView und SqlDataSource in etwa 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 Steuerelement für die optimistische Parallelität in Aktion zu sehen, öffnen Sie zwei Browserfenster, und laden Sie die OptimisticConcurrency.aspx Seite in beiden. 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, der den neuen Produktnamen für den gerade bearbeiteten Datensatz anzeigt.

Ändern Sie im zweiten Browserfenster den Preis (behalten Sie den Produktnamen jedoch 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 an wie der erste, der neue Produktname mit dem alten Preis. Die im zweiten Browserfenster vorgenommenen Änderungen gingen verloren. Darüber hinaus gingen die Änderungen ziemlich ruhig verloren, da es keine Ausnahme oder Meldung gab, die darauf hinweist, dass gerade ein Parallelitätsverstoß aufgetreten ist.

Die Änderungen im zweiten Browserfenster wurden im Hintergrund 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, warum die änderungen des zweiten Browsers nicht committet wurden, war, dass die UPDATE Anweisung s-Klausel WHERE alle Datensätze herausgefiltert und daher keine Zeilen beeinflusste. 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 der 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 automatisch fehl.

Aus der Perspektive des Endbenutzers im zweiten Browserfenster kehrt das Raster nach dem Klicken auf die Schaltfläche Aktualisieren in den Vorbearbeitungsmodus zurück, die Änderungen sind jedoch verloren gegangen. Es gibt jedoch kein visuelles Feedback, dass die Änderungen nicht beibehalten wurden. Wenn Änderungen eines Benutzers aufgrund einer Parallelitätsverletzung verloren gehen, wird er im Idealfall benachrichtigt und möglicherweise das Raster im Bearbeitungsmodus beibehalten. Sehen wir uns an, wie dies erreicht wird.

Schritt 3: Ermitteln, wann ein Parallelitätsverstoß 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. Fügen Sie zum Warnen des Benutzers 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 Das Update oder Löschen. Legen Sie die Eigenschaft des Label-Steuerelements CssClass auf Warning fest. Hierbei handelt es sich um eine CSS-Klasse, die in Styles.css definiert ist und Text in einer roten, kursiven, fetten und großen Schriftart anzeigt. Legen Sie schließlich die Eigenschaften Label 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 Bezeichnungssteuerelements zur Seite zum Anzeigen der Warnung

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

Beim Ausführen eines Updates oder Löschens werden die GridView-Ereignishandler RowUpdated und RowDeleted ausgelöst, nachdem die Datenquellensteuerung das angeforderte Update oder Löschvorgang ausgeführt hat. Wir können anhand dieser Ereignishandler bestimmen, wie viele Zeilen vom Vorgang betroffen waren. Wenn 0 Zeilen betroffen sind, möchten wir die ConcurrencyViolationMessage Bezeichnung anzeigen.

Erstellen Sie einen Ereignishandler für das RowUpdated - und RowDeleted -Ereignis, und fügen Sie den folgenden Code hinzu:

protected void Products_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
    if (e.AffectedRows == 0)
    {
        ConcurrencyViolationMessage.Visible = true;
        e.KeepInEditMode = true;
        // Rebind the data to the GridView to show the latest changes
        Products.DataBind();
    }
}
protected void Products_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
    if (e.AffectedRows == 0)
        ConcurrencyViolationMessage.Visible = true;
}

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 gridView außerdem an, im Bearbeitungsmodus zu bleiben, indem wir die KeepInEditMode Eigenschaft auf true festlegen. 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 Abbildung 9 zeigt, wird bei diesen beiden Ereignishandlern immer dann eine sehr auffällige Meldung angezeigt, wenn eine Parallelitätsverletzung auftritt.

Eine Nachricht wird im Gesicht einer Parallelitätsverletzung angezeigt.

Abbildung 9: Eine Meldung wird im Gesicht eines Nebenläufigkeitsverstoßes 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 können, ist es wichtig, Optionen für die Parallelitätssteuerung in Betracht zu ziehen. Standardmäßig verwenden die ASP.NET Datenwebsteuerelemente und Datenquellensteuerelemente keine Parallelitätssteuerung. Wie wir in diesem Tutorial gesehen haben, ist die Implementierung der Steuerung für optimistische Parallelität mit SqlDataSource relativ schnell und einfach. SqlDataSource übernimmt den größten Teil der Aufgaben beim Hinzufügen von erweiterten WHERE Klauseln zu den automatisch generierten UPDATE Und DELETE -Anweisungen, es gibt jedoch einige Feinheiten bei der Behandlung von NULL Wertspalten, wie im Abschnitt Richtig behandelte NULL Werte erläutert.

Dieses Tutorial schließt unsere Untersuchung der SqlDataSource ab. In unseren verbleibenden Tutorials wird die Arbeit mit Daten mithilfe von ObjectDataSource und mehrstufiger 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 Hours. Er kann unter mitchell@4GuysFromRolla.comoder über seinen Blog erreicht werden, der unter http://ScottOnWriting.NETzu finden ist.