Freigeben über


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

von Scott Mitchell

PDF herunterladen

In diesem Tutorial überblicken wir die Grundlagen der optimistischen Parallelitätskontrolle und untersuchen dann, wie diese mithilfe des SqlDataSource-Steuerelements 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 Eigenschaften der Steuerelemente InsertCommand, UpdateCommand, oder DeleteCommand zusammen mit den entsprechenden Parametern in den Auflistungen InsertParameters, UpdateParameters und DeleteParameters angeben. Während diese Eigenschaften und Auflistungen manuell angegeben werden können, bietet der Assistent zum Konfigurieren von Datenquellen über die Schaltfläche "Erweitert" ein Kontrollkästchen "Anweisungen für Generierung von INSERT, UPDATE und DELETE", mit dem diese Anweisungen basierend auf der SELECT-Anweisung automatisch erstellt werden.

Zusammen mit dem Kontrollkästchen „INSERT generieren“, „UPDATE generieren“ und „DELETE Anweisungen generieren“ enthält das Dialogfeld „Erweiterte SQL-Generationsoptionen“ die Option „Optimistische Parallelität verwenden“ (siehe Abbildung 1). Bei Aktivierung werden die WHERE Klauseln in den automatisch generierten UPDATE und DELETE Anweisungen so geändert, dass die Aktualisierung oder Löschung nur durchgeführt wird, wenn die zugrunde liegenden Datenbankdaten seit dem letzten Laden durch den Benutzer in das Gitter 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 befassen wir uns mit den Grundlagen der optimistischen Parallelitätskontrolle und dann untersuchen, wie sie mithilfe der SqlDataSource implementiert werden.

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 "Implementierung 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. Jedoch zeigt die GridView auf Sams Bildschirm weiterhin den Produktnamen in der bearbeitbaren Zeile als Chai an. 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 festlegt, während CategoryID auf die entsprechende Kategorie-ID für die Kategorie Condiments gesetzt wird usw. Jisuns Änderungen an dem Produktnamen wurden überschrieben.

Abbildung 2 veranschaulicht diese Interaktion.

Wenn zwei Benutzer gleichzeitig einen Datensatz aktualisieren, besteht die Möglichkeit, dass die Änderungen eines Benutzers die eines 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 dieses Szenario eintreten kann, muss eine Form der Nebenläufigkeitssteuerung implementiert werden. Optimistische Parallelität, der Schwerpunkt dieses Lernprogramms, basiert auf der Annahme, dass, obwohl es gelegentlich zu Parallelitätskonflikten kommen kann, in der überwiegenden Mehrheit der Fälle solche Konflikte nicht auftreten werden. 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 eine pessimistische Steuerung 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. Nachher, nachdem die Benutzerin ihre Änderungen vorgenommen hat und auf die Schaltfläche "Aktualisieren" klickt, muss die verwendete UPDATE-Anweisung die ursprünglichen Werte sowie die neuen Werte berücksichtigen und den zugrunde liegenden Datenbankdatensatz nur dann aktualisieren, wenn die ursprünglichen Werte, die die Benutzerin zu bearbeiten begonnen hat, mit den Werten identisch 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 diesem Tutorial sehen, ist das Aktivieren der optimistischen Parallelitätssteuerung mit der SqlDataSource genauso einfach wie das Anhaken 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 und legen seine ID-Eigenschaft auf ProductsDataSourceWithOptimisticConcurrency fest. Klicken Sie als Nächstes auf den Link "Datenquelle konfigurieren" im Smarttag des Steuerelements. Wählen Sie im ersten Bildschirm des Assistenten aus, mit dem NORTHWINDConnectionString Sie arbeiten möchten, und klicken Sie auf "Weiter".

Wählen Sie die Arbeit mit NORTHWINDConnectionString

