Freigeben über


Implementierung optimistischer Gleichzeitigkeit (VB)

von Scott Mitchell

PDF herunterladen

Für eine Webanwendung, die es mehreren Benutzern ermöglicht, Daten zu bearbeiten, besteht das Risiko, dass zwei Benutzer dieselben Daten gleichzeitig bearbeiten können. In diesem Tutorial wenden wir optimistische Parallelitätssteuerung zur Behandlung dieses Risikos an.

Einleitung

Für Webanwendungen, die es Benutzern nur ermöglichen, Daten anzuzeigen, oder für Diejenigen, die nur einen einzelnen Benutzer umfassen, der Daten ändern kann, besteht keine Gefahr, dass zwei gleichzeitige Benutzer versehentlich die Änderungen eines anderen überschreiben. Für Webanwendungen, die es mehreren Benutzern ermöglichen, Daten zu aktualisieren oder zu löschen, besteht jedoch das Potenzial, dass die Änderungen eines Benutzers mit einem anderen gleichzeitigen Benutzer kollidieren. Ohne eine Parallelitätsrichtlinie wird beim gleichzeitigen Bearbeiten eines einzelnen Datensatzes der Nutzer, der seine Änderungen zuletzt übernimmt, die Änderungen außer Kraft setzen, die vom ersten vorgenommen wurden.

Stellen Sie sich beispielsweise vor, dass zwei Benutzer, Jisun und Sam, beide eine Seite in unserer Anwendung besucht haben, mit der Besucher die Produkte über ein GridView-Steuerelement aktualisieren und löschen konnten. Beide klicken auf die Schaltfläche "Bearbeiten" in der GridView 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 hat die Datenbank die Werte "Chai Tea", die Kategorie "Getränke", den Lieferanten Exotische Flüssigkeiten usw. für dieses bestimmte Produkt. Auf dem Bildschirm von Sam wird jedoch weiterhin der Produktname in der bearbeitbaren GridView-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, die den Produktnamen auf "Chai", die CategoryID entsprechende Kategorie-ID für Getränke usw. festlegt. Jisuns Änderungen an dem Produktnamen wurden überschrieben. In Abbildung 1 wird diese Ereignisreihe grafisch dargestellt.

Wenn zwei Benutzer gleichzeitig einen Datensatz aktualisieren, besteht die Möglichkeit, dass die Änderungen eines Benutzers die Änderungen des anderen überschreiben

Abbildung 1: 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)

Wenn zwei Benutzer eine Seite besuchen, befindet sich ein Benutzer möglicherweise mitten in der Aktualisierung eines Datensatzes, wenn er von einem anderen Benutzer gelöscht wird. Oder zwischen dem Laden einer Seite und dem Klicken auf die Schaltfläche "Löschen" hat ein anderer Benutzer möglicherweise den Inhalt dieses Datensatzes geändert.

Es stehen drei Parallelitätskontrollstrategien zur Verfügung:

  • Do Nothing -if gleichzeitig modifizierende Benutzer ändern denselben Datensatz, lassen Sie den letzten Commit gewinnen (das Standardverhalten)
  • Optimistische Parallelität - nehmen Sie an, dass es zwar gelegentlich zu Parallelitätskonflikten kommen kann, in der überwiegenden Mehrheit der Fälle jedoch nicht; Tritt ein Konflikt auf, informieren Sie den Benutzer einfach darüber, dass seine Änderungen nicht gespeichert werden können, da ein anderer Benutzer die gleichen Daten geändert hat.
  • Pessimistische Parallelität – gehen Sie davon aus, dass Parallelitätskonflikte üblich sind und dass Benutzer nicht tolerieren, dass ihre Änderungen aufgrund der gleichzeitigen Aktivität eines anderen Benutzers nicht gespeichert wurden; Wenn ein Benutzer mit der Aktualisierung eines Datensatzes beginnt, sperren Sie ihn, wodurch verhindert wird, dass andere Benutzer diesen Datensatz bearbeiten oder löschen, bis der Benutzer seine Änderungen festnimmt.

Alle unsere Tutorials haben bisher die Standardstrategie zur Konfliktlösung bei Nebenläufigkeit verwendet – nämlich die Strategie "Letzter Schreibvorgang gewinnt". In diesem Lernprogramm untersuchen wir, wie optimistische Parallelitätssteuerelemente implementiert werden.

Hinweis

In dieser Lernprogrammreihe werden wir keine pessimistischen Parallelitätsbeispiele betrachten. Pessimistische Parallelität wird selten verwendet, da solche Sperren, wenn sie nicht ordnungsgemäß freigegeben werden, andere Benutzer daran hindern können, Daten zu ändern. Wenn ein Benutzer z. B. einen Datensatz zum Bearbeiten sperrt und dann vor der Entsperrung für den Tag verlässt, kann kein anderer Benutzer diesen Datensatz aktualisieren, bis der ursprüngliche Benutzer seine Aktualisierung zurückgibt und abgeschlossen hat. Daher gibt es in Situationen, in denen ein pessimistisches Concurrency-Modell verwendet wird, in der Regel ein Timeout, das die Sperre aufhebt, sobald es erreicht wird. Ticketverkaufswebsites, die einen bestimmten Sitzplatz für kurze Zeit sperren, während der Benutzer den Bestellvorgang abgeschlossen hat, ist ein Beispiel für pessimistische Parallelitätskontrolle.

Schritt 1: Untersuchen, wie optimistische Parallelität implementiert wird

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 Werte des Datensatzes aus der Datenbank gelesen und in TextBoxes und anderen Websteuerelementen angezeigt. Diese ursprünglichen Werte werden von GridView gespeichert. Nachdem der Benutzer ihre Änderungen vorgenommen hat und auf die Schaltfläche "Aktualisieren" klickt, werden die ursprünglichen Werte sowie die neuen Werte an die Geschäftslogikebene und dann nach unten an die Datenzugriffsschicht gesendet. Die Datenzugriffsebene muss eine SQL-Anweisung ausgeben, die den Datensatz nur aktualisiert, wenn die ursprünglichen Werte, die der Benutzer bearbeitet hat, mit den Werten identisch sind, die sich noch in der Datenbank befinden. Abbildung 2 zeigt diese Abfolge von Ereignissen.

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

Abbildung 2: Damit "Aktualisieren" oder "Löschen erfolgreich" ist, müssen die ursprünglichen Werte den aktuellen Datenbankwerten entsprechen (Klicken Sie, 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). Das ADO.NET Typed DataSet bietet eine Lösung, die durch einfaches Anklicken eines Kontrollkästchens konfiguriert werden kann. Durch Aktivieren der optimistischen Parallelität für ein TableAdapter-Objekt im Typed DataSet werden die Anweisungen des TableAdapters UPDATE und DELETE erweitert, indem ein Vergleich aller ursprünglichen Werte in der WHERE-Klausel einbezogen wird. 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

Hinweis

Diese UPDATE Anweisung wurde zur Lesbarkeit vereinfacht. In der Praxis wäre die UnitPrice Überprüfung der WHERE Klausel umfassender, da UnitPriceNULLs enthalten kann und zu überprüfen, ob NULL = NULL immer 'False' zurückgibt (stattdessen sollten Sie IS NULL verwenden).

Zusätzlich zur Verwendung einer anderen zugrunde liegenden UPDATE Anweisung ändert das Konfigurieren eines TableAdapters für die Nutzung von Optimistischer Parallelität auch die Signatur seiner direkten DB-Methoden. Erinnern Sie sich an unser erstes Lernprogramm zum Erstellen einer Datenzugriffsschicht, dass db-direkte Methoden diejenigen waren, die eine Liste skalarer Werte als Eingabeparameter akzeptieren (anstelle einer stark typierten DataRow- oder DataTable-Instanz). Bei Verwendung optimistischer Nebenläufigkeit enthalten die direkten DB-Methoden Update() und Delete() auch Eingabeparameter für die ursprünglichen Werte. Darüber hinaus muss der Code in der BLL für die Verwendung des Batchaktualisierungsmusters (die Methodenüberladungen, die Update() DataRows und DataTables akzeptieren, anstelle von Skalarwerten) ebenfalls geändert werden.

Anstatt die tableAdapters unserer vorhandenen DAL so zu erweitern, dass optimistische Parallelität verwendet wird (was eine Änderung der BLL erforderlich wäre, um dies zu berücksichtigen), erstellen wir stattdessen ein neues typiertes DataSet namens NorthwindOptimisticConcurrency, dem wir einen Products TableAdapter hinzufügen, der optimistische Parallelität verwendet. Anschließend erstellen wir eine ProductsOptimisticConcurrencyBLL Business Logic Layer-Klasse, die über die entsprechenden Änderungen verfügt, um die optimistische Parallelität DAL zu unterstützen. Sobald diese Grundarbeiten erstellt wurden, können wir die ASP.NET Seite erstellen.

Schritt 2: Erstellen einer Datenzugriffsebene, die optimistische Parallelität unterstützt