Abbildung 4: Arbeiten mit dem NORTHWINDConnectionString (Klicken, um das 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 "Auswahl der Anweisung" die Products-Tabelle aus der Dropdown-Liste aus und wählen Sie die ProductID, ProductName, UnitPrice und Discontinued Spalten, wie in Abbildung 5 gezeigt.

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 Optionen "Anweisungen generieren" für INSERT, UPDATE, und DELETE sowie "Optimistische Parallelität verwenden" und klicken Sie auf "OK" (siehe Abbildung 1 für einen Screenshot). Vervollständigen Sie den Assistenten, indem Sie auf "Weitergehen" und dann auf "Abschließen" 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-Sammlungen 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 WHERE Klauseln der UpdateCommand- und DeleteCommand-Eigenschaften (und dem Hinzufügen der zusätzlichen Parameter zu den jeweiligen Parameterauflistungen) passt die Auswahl der Option zur Verwendung von optimistischer Parallelität zwei weitere Eigenschaften an.

Wenn das Webdatensteuerelement die SqlDataSource-Update()- oder Delete()-Methode 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 Originalparameter in den UpdateCommand- und DeleteCommand-Eigenschaften und in den UpdateParameters- und DeleteParameters-Auflistungen entsprechend.

Hinweis

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

Korrekte Verarbeitung vonNULLWerten

Leider funktionieren die von den Assistenten zum Konfigurieren von Datenquellen automatisch generierten UPDATE und DELETE Anweisungen 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

In der UnitPrice Tabelle kann die Products Spalte NULL Werte enthalten. Wenn ein bestimmter Datensatz einen NULL-Wert für UnitPrice hat, ergibt der WHERE-Klauselbereich [UnitPrice] = @original_UnitPriceimmer den Wert Falsch, da NULL = NULL stets Falsch 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 erstmals im Juni 2004 in SqlDataSource Generiert Falsche SQL-Anweisungen an Microsoft gemeldet und soll Berichten zufolge 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. Wechseln Sie generell [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. Erneut muss diese Änderung für jede Spalte in der UpdateCommand und DeleteCommandWHERE-Klausel vorgenommen werden, die NULL Werte enthalten kann.

Durch die Anwendung auf unser Beispiel ergeben sich die folgenden geänderten UpdateCommand- 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 Bearbeitungs- und Löschoptionen

Da der SqlDataSource konfiguriert ist, um optimistische Parallelität zu unterstützen, bleibt nur noch, der Seite ein Daten-Websteuerelement 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 es mit ID auf Products fest. Binden Sie das Smarttag von GridView an das SqlDataSource-Steuerelement, das Sie in Schritt 1 hinzugefügt haben. Überprüfen Sie abschließend die Optionen "Bearbeiten aktivieren" und "Löschen aktivieren" aus dem Smarttag.

GridView an SqlDataSource binden und Bearbeiten und Löschen aktivieren

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 Parallelitätskontrolle 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 zurückgesendet und der GridView kehrt in den Bearbeitungsmodus zurück, wobei er den neuen Produktnamen für den soeben bearbeiteten Datensatz anzeigt.

Ä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: den neuen Produktnamen mit dem alten Preis. Die im zweiten Browserfenster vorgenommenen Änderungen gingen verloren. Darüber hinaus gingen die Änderungen auf unauffällige Weise verloren, da keine Ausnahme oder Meldung darauf hinwies, dass gerade eine Verletzung der Nebenläufigkeit aufgetreten ist.

Die Änderungen im zweiten Browserfenster gingen stillschweigend 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 des zweiten Browsers nicht übernommen wurden, liegt daran, 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 Anweisungsklausel WHERE nicht übereinstimmen, schlägt der Löschvorgang ohne Benachrichtigung 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 würden wir den Benutzer benachrichtigen, wenn die Änderungen eines Benutzers durch eine Concurrency-Verletzung verloren gehen, und das Raster möglicherweise im Bearbeitungsmodus belassen. 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 überarbeiten Sie dann das Update oder löschen es. Legen Sie die Eigenschaft des Label-Steuerelements CssClass auf "Warning" fest. Dabei handelt es sich um eine CSS-Klasse, die Text Styles.css in einer roten, kursiven, fetten und großen Schriftart anzeigt. Legen Sie schließlich die Label- und die Eigenschaften-Einstellungen von Visible und EnableViewState auf false. Dadurch wird das Label ausgeblendet, mit Ausnahme der Postbacks, bei denen wir die Eigenschaft ausdrücklich auf Visible setzen.

Fügen Sie ein Label-Steuerelement 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 einer Aktualisierung oder Löschung werden die GridView-Ereignishandlers RowUpdated und RowDeleted ausgelöst, nachdem die Datenquellensteuerung 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 sowohl die RowUpdated- als auch die RowDeleted-Ereignisse 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 wenn sie gleich 0 ist, setzen wir die ConcurrencyViolationMessageVisible Eigenschaft 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 angesichts einer Nebenläufigkeitsverletzung angezeigt.

Abbildung 9: Eine Meldung wird im Fall einer Parallelitätsverletzung 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ätskontrolle. Wie wir in diesem Lernprogramm gesehen haben, ist die Implementierung der optimistischen Parallelitätssteuerung mit SqlDataSource relativ schnell und einfach. Die SqlDataSource übernimmt die meisten Vorarbeiten für das Hinzufügen erweiterter WHERE Klauseln zu den automatisch generierten UPDATE und DELETE Anweisungen, aber es gibt einige Subtilitäten bei der Behandlung von NULL Wertspalten, wie im Abschnitt "Richtige Behandlung von NULL Werten" beschrieben.

Dieses Lernprogramm schließt unsere Untersuchung der SqlDataSource ab. Unsere verbleibenden Lernprogramme werden sich wieder mit der Arbeit mit Daten, unter Verwendung von ObjectDataSource und mehrschichtiger 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.