Um ein neues Typed DataSet zu erstellen, klicken Sie mit der rechten Maustaste auf den DAL Ordner innerhalb des App_Code Ordners, und fügen Sie ein neues DataSet mit dem Namen hinzu NorthwindOptimisticConcurrency. Wie wir im ersten Tutorial gesehen haben, wird dadurch ein neuer TableAdapter zum Typed DataSet hinzugefügt, wobei der TableAdapter-Konfigurations-Assistent automatisch gestartet wird. Im ersten Bildschirm werden wir aufgefordert, die Datenbank anzugeben, mit der eine Verbindung hergestellt werden soll – verbinden Sie sich mit derselben Northwind-Datenbank über die NORTHWNDConnectionString-Einstellung von Web.config.

Herstellen einer Verbindung mit derselben Northwind-Datenbank

Abbildung 3: Herstellen einer Verbindung mit derselben Northwind-Datenbank (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Als Nächstes werden wir aufgefordert, wie die Daten abgefragt werden: über eine Ad-hoc-SQL-Anweisung, eine neue gespeicherte Prozedur oder eine vorhandene gespeicherte Prozedur. Da wir Ad-hoc-SQL-Abfragen in unserem ursprünglichen DAL verwendet haben, verwenden Sie diese Option auch hier.

Angeben der abzurufenden Daten mithilfe einer Ad-hoc-SQL-Anweisung

Abbildung 4: Angeben der abzurufenden Daten mithilfe einer Ad-hoc-SQL-Anweisung (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Geben Sie auf dem folgenden Bildschirm die SQL-Abfrage ein, die zum Abrufen der Produktinformationen verwendet werden soll. Verwenden wir die genaue SQL-Abfrage, die für den Products TableAdapter aus unserer ursprünglichen DAL verwendet wird, die alle Product Spalten zusammen mit den Lieferanten- und Kategorienamen des Produkts zurückgibt:

SELECT   ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
           UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
           (SELECT CategoryName FROM Categories
              WHERE Categories.CategoryID = Products.CategoryID)
              as CategoryName,
           (SELECT CompanyName FROM Suppliers
              WHERE Suppliers.SupplierID = Products.SupplierID)
              as SupplierName
FROM     Products

Verwenden Sie die gleiche SQL-Abfrage aus dem Products TableAdapter im ursprünglichen DAL

Abbildung 5: Verwenden der gleichen SQL-Abfrage aus dem Products TableAdapter im ursprünglichen DAL (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Bevor Sie auf den nächsten Bildschirm wechseln, klicken Sie auf die Schaltfläche "Erweiterte Optionen". Damit dieser TableAdapter optimistische Parallelitätssteuerelemente verwendet, aktivieren Sie einfach das Kontrollkästchen "Optimistische Parallelität verwenden".

Aktivieren Sie die optimistische Parallelitätskontrolle, indem Sie das Kontrollkästchen "Optimistische Parallelität verwenden" aktivieren.

Abbildung 6: Aktivieren des optimistischen Parallelitätssteuerelements durch Überprüfen des CheckBox -Steuerelements "Optimistische Parallelität verwenden" (Klicken Sie, um das Bild mit voller Größe anzuzeigen)

Geben Sie schließlich an, dass "TableAdapter" die Datenzugriffsmuster verwenden soll, die sowohl eine DataTable ausfüllen als auch eine DataTable zurückgeben. Geben Sie außerdem an, dass die direkten DB-Methoden erstellt werden sollen. Ändern Sie den Methodennamen für das "DataTable-Muster zurückgeben" von "GetData" in "GetProducts", um die Namenskonventionen zu spiegeln, die wir in unserem ursprünglichen DAL verwendet haben.

Lassen Sie den TableAdapter alle Datenzugriffsmuster nutzen.

Abbildung 7: Lassen Sie den TableAdapter alle Datenzugriffsmuster nutzen (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Nach Abschluss des Assistenten enthält der DataSet-Designer eine stark typisierte Products DataTable sowie einen TableAdapter. Nehmen Sie sich einen Moment Zeit, um die DataTable von Products zu ProductsOptimisticConcurrency umzubenennen, was Sie tun können, indem Sie mit der rechten Maustaste auf die Titelleiste der DataTable klicken und im Kontextmenü "Umbenennen" auswählen.

Ein DataTable und ein TableAdapter wurden dem typisierten DataSet hinzugefügt.

Abbildung 8: Ein DataTable- und TableAdapter-Objekt wurde dem typierten DataSet hinzugefügt (Klicken, um das Bild in voller Größe anzuzeigen)

Um die Unterschiede zwischen den UPDATE und DELETE Abfragen zwischen dem ProductsOptimisticConcurrency TableAdapter (der optimistische Parallelität verwendet) und dem Products TableAdapter (der nicht verwendet) anzuzeigen, klicken Sie auf den TableAdapter und gehen Sie zum Eigenschaftenfenster. In den DeleteCommand- und UpdateCommand-Eigenschaften können Sie in den Untereigenschaften von CommandText die tatsächliche SQL-Syntax sehen, die an die Datenbank gesendet wird, wenn die Update- oder Delete-Methoden des DAL aufgerufen werden. Für den ProductsOptimisticConcurrency TableAdapter lautet die DELETE verwendete Anweisung:

DELETE FROM [Products]
    WHERE (([ProductID] = @Original_ProductID)
    AND ([ProductName] = @Original_ProductName)
    AND ((@IsNull_SupplierID = 1 AND [SupplierID] IS NULL)
       OR ([SupplierID] = @Original_SupplierID))
    AND ((@IsNull_CategoryID = 1 AND [CategoryID] IS NULL)
       OR ([CategoryID] = @Original_CategoryID))
    AND ((@IsNull_QuantityPerUnit = 1 AND [QuantityPerUnit] IS NULL)
       OR ([QuantityPerUnit] = @Original_QuantityPerUnit))
    AND ((@IsNull_UnitPrice = 1 AND [UnitPrice] IS NULL)
       OR ([UnitPrice] = @Original_UnitPrice))
    AND ((@IsNull_UnitsInStock = 1 AND [UnitsInStock] IS NULL)
       OR ([UnitsInStock] = @Original_UnitsInStock))
    AND ((@IsNull_UnitsOnOrder = 1 AND [UnitsOnOrder] IS NULL)
       OR ([UnitsOnOrder] = @Original_UnitsOnOrder))
    AND ((@IsNull_ReorderLevel = 1 AND [ReorderLevel] IS NULL)
       OR ([ReorderLevel] = @Original_ReorderLevel))
    AND ([Discontinued] = @Original_Discontinued))

Während die DELETE Anweisung für den Product TableAdapter in unserem ursprünglichen DAL viel einfacher ist:

DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))

Wie Sie sehen können, enthält die WHERE Klausel in der DELETE Anweisung für den TableAdapter, die optimistische Parallelität verwendet, einen Vergleich zwischen den vorhandenen Spaltenwerten der Product Tabelle und den ursprünglichen Werten zum Zeitpunkt, zu dem die GridView (oder DetailsView oder FormView) zuletzt aufgefüllt wurde. Da alle Felder außer ProductID, ProductName und DiscontinuedNULL Werte enthalten können, werden zusätzliche Parameter und Prüfungen eingeschlossen, um NULL Werte in der WHERE Klausel ordnungsgemäß zu vergleichen.

Wir werden für dieses Tutorial keine zusätzlichen DataTables zum optimistischem Parallelitäts-fähigen DataSet hinzufügen, da die ASP.NET-Seite nur das Aktualisieren und Löschen von Produktinformationen bietet. Dem TableAdapter muss die GetProductByProductID(productID) Methode jedoch weiterhin hinzugefügt ProductsOptimisticConcurrency werden.

Klicken Sie dazu mit der rechten Maustaste auf die Titelleiste des TableAdapters (den Bereich rechts über den Namen der FillGetProducts Methoden), und wählen Sie im Kontextmenü "Abfrage hinzufügen" aus. Dadurch wird der Konfigurations-Assistent für TableAdapter-Abfragen gestartet. Wie bei der Erstkonfiguration von TableAdapter können Sie die GetProductByProductID(productID) Methode mit einer Ad-hoc-SQL-Anweisung erstellen (siehe Abbildung 4). Da die GetProductByProductID(productID) Methode Informationen zu einem bestimmten Produkt zurückgibt, geben Sie an, dass es sich bei dieser Abfrage um einen SELECT Abfragetyp handelt, der Zeilen zurückgibt.

Markieren des Abfragetyps als

Abbildung 9: Markieren des Abfragetyps als "SELECT ,die Zeilen zurückgibt" (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Auf dem nächsten Bildschirm werden wir aufgefordert, die sql-Abfrage zu verwenden, wobei die Standardabfrage von TableAdapter bereits geladen ist. Erweitern Sie die vorhandene Abfrage, um die Klausel WHERE ProductID = @ProductIDeinzuschließen, wie in Abbildung 10 dargestellt.

Hinzufügen einer WHERE-Klausel zur vorab geladenen Abfrage, um einen bestimmten Produktdatensatz zurückzugeben

Abbildung 10: Hinzufügen einer WHERE Klausel zur vorab geladenen Abfrage, um einen bestimmten Produktdatensatz zurückzugeben (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Ändern Sie schließlich die generierten Methodennamen in FillByProductID und GetProductByProductID.

Benennen Sie die Methoden in FillByProductID und GetProductByProductID um.

Abbildung 11: Umbenennen der Methoden in FillByProductID und GetProductByProductID (Klicken, um das Bild in voller Größe anzuzeigen)

Nachdem dieser Assistent abgeschlossen ist, enthält "TableAdapter" nun zwei Methoden zum Abrufen von Daten: GetProducts(), die alle Produkte zurückgibt und GetProductByProductID(productID)die das angegebene Produkt zurückgibt.

Schritt 3: Erstellen einer Geschäftslogikschicht für die optimistische Concurrency-Enabled DAL

Unsere vorhandene ProductsBLL Klasse enthält Beispiele für die Verwendung von Batchaktualisierungs- und DB-Direkten Mustern. Die Methode AddProduct und die Überladungen UpdateProduct verwenden beide das Muster der Blockaktualisierung, wobei eine Instanz ProductRow an die Update-Methode des TableAdapters übergeben wird. Die DeleteProduct Methode verwendet dagegen das direkte DB-Muster und ruft die TableAdapter-Methode Delete(productID) auf.

Mit dem neuen ProductsOptimisticConcurrency TableAdapter erfordern die direkten DB-Methoden jetzt, dass auch die ursprünglichen Werte übergeben werden. Beispielsweise erwartet die Delete-Methode jetzt zehn Eingabeparameter: die ursprünglichen ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel und Discontinued. Sie verwendet die Werte dieser zusätzlichen Eingabeparameter in der WHERE-Klausel der DELETE-Anweisung, die an die Datenbank gesendet wird, und löscht den angegebenen Datensatz nur, wenn die aktuellen Werte der Datenbank den ursprünglichen Werten entsprechen.

Die Methodensignatur für die Methode Update des TableAdapter, die im Batch-Update-Muster verwendet wird, hat sich zwar nicht geändert, aber der Code, der zum Aufzeichnen der ursprünglichen und neuen Werte benötigt wird, hat sich geändert. Anstatt daher die optimistische Parallelitäts-fähige DAL mit unserer vorhandenen ProductsBLL Klasse zu verwenden, erstellen wir eine neue Business Logic Layer-Klasse für die Arbeit mit unserem neuen DAL.

Fügen Sie eine Klasse mit dem Namen ProductsOptimisticConcurrencyBLL in den Ordner BLL innerhalb des Ordners App_Code hinzu.

Hinzufügen der ProductsOptimisticConcurrencyBLL-Klasse zum BLL-Ordner

Abbildung 12: Hinzufügen der ProductsOptimisticConcurrencyBLL Klasse zum BLL-Ordner

Fügen Sie als Nächstes der Klasse den folgenden Code hinzu ProductsOptimisticConcurrencyBLL :

Imports NorthwindOptimisticConcurrencyTableAdapters
<System.ComponentModel.DataObject()> _
Public Class ProductsOptimisticConcurrencyBLL
    Private _productsAdapter As ProductsOptimisticConcurrencyTableAdapter = Nothing
    Protected ReadOnly Property Adapter() As ProductsOptimisticConcurrencyTableAdapter
        Get
            If _productsAdapter Is Nothing Then
                _productsAdapter = New ProductsOptimisticConcurrencyTableAdapter()
            End If
            Return _productsAdapter
        End Get
    End Property
    <System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Select, True)> _
    Public Function GetProducts() As _
        NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable
        Return Adapter.GetProducts()
    End Function
End Class

Beachten Sie die using-Anweisung NorthwindOptimisticConcurrencyTableAdapters über dem Anfang der Klassendeklaration. Der NorthwindOptimisticConcurrencyTableAdapters Namespace enthält die ProductsOptimisticConcurrencyTableAdapter Klasse, die die DAL-Methoden bereitstellt. Außerdem vor der Klassendeklaration finden Sie das System.ComponentModel.DataObject Attribut, das Visual Studio anweist, diese Klasse in die Dropdownliste des ObjectDataSource-Assistenten einzuschließen.

Die Eigenschaft ProductsOptimisticConcurrencyBLL von Adapter bietet schnellen Zugriff auf eine Instanz der Klasse ProductsOptimisticConcurrencyTableAdapter und folgt dem Muster, das in unseren ursprünglichen BLL-Klassen (ProductsBLL, CategoriesBLL und so weiter) verwendet wird. Schließlich ruft die GetProducts() Methode einfach die DAL-Methode GetProducts() auf und gibt ein ProductsOptimisticConcurrencyDataTable Objekt zurück, das mit einer ProductsOptimisticConcurrencyRow Instanz für jeden Produktdatensatz in der Datenbank gefüllt ist.

Löschen eines Produkts mithilfe des DB Direct-Musters mit optimistischer Parallelität

Bei der Verwendung des direkten DB-Musters in einer DAL, die optimistische Parallelität nutzt, müssen den Methoden die neuen und ursprünglichen Werte übergeben werden. Zum Löschen gibt es keine neuen Werte, daher müssen nur die ursprünglichen Werte übergeben werden. In unserer BLL müssen wir dann alle ursprünglichen Parameter als Eingabeparameter akzeptieren. Lassen Sie uns die Methode DeleteProduct in der Klasse ProductsOptimisticConcurrencyBLL verwenden, damit sie die DB Direct-Methode nutzt. Dies bedeutet, dass diese Methode alle zehn Produktdatenfelder als Eingabeparameter übernehmen und diese an die DAL übergeben muss, wie im folgenden Code gezeigt:

<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Delete, True)> _
Public Function DeleteProduct( _
    ByVal original_productID As Integer, ByVal original_productName As String, _
    ByVal original_supplierID As Nullable(Of Integer), _
    ByVal original_categoryID As Nullable(Of Integer), _
    ByVal original_quantityPerUnit As String, _
    ByVal original_unitPrice As Nullable(Of Decimal), _
    ByVal original_unitsInStock As Nullable(Of Short), _
    ByVal original_unitsOnOrder As Nullable(Of Short), _
    ByVal original_reorderLevel As Nullable(Of Short), _
    ByVal original_discontinued As Boolean) _
    As Boolean
    Dim rowsAffected As Integer = Adapter.Delete(
                                    original_productID, _
                                    original_productName, _
                                    original_supplierID, _
                                    original_categoryID, _
                                    original_quantityPerUnit, _
                                    original_unitPrice, _
                                    original_unitsInStock, _
                                    original_unitsOnOrder, _
                                    original_reorderLevel, _
                                    original_discontinued)
    ' Return true if precisely one row was deleted, otherwise false
    Return rowsAffected = 1
End Function

Wenn sich die ursprünglichen Werte – die Werte, die zuletzt in das GridView -Objekt (oder DetailsView oder FormView) geladen wurden – von den Werten in der Datenbank unterscheiden, wenn der Benutzer auf die Schaltfläche "Löschen" klickt, stimmt die WHERE Klausel nicht mit einem Datenbankdatensatz überein, und es sind keine Datensätze betroffen. Daher wird die Methode Delete des TableAdapters 0 zurückgeben, und die Methode DeleteProduct des BLL wird false zurückgeben.

Aktualisieren eines Produkts mithilfe des Batch-Update-Musters mit optimistischer Synchronisation

Wie bereits erwähnt, weist die TableAdapter-Methode für das Batchaktualisierungsmuster Update dieselbe Methodensignatur auf, unabhängig davon, ob optimistische Parallelität verwendet wird. Die Methode erwartet nämlich Update ein DataRow,ein Array von DataRows, eine DataTable oder ein typiertes DataSet. Es gibt keine zusätzlichen Eingabeparameter zum Angeben der ursprünglichen Werte. Dies ist möglich, da die DataTable die ursprünglichen und geänderten Werte für seine DataRow(n) nachverfolgt. Wenn die DAL ihre UPDATE Anweisung ausgibt, werden die @original_ColumnName Parameter mit den ursprünglichen Werten von DataRow aufgefüllt, während die @ColumnName Parameter mit den geänderten Werten von DataRow aufgefüllt werden.

In der ProductsBLL Klasse (die unsere ursprüngliche, nicht optimistische Parallelität DAL verwendet) bei Verwendung des Batchaktualisierungsmusters zum Aktualisieren von Produktinformationen führt unser Code die folgende Abfolge von Ereignissen aus:

  1. Aktuelle Produktinformationen aus der Datenbank in eine ProductRow Instanz mithilfe der Methode des TableAdapters GetProductByProductID(productID) lesen
  2. Zuweisen der neuen Werte zur ProductRow Instanz aus Schritt 1
  3. Rufen Sie die Methode des TableAdapters Update auf und übergeben Sie die Instanz ProductRow.

Diese Schrittabfolge unterstützt jedoch keine optimistische Parallelität, da die ProductRow in Schritt 1 aufgefüllte Datei direkt aus der Datenbank aufgefüllt wird, was bedeutet, dass die ursprünglichen Werte, die von DataRow verwendet werden, diejenigen sind, die derzeit in der Datenbank vorhanden sind, und nicht diejenigen, die am Anfang des Bearbeitungsprozesses an gridView gebunden waren. Stattdessen müssen wir bei Verwendung einer optimistischen Parallelitäts-fähigen DAL die UpdateProduct Methodenüberladungen ändern, um die folgenden Schritte auszuführen:

  1. Aktuelle Produktinformationen aus der Datenbank in eine ProductsOptimisticConcurrencyRow Instanz mithilfe der Methode des TableAdapters GetProductByProductID(productID) lesen
  2. Zuweisen der ursprünglichen Werte zur ProductsOptimisticConcurrencyRow Instanz aus Schritt 1
  3. Rufen Sie die Methode der ProductsOptimisticConcurrencyRow Instanz AcceptChanges() auf, die dataRow anweist, dass die aktuellen Werte die "ursprünglichen" werte sind.
  4. Zuweisen der neuen Werte zur ProductsOptimisticConcurrencyRow Instanz
  5. Rufen Sie die Methode des TableAdapters Update auf und übergeben Sie die Instanz ProductsOptimisticConcurrencyRow.

Schritt 1 liest alle aktuellen Datenbankwerte für den angegebenen Produktdatensatz vor. Dieser Schritt ist in der UpdateProduct-Überladung, die alle Produktspalten aktualisiert, überflüssig (da diese Werte in Schritt 2 überschrieben werden), ist aber unerlässlich für diejenigen Überladungen, bei denen nur eine Teilmenge der Spaltenwerte als Eingabeparameter übergeben wird. Sobald die ursprünglichen Werte der ProductsOptimisticConcurrencyRow Instanz zugewiesen wurden, wird die AcceptChanges() Methode aufgerufen, die die aktuellen DataRow-Werte als die ursprünglichen Werte kennzeichnet, die in den @original_ColumnName Parametern in der UPDATE Anweisung verwendet werden sollen. Als Nächstes werden die neuen Parameterwerte dem ProductsOptimisticConcurrencyRow zugewiesen, und schließlich wird die Update-Methode aufgerufen, wobei die DataRow übergeben wird.

Der folgende Code zeigt die UpdateProduct-Überladung, die alle Produktdatenfelder als Eingabeparameter akzeptiert. Obwohl hier nicht gezeigt, enthält die ProductsOptimisticConcurrencyBLL klasse, die im Download für dieses Lernprogramm enthalten ist, auch eine UpdateProduct Überladung, die nur den Namen und den Preis des Produkts als Eingabeparameter akzeptiert.

Protected Sub AssignAllProductValues( _
    ByVal product As NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow, _
    ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _
    ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _
    ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
    ByVal unitsOnOrder As Nullable(Of Short), ByVal reorderLevel As Nullable(Of Short), _
    ByVal discontinued As Boolean)
    product.ProductName = productName
    If Not supplierID.HasValue Then
        product.SetSupplierIDNull()
    Else
        product.SupplierID = supplierID.Value
    End If
    If Not categoryID.HasValue Then
        product.SetCategoryIDNull()
    Else
        product.CategoryID = categoryID.Value
    End If
    If quantityPerUnit Is Nothing Then
        product.SetQuantityPerUnitNull()
    Else
        product.QuantityPerUnit = quantityPerUnit
    End If
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    If Not unitsOnOrder.HasValue Then
        product.SetUnitsOnOrderNull()
    Else
        product.UnitsOnOrder = unitsOnOrder.Value
    End If
    If Not reorderLevel.HasValue Then
        product.SetReorderLevelNull()
    Else
        product.ReorderLevel = reorderLevel.Value
    End If
    product.Discontinued = discontinued
End Sub
<System.ComponentModel.DataObjectMethodAttribute( _
System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct(
    ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _
    ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _
    ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
    ByVal unitsOnOrder As Nullable(Of Short), ByVal reorderLevel As Nullable(Of Short), _
    ByVal discontinued As Boolean, ByVal productID As Integer, _
    _
    ByVal original_productName As String, _
    ByVal original_supplierID As Nullable(Of Integer), _
    ByVal original_categoryID As Nullable(Of Integer), _
    ByVal original_quantityPerUnit As String, _
    ByVal original_unitPrice As Nullable(Of Decimal), _
    ByVal original_unitsInStock As Nullable(Of Short), _
    ByVal original_unitsOnOrder As Nullable(Of Short), _
    ByVal original_reorderLevel As Nullable(Of Short), _
    ByVal original_discontinued As Boolean, _
    ByVal original_productID As Integer) _
    As Boolean
    'STEP 1: Read in the current database product information
    Dim products As _
        NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable = _
        Adapter.GetProductByProductID(original_productID)
    If products.Count = 0 Then
        ' no matching record found, return false
        Return False
    End If
    Dim product As _
        NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow = products(0)
    'STEP 2: Assign the original values to the product instance
    AssignAllProductValues( _
        product, original_productName, original_supplierID, _
        original_categoryID, original_quantityPerUnit, original_unitPrice, _
        original_unitsInStock, original_unitsOnOrder, original_reorderLevel, _
        original_discontinued)
    'STEP 3: Accept the changes
    product.AcceptChanges()
    'STEP 4: Assign the new values to the product instance
    AssignAllProductValues( _
        product, productName, supplierID, categoryID, quantityPerUnit, unitPrice, _
        unitsInStock, unitsOnOrder, reorderLevel, discontinued)
    'STEP 5: Update the product record
    Dim rowsAffected As Integer = Adapter.Update(product)
    ' Return true if precisely one row was updated, otherwise false
    Return rowsAffected = 1
End Function

Schritt 4: Übergeben der ursprünglichen und neuen Werte von der ASP.NET Seite an die BLL-Methoden

Da dal und BLL abgeschlossen sind, müssen Sie eine ASP.NET Seite erstellen, die die optimistische Parallelitätslogik nutzen kann, die in das System integriert ist. Insbesondere muss das Datenwebsteuerelement (GridView, DetailsView oder FormView) seine ursprünglichen Werte speichern, und die ObjectDataSource muss beide Wertesätze an die Geschäftslogikebene übergeben. Darüber hinaus muss die ASP.NET Seite so konfiguriert werden, dass Parallelitätsverletzungen ordnungsgemäß behandelt werden.

Öffnen Sie zunächst die OptimisticConcurrency.aspx Seite im EditInsertDelete Ordner, fügen Sie dem Designer eine GridView hinzu und legen Sie dessen ID Eigenschaft auf ProductsGrid fest. Wählen Sie im Schnellzugriff von GridView aus, eine neue ObjectDataSource namens ProductsOptimisticConcurrencyDataSource zu erstellen. Da diese ObjectDataSource die DAL verwenden soll, die optimistische Parallelität unterstützt, konfigurieren Sie sie für die Verwendung des ProductsOptimisticConcurrencyBLL Objekts.

Das ObjectDataSource soll das ProductsOptimisticConcurrencyBLL-Objekt verwenden

Abbildung 13: Verwenden des ProductsOptimisticConcurrencyBLL Objekts "ObjectDataSource" (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Wählen Sie im Assistenten die Methoden GetProducts, UpdateProduct und DeleteProduct aus Drop-down-Listen aus. Für die UpdateProduct-Methode verwenden Sie die Überladungsmethode, die alle Datenfelder des Produkts akzeptiert.

Konfigurieren der Eigenschaften des ObjectDataSource-Steuerelements

Nach Abschluss des Assistenten sollte das deklarative Markup von ObjectDataSource wie folgt aussehen:

<asp:ObjectDataSource ID="ProductsOptimisticConcurrencyDataSource" runat="server"
    DeleteMethod="DeleteProduct" OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProducts" TypeName="ProductsOptimisticConcurrencyBLL"
    UpdateMethod="UpdateProduct">
    <DeleteParameters>
        <asp:Parameter Name="original_productID" Type="Int32" />
        <asp:Parameter Name="original_productName" Type="String" />
        <asp:Parameter Name="original_supplierID" Type="Int32" />
        <asp:Parameter Name="original_categoryID" Type="Int32" />
        <asp:Parameter Name="original_quantityPerUnit" Type="String" />
        <asp:Parameter Name="original_unitPrice" Type="Decimal" />
        <asp:Parameter Name="original_unitsInStock" Type="Int16" />
        <asp:Parameter Name="original_unitsOnOrder" Type="Int16" />
        <asp:Parameter Name="original_reorderLevel" Type="Int16" />
        <asp:Parameter Name="original_discontinued" Type="Boolean" />
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="supplierID" Type="Int32" />
        <asp:Parameter Name="categoryID" Type="Int32" />
        <asp:Parameter Name="quantityPerUnit" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="unitsInStock" Type="Int16" />
        <asp:Parameter Name="unitsOnOrder" Type="Int16" />
        <asp:Parameter Name="reorderLevel" Type="Int16" />
        <asp:Parameter Name="discontinued" Type="Boolean" />
        <asp:Parameter Name="productID" Type="Int32" />
        <asp:Parameter Name="original_productName" Type="String" />
        <asp:Parameter Name="original_supplierID" Type="Int32" />
        <asp:Parameter Name="original_categoryID" Type="Int32" />
        <asp:Parameter Name="original_quantityPerUnit" Type="String" />
        <asp:Parameter Name="original_unitPrice" Type="Decimal" />
        <asp:Parameter Name="original_unitsInStock" Type="Int16" />
        <asp:Parameter Name="original_unitsOnOrder" Type="Int16" />
        <asp:Parameter Name="original_reorderLevel" Type="Int16" />
        <asp:Parameter Name="original_discontinued" Type="Boolean" />
        <asp:Parameter Name="original_productID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

Wie Sie sehen können, enthält die DeleteParameters Auflistung eine Parameter Instanz für jeden der zehn Eingabeparameter in der Methode der ProductsOptimisticConcurrencyBLL Klasse DeleteProduct . Ebenso enthält die UpdateParameters Auflistung eine Parameter Instanz für jeden eingabeparameter in UpdateProduct.

Für diese früheren Tutorials, bei denen Daten geändert wurden, entfernen wir die Eigenschaft der OldValuesParameterFormatString ObjectDataSource hier, da diese Eigenschaft angibt, dass die BLL-Methode erwartet, dass sowohl die alten (ursprünglichen) als auch die neuen Werte übergeben werden. Darüber hinaus gibt dieser Eigenschaftswert die Eingabeparameternamen für die ursprünglichen Werte an. Da wir die ursprünglichen Werte an die BLL übergeben, entfernen Sie diese Eigenschaft nicht .

Hinweis

Der Wert der OldValuesParameterFormatString Eigenschaft muss den Eingabeparameternamen in der BLL zugeordnet werden, die die ursprünglichen Werte erwarten. Da wir diese Parameter original_productNamebenannt haben, original_supplierIDusw. können Sie den OldValuesParameterFormatString Eigenschaftswert als original_{0}belassen. Wenn die Eingabeparameter der BLL-Methoden jedoch Namen wie old_productName, old_supplierID usw. hatten, müssen Sie die OldValuesParameterFormatString-Eigenschaft auf old_{0} aktualisieren.

Es gibt eine endgültige Eigenschaftseinstellung, die vorgenommen werden muss, damit ObjectDataSource die ursprünglichen Werte ordnungsgemäß an die BLL-Methoden übergibt. Die ObjectDataSource verfügt über eine ConflictDetection-Eigenschaft , die einem von zwei Werten zugewiesen werden kann:

  • OverwriteChanges - der Standardwert; sendet die ursprünglichen Werte nicht an die ursprünglichen Eingabeparameter der BLL-Methoden.
  • CompareAllValues - sendet die ursprünglichen Werte an die BLL-Methoden; Wählen Sie diese Option aus, wenn Sie optimistische Parallelität verwenden.

Nehmen Sie sich einen Moment Zeit, um die ConflictDetection-Eigenschaft auf CompareAllValues zu setzen.

Konfigurieren der Eigenschaften und Felder von GridView

Wenn die Eigenschaften von ObjectDataSource ordnungsgemäß konfiguriert sind, wenden wir uns der Einrichtung des GridView zu. Zuerst, da wir möchten, dass das GridView das Bearbeiten und Löschen unterstützt, klicken Sie auf die Kontrollkästchen "Bearbeiten aktivieren" und "Löschen aktivieren" im Smarttag von GridView. Hierdurch wird ein CommandField hinzugefügt, bei dem sowohl ShowEditButton als auch ShowDeleteButton auf true gesetzt sind.

Wenn sie an objectDataSource ProductsOptimisticConcurrencyDataSource gebunden ist, enthält die GridView ein Feld für jedes Der Datenfelder des Produkts. Während eine solche GridView bearbeitet werden kann, ist die Benutzeroberfläche alles andere als akzeptabel. Die CategoryID und SupplierID BoundFields werden als TextBoxes gerendert, sodass der Benutzer die entsprechende Kategorie und den entsprechenden Lieferanten als ID-Nummern eingeben muss. Es gibt keine Formatierung für die numerischen Felder und keine Validierungskontrollen, um sicherzustellen, dass der Name des Produkts angegeben wurde und dass der Einzelpreis, die Einheiten auf Lager, die Bestelleinheiten und der Meldebestand korrekte numerische Werte sind und größer oder gleich Null sind.

Wie wir in den Lernprogrammen zum Bearbeiten und Einfügen von Schnittstellen zum Bearbeiten und Einfügen von Validierungssteuerelementen und zum Anpassen der Datenänderungsschnittstelle erläutert haben, kann die Benutzeroberfläche angepasst werden, indem die BoundFields durch TemplateFields ersetzt werden. Ich habe dieses GridView und seine Bearbeitungsschnittstelle auf folgende Weise geändert:

  • Die ProductID, SupplierName und CategoryName BoundFields wurden entfernt.
  • Konvertierte das ProductName BoundField in ein TemplateField und fügte ein RequiredFieldValidation-Steuerelement hinzu.
  • Konvertierte die CategoryID und SupplierID BoundFields in TemplateFields und passte die Bearbeitungsschnittstelle an die Verwendung von DropDownLists anstelle von TextBoxes an. In diesen TemplateFields ItemTemplates werden die Datenfelder CategoryName und SupplierName angezeigt.
  • Die UnitPrice, UnitsInStock, UnitsOnOrder und ReorderLevel BoundFields wurden in TemplateFields konvertiert, und CompareValidator-Steuerelemente wurden hinzugefügt.

Da wir bereits untersucht haben, wie diese Aufgaben in früheren Lernprogrammen ausgeführt werden können, listee ich hier einfach die endgültige deklarative Syntax auf und belassen die Implementierung als Praxis.

<asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ProductsOptimisticConcurrencyDataSource"
    OnRowUpdated="ProductsGrid_RowUpdated">
    <Columns>
        <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
        <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
            <EditItemTemplate>
                <asp:TextBox ID="EditProductName" runat="server"
                    Text='<%# Bind("ProductName") %>'></asp:TextBox>
                <asp:RequiredFieldValidator ID="RequiredFieldValidator1"
                    ControlToValidate="EditProductName"
                    ErrorMessage="You must enter a product name."
                    runat="server">*</asp:RequiredFieldValidator>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server"
                    Text='<%# Bind("ProductName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
            <EditItemTemplate>
                <asp:DropDownList ID="EditCategoryID" runat="server"
                    DataSourceID="CategoriesDataSource" AppendDataBoundItems="true"
                    DataTextField="CategoryName" DataValueField="CategoryID"
                    SelectedValue='<%# Bind("CategoryID") %>'>
                    <asp:ListItem Value=">(None)</asp:ListItem>
                </asp:DropDownList><asp:ObjectDataSource ID="CategoriesDataSource"
                    runat="server" OldValuesParameterFormatString="original_{0}"
                    SelectMethod="GetCategories" TypeName="CategoriesBLL">
                </asp:ObjectDataSource>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label2" runat="server"
                    Text='<%# Bind("CategoryName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
            <EditItemTemplate>
                <asp:DropDownList ID="EditSuppliersID" runat="server"
                    DataSourceID="SuppliersDataSource" AppendDataBoundItems="true"
                    DataTextField="CompanyName" DataValueField="SupplierID"
                    SelectedValue='<%# Bind("SupplierID") %>'>
                    <asp:ListItem Value=">(None)</asp:ListItem>
                </asp:DropDownList><asp:ObjectDataSource ID="SuppliersDataSource"
                    runat="server" OldValuesParameterFormatString="original_{0}"
                    SelectMethod="GetSuppliers" TypeName="SuppliersBLL">
                </asp:ObjectDataSource>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label3" runat="server"
                    Text='<%# Bind("SupplierName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
            SortExpression="QuantityPerUnit" />
        <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
            <EditItemTemplate>
                <asp:TextBox ID="EditUnitPrice" runat="server"
                    Text='<%# Bind("UnitPrice", "{0:N2}") %>' Columns="8" />
                <asp:CompareValidator ID="CompareValidator1" runat="server"
                    ControlToValidate="EditUnitPrice"
                    ErrorMessage="Unit price must be a valid currency value without the
                    currency symbol and must have a value greater than or equal to zero."
                    Operator="GreaterThanEqual" Type="Currency"
                    ValueToCompare="0">*</asp:CompareValidator>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label4" runat="server"
                    Text='<%# Bind("UnitPrice", "{0:C}") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Units In Stock" SortExpression="UnitsInStock">
            <EditItemTemplate>
                <asp:TextBox ID="EditUnitsInStock" runat="server"
                    Text='<%# Bind("UnitsInStock") %>' Columns="6"></asp:TextBox>
                <asp:CompareValidator ID="CompareValidator2" runat="server"
                    ControlToValidate="EditUnitsInStock"
                    ErrorMessage="Units in stock must be a valid number
                        greater than or equal to zero."
                    Operator="GreaterThanEqual" Type="Integer"
                    ValueToCompare="0">*</asp:CompareValidator>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label5" runat="server"
                    Text='<%# Bind("UnitsInStock", "{0:N0}") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Units On Order" SortExpression="UnitsOnOrder">
            <EditItemTemplate>
                <asp:TextBox ID="EditUnitsOnOrder" runat="server"
                    Text='<%# Bind("UnitsOnOrder") %>' Columns="6"></asp:TextBox>
                <asp:CompareValidator ID="CompareValidator3" runat="server"
                    ControlToValidate="EditUnitsOnOrder"
                    ErrorMessage="Units on order must be a valid numeric value
                        greater than or equal to zero."
                    Operator="GreaterThanEqual" Type="Integer"
                    ValueToCompare="0">*</asp:CompareValidator>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label6" runat="server"
                    Text='<%# Bind("UnitsOnOrder", "{0:N0}") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Reorder Level" SortExpression="ReorderLevel">
            <EditItemTemplate>
                <asp:TextBox ID="EditReorderLevel" runat="server"
                    Text='<%# Bind("ReorderLevel") %>' Columns="6"></asp:TextBox>
                <asp:CompareValidator ID="CompareValidator4" runat="server"
                    ControlToValidate="EditReorderLevel"
                    ErrorMessage="Reorder level must be a valid numeric value
                        greater than or equal to zero."
                    Operator="GreaterThanEqual" Type="Integer"
                    ValueToCompare="0">*</asp:CompareValidator>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label7" runat="server"
                    Text='<%# Bind("ReorderLevel", "{0:N0}") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>

Wir sind sehr nah an einem vollständig funktionierenden Beispiel. Es gibt jedoch einige Feinheiten, die sich einschleichen und uns Probleme verursachen. Darüber hinaus benötigen wir weiterhin eine Schnittstelle, die den Benutzer benachrichtigt, wenn eine Parallelitätsverletzung aufgetreten ist.

Hinweis

Damit ein Datenwebsteuerelement die ursprünglichen Werte ordnungsgemäß an die ObjectDataSource (die dann an die BLL übergeben wird) übergeben kann, ist es wichtig, dass die Eigenschaft von GridView auf EnableViewState (Standardeinstellung) festgelegt ist. Wenn Sie den Ansichtszustand deaktivieren, gehen die ursprünglichen Werte beim Postback verloren.

Übergeben der korrekten Originalwerte an die ObjectDataSource

Es gibt ein paar Probleme mit der Konfiguration von GridView. Wenn die ConflictDetection-Eigenschaft der ObjectDataSource auf CompareAllValues festgelegt ist (wie bei uns), versucht die ObjectDataSource, wenn die Update()- oder Delete()-Methoden von der GridView (oder DetailsView oder FormView) aufgerufen werden, die ursprünglichen Werte der GridView in die entsprechenden Parameter-Instanzen zu kopieren. Eine grafische Darstellung dieses Prozesses finden Sie in Abbildung 2.

Insbesondere werden den ursprünglichen Werten der GridView die Werte in den bidirektionalen Datenbindungsanweisungen bei jeder Datenbindung an die GridView zugewiesen. Daher ist es wichtig, dass alle erforderlichen Originalwerte über bidirektionale Datenbindung erfasst und in einem konvertierbaren Format bereitgestellt werden.

Um zu sehen, warum dies wichtig ist, nehmen Sie sich einen Moment Zeit, um unsere Seite in einem Browser zu besuchen. Wie erwartet, listet gridView jedes Produkt mit einer Schaltfläche "Bearbeiten" und "Löschen" in der spalte ganz links auf.

Die Produkte werden in einer GridView aufgeführt

Abbildung 14: Die Produkte werden in einer GridView aufgelistet (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Wenn Sie auf die Schaltfläche "Löschen" für ein beliebiges Produkt klicken, wird ein FormatException Fehler ausgelöst.

Der Versuch, ein beliebiges Produkt zu löschen, führt zu einer FormatException

Abbildung 15: Versuch, ein beliebiges Produkt zu löschen, führt zu einem FormatException (Klicken, um das Bild in voller Größe anzuzeigen)

Die FormatException wird ausgelöst, wenn die ObjectDataSource versucht, den ursprünglichen UnitPrice-Wert zu lesen. Da das ItemTemplate im UnitPrice als Währung (<%# Bind("UnitPrice", "{0:C}") %>) formatiert ist, enthält es ein Währungssymbol, wie $19,95. Dies FormatException tritt auf, wenn die ObjectDataSource versucht, diese Zeichenfolge in eine decimal. Um dieses Problem zu umgehen, haben wir eine Reihe von Optionen:

  • Entfernen Sie die Währungsformatierung aus dem ItemTemplate. Das heißt, anstatt <%# Bind("UnitPrice", "{0:C}") %> zu verwenden, einfach <%# Bind("UnitPrice") %> verwenden. Der Nachteil ist, dass der Preis nicht mehr formatiert ist.
  • Zeigen Sie UnitPrice als Währung formatiert in der ItemTemplate an, verwenden Sie jedoch das Schlüsselwort Eval, um dies zu erreichen. Vergessen Sie nicht, dass Eval eine einseitige Datenbindung ausführt. Wir müssen den UnitPrice Wert für die ursprünglichen Werte weiterhin angeben, daher benötigen wir weiterhin eine bidirektionale Datenbindungsanweisung, aber dies kann in einem Label Web-Steuerelement platziert werden, dessen ItemTemplate Eigenschaft auf Visible gesetzt ist. Wir könnten das folgende Markup in der ItemTemplate verwenden:
<ItemTemplate>
    <asp:Label ID="DummyUnitPrice" runat="server"
        Text='<%# Bind("UnitPrice") %>' Visible="false"></asp:Label>
    <asp:Label ID="Label4" runat="server"
        Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:Label>
</ItemTemplate>
  • Entfernen Sie die Währungsformatierung aus dem ItemTemplate, indem Sie <%# Bind("UnitPrice") %> verwenden. Im Ereignishandler von RowDataBound der GridView greifen Sie programmgesteuert auf das Websteuerelement des Labels zu, innerhalb dessen der UnitPrice Wert angezeigt wird, und setzen dessen Text Eigenschaft auf die formatierte Version.
  • Lassen Sie die UnitPrice als Währung formatiert. Ersetzen Sie im Ereignishandler des GridView-RowDeleting den vorhandenen ursprünglichen Wert UnitPrice ($19,95) durch einen tatsächlichen Dezimalwert, indem Sie Decimal.Parse verwenden. Wir haben gesehen, wie man im RowUpdating Ereignishandler im Tutorial 'Umgang mit BLL- und DAL-Level-Ausnahmen auf einer ASP.NET-Seite' etwas Ähnliches erledigen kann.

Für mein Beispiel habe ich mich entschieden, den zweiten Ansatz zu verfolgen und ein ausgeblendetes Websteuerungslabel hinzuzufügen, dessen Text Eigenschaft bidirektional an den unformatierten UnitPrice Wert gebunden ist.

Nachdem Sie dieses Problem behoben haben, versuchen Sie erneut, auf die Schaltfläche "Löschen" für ein beliebiges Produkt zu klicken. Dieses Mal erhalten Sie eine InvalidOperationException , wenn die ObjectDataSource versucht, die BLL-Methode UpdateProduct aufzurufen.

Die ObjectDataSource Kann eine Methode mit den Eingabeparametern, die er senden möchte, nicht finden

Abbildung 16: Die ObjectDataSource Kann eine Methode mit den Eingabeparametern nicht finden, die gesendet werden soll (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Wenn Sie sich die Meldung der Ausnahme ansehen, wird klar, dass die ObjectDataSource eine BLL-Methode DeleteProduct aufrufen möchte, die Eingabeparameter original_CategoryName und original_SupplierName enthält. Dies liegt daran, dass die ItemTemplate-Elemente für die CategoryID und SupplierID TemplateFields derzeit bidirektionale Bind-Anweisungen mit den CategoryName und SupplierName Datenfeldern enthalten. Stattdessen müssen wir Bind Anweisungen zusammen mit den Datenfeldern CategoryID und SupplierID einfügen. Ersetzen Sie dazu die vorhandenen Bind-Anweisungen durch Eval-Anweisungen, und fügen Sie dann versteckte Bezeichnungssteuerelemente hinzu, deren Text-Eigenschaften mit den CategoryID- und SupplierID-Datenfeldern durch bidirektionale Datenbindung verbunden sind, wie unten dargestellt.

<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
    <EditItemTemplate>
        ...
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Label ID="DummyCategoryID" runat="server"
            Text='<%# Bind("CategoryID") %>' Visible="False"></asp:Label>
        <asp:Label ID="Label2" runat="server"
            Text='<%# Eval("CategoryName") %>'></asp:Label>
    </ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
    <EditItemTemplate>
        ...
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Label ID="DummySupplierID" runat="server"
            Text='<%# Bind("SupplierID") %>' Visible="False"></asp:Label>
        <asp:Label ID="Label3" runat="server"
            Text='<%# Eval("SupplierName") %>'></asp:Label>
    </ItemTemplate>
</asp:TemplateField>

Mit diesen Änderungen können wir jetzt Produktinformationen erfolgreich löschen und bearbeiten! In Schritt 5 sehen wir uns an, wie überprüft wird, ob Parallelitätsverletzungen erkannt werden. Es dauert jedoch einige Minuten, bis Sie versuchen, einige Datensätze zu aktualisieren und zu löschen, um sicherzustellen, dass das Aktualisieren und Löschen für einen einzelnen Benutzer wie erwartet funktioniert.

Schritt 5: Testen der optimistischen Parallelitätsunterstützung

Um zu überprüfen, ob Parallelitätsverletzungen erkannt werden (und nicht dazu führen, dass Daten blind überschrieben werden), müssen wir zwei Browserfenster auf dieser Seite öffnen. Klicken Sie in beiden Browserinstanzen auf die Schaltfläche "Bearbeiten" für "Chai". Ändern Sie dann in nur einem der Browser den Namen in "Chai Tea", und klicken Sie auf "Aktualisieren". Das Update sollte erfolgreich sein und das GridView-Element in den Vorbearbeitungszustand mit "Chai Tea" als neuen Produktnamen zurückgeben.

In der anderen Browserfensterinstanz wird im TextBox-Produktnamen jedoch weiterhin "Chai" angezeigt. Aktualisieren Sie in diesem zweiten Browserfenster die UnitPrice Datei auf 25.00. Ohne optimistische Parallelitätsunterstützung würde durch Klicken auf "Update" in der zweiten Browserinstanz der Produktname wieder in "Chai" geändert, wodurch die von der ersten Browserinstanz vorgenommenen Änderungen überschrieben werden. Bei optimistischer Parallelität führt das Klicken auf die Schaltfläche "Aktualisieren" in der zweiten Browserinstanz zu einer DBConcurrencyException.

Wenn eine Parallelitätsverletzung erkannt wird, wird eine DBConcurrencyException ausgelöst.

Abbildung 17: Wenn eine Parallelitätsverletzung erkannt wird, wird ein DBConcurrencyException Fehler ausgelöst (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Dies DBConcurrencyException wird nur ausgelöst, wenn das Batchaktualisierungsmuster des DAL verwendet wird. Das direkte DB-Muster löst keine Ausnahme aus, sondern gibt lediglich an, dass keine Zeilen betroffen waren. Um dies zu veranschaulichen, geben Sie die GridView der beiden Browserinstanzen in ihren Zustand vor der Bearbeitung zurück. Klicken Sie als Nächstes in der ersten Browserinstanz auf die Schaltfläche "Bearbeiten", und ändern Sie den Produktnamen von "Chai Tea" wieder in "Chai", und klicken Sie auf "Aktualisieren". Klicken Sie im zweiten Browserfenster auf die Schaltfläche "Löschen" für "Chai".

Beim Klicken auf "Löschen" lädt die Seite neu, das GridView ruft die Methode Delete() der ObjectDataSource auf, und die ObjectDataSource ruft die Methode ProductsOptimisticConcurrencyBLL der Klasse DeleteProduct auf und übergibt dabei die ursprünglichen Werte. Der ursprüngliche ProductName Wert für die zweite Browserinstanz lautet "Chai Tea", der nicht mit dem aktuellen ProductName Wert in der Datenbank übereinstimmt. Daher wirkt sich die DELETE an die Datenbank ausgegebene Anweisung auf null Zeilen aus, da keine Datensätze in der Datenbank vorhanden sind, die die WHERE Klausel erfüllt. Die DeleteProduct Methode gibt zurück false , und die Daten von ObjectDataSource werden an das GridView-Objekt zurückgegeben.

Aus Sicht des Endbenutzers hat das Klicken auf die Schaltfläche "Löschen" für "Chai Tea" im zweiten Browserfenster dazu geführt, dass der Bildschirm blinkt und das Produkt nach der Rückkehr noch vorhanden ist, obwohl es jetzt als "Chai" aufgeführt ist (die Änderung des Produktnamens, die von der ersten Browserinstanz vorgenommen wurde). Wenn der Benutzer erneut auf die Schaltfläche "Löschen" klickt, wird der Löschvorgang erfolgreich sein, da der ursprüngliche ProductName Wert von GridView ("Chai") nun mit dem Wert in der Datenbank übereinstimmt.

In beiden Fällen ist die Benutzererfahrung nicht ideal. Wir möchten dem Benutzer bei der Verwendung des Batch-Update-Musters eindeutig nicht die Details der DBConcurrencyException Ausnahme anzeigen. Und das Verhalten bei der Verwendung des direkten DB-Musters ist etwas verwirrend, da der Benutzerbefehl fehlgeschlagen ist, aber es gab keinen genauen Hinweis darauf, warum.

Um diese beiden Probleme zu beheben, können wir Bezeichnungswebsteuerelemente auf der Seite erstellen, die eine Erläuterung enthalten, warum ein Update oder Löschfehler aufgetreten ist. Für das Batch-Aktualisierungsmuster können wir feststellen, ob im Ereignishandler auf der Postebene des GridView eine DBConcurrencyException Ausnahme aufgetreten ist, wobei das Warnetikett bei Bedarf angezeigt wird. Für die direkte DB-Methode können wir den Rückgabewert der BLL-Methode (d. h true . wenn eine Zeile betroffen ist, false andernfalls) untersuchen und bei Bedarf eine Informationsmeldung anzeigen.

Schritt 6: Hinzufügen von Informationsmeldungen und Anzeigen dieser Meldungen bei einer Parallelitätsverletzung

Wenn eine Parallelitätsverletzung auftritt, hängt das angezeigte Verhalten davon ab, ob die Batchaktualisierung des DAL oder das direkte DB-Muster verwendet wurde. Unser Lernprogramm verwendet beide Muster, wobei das Batchaktualisierungsmuster für die Aktualisierung und das direkte DB-Muster zum Löschen verwendet wird. Um zu beginnen, fügen wir unserer Seite zwei Bezeichnungswebsteuerelemente hinzu, die erklären, dass beim Versuch, Daten zu löschen oder zu aktualisieren, eine Parallelitätsverletzung aufgetreten ist. Legen Sie die Visible und EnableViewState Eigenschaften des Bezeichnungssteuerelements auf false fest; dies führt dazu, dass sie bei jedem Seitenbesuch ausgeblendet werden, außer bei den bestimmten Seitenbesuchen, bei denen ihre Visible Eigenschaft programmgesteuert auf true festgelegt ist.

<asp:Label ID="DeleteConflictMessage" runat="server" Visible="False"
    EnableViewState="False" CssClass="Warning"
    Text="The record you attempted to delete has been modified by another user
           since you last visited this page. Your delete was cancelled to allow
           you to review the other user's changes and determine if you want to
           continue deleting this record." />
<asp:Label ID="UpdateConflictMessage" runat="server" Visible="False"
    EnableViewState="False" CssClass="Warning"
    Text="The record you attempted to update has been modified by another user
           since you started the update process. Your changes have been replaced
           with the current values. Please review the existing values and make
           any needed changes." />

Zusätzlich zum Festlegen ihrer Visible-, EnabledViewState- und Text-Eigenschaften habe ich auch die CssClass-Eigenschaft auf Warning gesetzt, was bewirkt, dass die Labels in einer großen, roten, kursiven und fett formatierten Schriftart angezeigt werden. Diese CSS-Klasse Warning wurde im Lernprogramm "Untersuchen der Ereignisse im Zusammenhang mit Einfügen, Aktualisieren und Löschen" definiert und zu Styles.css hinzugefügt.

Nach dem Hinzufügen dieser Bezeichnungen sollte der Designer in Visual Studio ähnlich aussehen wie in Abbildung 18.

Der Seite wurden zwei Label-Steuerelemente hinzugefügt.

Abbildung 18: Zwei Bezeichnungssteuerelemente wurden der Seite hinzugefügt (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Mit diesen Label-Websteuerelementen sind wir bereit zu untersuchen, wie man feststellt, wann eine Parallelitätsverletzung aufgetreten ist, zu welchem Zeitpunkt die Eigenschaft des entsprechenden Labels auf Visible gesetzt werden kann, um die Informationsmeldung true anzuzeigen.

Behandeln von Parallelitätsverletzungen beim Aktualisieren

Sehen wir uns zunächst an, wie Parallelitätsverletzungen beim Verwenden des Batchaktualisierungsmusters behandelt werden. Da solche Verstöße mit dem Batchaktualisierungsmuster zu einer DBConcurrencyException Ausnahme führen, müssen wir der seite ASP.NET Code hinzufügen, um zu bestimmen, ob während des Aktualisierungsprozesses eine DBConcurrencyException Ausnahme aufgetreten ist. Wenn ja, sollte dem Benutzer eine Meldung angezeigt werden, in der erläutert wird, dass seine Änderungen nicht gespeichert wurden, da ein anderer Benutzer die gleichen Daten zwischen dem Bearbeiten des Datensatzes und dem Klicken auf die Schaltfläche "Aktualisieren" geändert hatte.

Wie wir im ASP.NET-Seite-Tutorial zur Behandlung von BLL- und DAL-Level-Ausnahmen gesehen haben, können solche Ausnahmen in den Ereignishandlern von Daten-Websteuerelementen erkannt und unterdrückt werden. Daher müssen wir einen Ereignishandler für das GridView-Ereignis RowUpdated erstellen, das überprüft, ob eine DBConcurrencyException Ausnahme ausgelöst wurde. Dieser Ereignishandler wird einen Verweis auf jede Ausnahme übergeben, die während des Aktualisierungsvorgangs ausgelöst wurde, wie im folgenden Ereignishandlercode gezeigt:

Protected Sub ProductsGrid_RowUpdated _
        (ByVal sender As Object, ByVal e As GridViewUpdatedEventArgs) _
        Handles ProductsGrid.RowUpdated
    If e.Exception IsNot Nothing AndAlso e.Exception.InnerException IsNot Nothing Then
        If TypeOf e.Exception.InnerException Is System.Data.DBConcurrencyException Then
            ' Display the warning message and note that the exception has
            ' been handled...
            UpdateConflictMessage.Visible = True
            e.ExceptionHandled = True
        End If
    End If
End Sub

Im Falle einer DBConcurrencyException Ausnahme zeigt dieser Ereignishandler das UpdateConflictMessage Label-Steuerelement an und signalisiert, dass die Ausnahme gehandhabt wurde. Wenn bei diesem Code beim Aktualisieren eines Datensatzes eine Parallelitätsverletzung auftritt, gehen die Änderungen des Benutzers verloren, da sie gleichzeitig die Änderungen eines anderen Benutzers überschrieben hätten. Insbesondere wird gridView an den Vorbearbeitungszustand zurückgegeben und an die aktuellen Datenbankdaten gebunden. Dadurch wird die GridView-Zeile mit den Änderungen des anderen Benutzers aktualisiert, die zuvor nicht sichtbar waren. Darüber hinaus wird das UpdateConflictMessage Bezeichnungssteuerelement dem Benutzer erklären, was gerade passiert ist. Diese Abfolge von Ereignissen ist in Abbildung 19 detailliert dargestellt.

Aktualisierungen eines Benutzers gehen angesichts einer Parallelitätsverletzung verloren

Abbildung 19: Die Aktualisierungen eines Benutzers gehen bei einer Parallelitätsverletzung verloren (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Hinweis

Alternativ können wir das GridView-Objekt nicht an den Vorbearbeitungszustand zurückgeben, sondern das GridView in seinem Bearbeitungszustand belassen, indem die KeepInEditMode Eigenschaft des übergebenen GridViewUpdatedEventArgs Objekts auf "true" festgelegt wird. Wenn Sie diesen Ansatz jedoch anwenden, müssen Sie die Daten mit gridView (durch Aufrufen der DataBind() Methode) neu verknüpfen, damit die Werte des anderen Benutzers in die Bearbeitungsoberfläche geladen werden. Der Code, der mit diesem Lernprogramm zum Download verfügbar ist, hat diese beiden Codezeilen im RowUpdated-Ereignishandler auskommentiert. Heben Sie einfach die Kommentare bei diesen Codezeilen auf, damit die GridView nach einem Verstoß gegen die Parallelität weiterhin im Bearbeitungsmodus bleibt.

Reagieren auf Parallelitätsverletzungen beim Löschen

Mit dem DB-Direktmuster wird bei einer Parallelitätsverletzung keine Ausnahme ausgelöst. Stattdessen betrifft die Datenbankanweisung einfach keine Datensätze, da die WHERE-Klausel mit keinem Datensatz übereinstimmt. Alle in der BLL erstellten Datenänderungsmethoden wurden so entworfen, dass sie einen booleschen Wert zurückgeben, der angibt, ob sie genau einen Datensatz betroffen haben. Um zu ermitteln, ob beim Löschen eines Datensatzes eine Parallelitätsverletzung aufgetreten ist, können wir den Rückgabewert der BLL-Methode DeleteProduct untersuchen.

Der Rückgabewert einer BLL-Methode kann in den Ereignishandlern der Post-Ebene von ObjectDataSource über die ReturnValue Eigenschaft des ObjectDataSourceStatusEventArgs Objekts untersucht werden, das an den Ereignishandler übergeben wird. Da wir an der Bestimmung des Rückgabewerts DeleteProduct aus der Deleted Methode interessiert sind, müssen wir einen Ereignishandler für das ObjectDataSource-Ereignis erstellen. Die ReturnValue Eigenschaft ist vom Typ object und kann sein null , wenn eine Ausnahme ausgelöst wurde und die Methode unterbrochen wurde, bevor ein Wert zurückgegeben werden konnte. Daher sollten wir zuerst sicherstellen, dass die ReturnValue Eigenschaft nicht null und ein boolescher Wert ist. Wenn diese Überprüfung erfolgreich ist, zeigen wir das DeleteConflictMessage Label-Steuerelement an, wenn ReturnValuefalse ist. Dies kann mithilfe des folgenden Codes erreicht werden:

Protected Sub ProductsOptimisticConcurrencyDataSource_Deleted _
        (ByVal sender As Object, ByVal e As ObjectDataSourceStatusEventArgs) _
        Handles ProductsOptimisticConcurrencyDataSource.Deleted
    If e.ReturnValue IsNot Nothing AndAlso TypeOf e.ReturnValue Is Boolean Then
        Dim deleteReturnValue As Boolean = CType(e.ReturnValue, Boolean)
        If deleteReturnValue = False Then
            ' No row was deleted, display the warning message
            DeleteConflictMessage.Visible = True
        End If
    End If
End Sub

Angesichts einer Verletzung der Gleichzeitigkeit wird die Löschanforderung des Benutzers abgebrochen. Das GridView-Objekt wird aktualisiert und zeigt die Änderungen an, die für diesen Datensatz zwischen dem Laden der Seite und dem Klicken auf die Schaltfläche "Löschen" aufgetreten sind. Wenn eine solche Verletzung eintritt, wird das DeleteConflictMessage Label angezeigt, um zu erläutern, was gerade passiert ist (siehe Abbildung 20).

Die Löschung eines Benutzers wird bei einer Parallelitätsverletzung abgebrochen.

Abbildung 20: Die Löschung eines Benutzers wird angesichts einer Parallelitätsverletzung abgebrochen (Klicken Sie hier, um das Bild mit voller Größe anzuzeigen)

Zusammenfassung

Es gibt in jeder Anwendung Möglichkeiten für Parallelitätsverletzungen, die es mehreren gleichzeitigen Benutzern ermöglichen, Daten zu aktualisieren oder zu löschen. Wenn solche Verstöße nicht berücksichtigt werden, wenn zwei Benutzer gleichzeitig die gleichen Daten aktualisieren, die in der letzten Schreibweise "wins" erhalten, wird die Änderung des anderen Benutzers überschrieben. Alternativ können Entwickler entweder optimistische oder pessimistische Parallelitätssteuerung implementieren. Optimistische Parallelitätskontrolle geht davon aus, dass Parallelitätsverletzungen selten auftreten und verbietet einfach einen Aktualisierungs- oder Löschbefehl, der eine Parallelitätsverletzung darstellen würde. Pessimistische Parallelitätssteuerung geht davon aus, dass Parallelitätsverletzungen häufig sind und das einfache Ablehnen des Befehls zum Aktualisieren oder Löschen eines Benutzers nicht akzeptabel ist. Bei pessimistischem Parallelitätssteuerelement schließt das Aktualisieren eines Datensatzes das Sperren des Datensatzes ein, wodurch verhindert wird, dass andere Benutzer den Datensatz ändern oder löschen, während er gesperrt ist.

Das typisierte DataSet in .NET bietet Funktionen zur Unterstützung optimistischer Parallelitätskontrolle. Insbesondere umfassen die an die Datenbank übermittelten Anweisungen UPDATE und DELETE alle Spalten der Tabelle. Dies stellt sicher, dass ein Update oder eine Löschung nur dann erfolgt, wenn die aktuellen Daten des Datensatzes mit den ursprünglichen Daten des Benutzers beim Ausführen des Updates oder der Löschung übereinstimmen. Nachdem die DAL für die Unterstützung optimistischer Parallelität konfiguriert wurde, müssen die BLL-Methoden aktualisiert werden. Darüber hinaus muss die ASP.NET Seite, die in die BLL aufruft, so konfiguriert werden, dass die ObjectDataSource die ursprünglichen Werte aus dem Datenwebsteuerelement abruft und sie an die BLL übergibt.

Wie wir in diesem Tutorial gesehen haben, umfasst die Implementierung der optimistischen Parallelitätskontrolle in einer ASP.NET-Webanwendung das Aktualisieren des DAL und BLL und das Hinzufügen von Unterstützung auf der ASP.NET-Seite. Ob diese hinzugefügte Arbeit eine kluge Investition ihrer Zeit und Ihres Aufwands ist, hängt von Ihrer Anwendung ab. Wenn Sie selten gleichzeitige Benutzer haben, die Daten aktualisieren, oder die Daten, die sie aktualisieren, unterscheiden sich voneinander, dann ist die Parallelitätssteuerung kein Schlüsselproblem. Wenn Ihre Website jedoch regelmäßig von mehreren Benutzern genutzt wird, die mit denselben Daten arbeiten, kann die Gleichzeitigkeitsteuerung dazu beitragen, zu verhindern, dass Aktualisierungen oder Löschungen eines Benutzers unbeabsichtigt die Änderungen eines anderen überschreiben.

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.