Share via


Implementieren von optimistischer Parallelität (C#)

von Scott Mitchell

PDF herunterladen

Bei einer Webanwendung, die es mehreren Benutzern ermöglicht, Daten zu bearbeiten, besteht das Risiko, dass zwei Benutzer dieselben Daten gleichzeitig bearbeiten. In diesem Tutorial implementieren wir eine optimistische Parallelitätssteuerung, um dieses Risiko zu bewältigen.

Einführung

Für Webanwendungen, die es Benutzern nur ermöglichen, Daten anzuzeigen, oder für Webanwendungen, die nur einen einzelnen Benutzer enthalten, der Daten ändern kann, besteht keine Gefahr, dass zwei gleichzeitige Benutzer versehentlich die Änderungen des anderen überschreiben. Bei Webanwendungen, die es mehreren Benutzern ermöglichen, Daten zu aktualisieren oder zu löschen, besteht jedoch das Potenzial, dass die Änderungen eines Benutzers mit der eines anderen gleichzeitigen Benutzers kollidieren. Wenn zwei Benutzer gleichzeitig einen einzelnen Datensatz bearbeiten, wird ohne Parallelitätsrichtlinie der Benutzer, der seine Änderungen zuletzt committet, die vom ersten vorgenommenen Änderungen überschrieben.

Stellen Sie sich beispielsweise vor, dass zwei Benutzer, Jisun und Sam, beide eine Seite in unserer Anwendung besuchen, die es Besuchern ermöglicht, die Produkte über ein GridView-Steuerelement zu aktualisieren und zu löschen. Beide klicken ungefähr zur gleichen Zeit in der GridView auf die Schaltfläche Bearbeiten. Jisun ändert den Produktnamen in "Chai Tea" und klickt auf die Schaltfläche Aktualisieren. Das Nettoergebnis ist eine UPDATE Anweisung, die an die Datenbank gesendet wird, die alle aktualisierbaren Felder des Produkts festlegt (obwohl Jisun nur ein Feld aktualisiert hat, ProductName). Zu diesem Zeitpunkt hat die Datenbank die Werte "Chai Tea", die Kategorie Getränke, der Lieferant Exotic Liquids usw. für dieses spezielle Produkt. Die GridView auf dem Bildschirm von Sam zeigt jedoch weiterhin den Produktnamen in der bearbeitbaren GridView-Zeile als "Chai" an. Einige Sekunden nach dem Commit von Jisuns Änderungen aktualisiert Sam die Kategorie in Condiments und klickt auf Aktualisieren. Dies führt zu einer UPDATE an die Datenbank gesendeten Anweisung, die den Produktnamen auf "Chai", die auf die CategoryID entsprechende Getränkekategorie-ID usw. festlegt. Jisuns Änderungen am Produktnamen wurden überschrieben. Abbildung 1 zeigt diese Ereignisreihe grafisch.

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

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

Wenn zwei Benutzer eine Seite besuchen, kann ein Benutzer gerade dabei sein, einen Datensatz zu aktualisieren, 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 den Inhalt dieses Datensatzes geändert.

Es stehen drei Strategien zur Parallelitätssteuerung zur Verfügung:

  • Nichts tun : Wenn gleichzeitige Benutzer denselben Datensatz ändern, lassen Sie den letzten Commit gewinnen (das Standardverhalten).
  • Optimistische Parallelität : Gehen Sie davon aus, dass es zwar hin und wieder zu Parallelitätskonflikten kommen kann, aber in der überwiegenden Mehrheit der Zeit werden solche Konflikte nicht auftreten; Wenn also ein Konflikt auftritt, informieren Sie den Benutzer einfach darüber, dass seine Änderungen nicht gespeichert werden können, da ein anderer Benutzer dieselben Daten geändert hat.
  • Pessimistische Parallelität : Angenommen, dass Parallelitätskonflikte üblich sind und Benutzer nicht tolerieren, dass ihre Änderungen aufgrund der gleichzeitigen Aktivität eines anderen Benutzers nicht gespeichert wurden; Wenn ein Benutzer daher mit der Aktualisierung eines Datensatzes beginnt, sperren Sie ihn, sodass andere Benutzer diesen Datensatz nicht bearbeiten oder löschen können, bis der Benutzer seine Änderungen committet.

In allen unseren Tutorials haben wir bisher die Standardstrategie für parallele Parallelitätsauflösung verwendet– nämlich haben wir den letzten Schreibvorgang gewinnen lassen. In diesem Tutorial untersuchen wir, wie Sie die Steuerung für optimistische Parallelität implementieren.

Hinweis

In dieser Tutorialreihe werden keine Beispiele für pessimistische Parallelität behandelt. Pessimistische Parallelität wird selten verwendet, da solche Sperren, wenn sie nicht ordnungsgemäß aufgehoben werden, andere Benutzer daran hindern können, Daten zu aktualisieren. Wenn ein Benutzer beispielsweise einen Datensatz für die Bearbeitung sperrt und dann für den Tag verlässt, bevor er entsperrt wird, kann kein anderer Benutzer diesen Datensatz aktualisieren, bis der ursprüngliche Benutzer zurückgibt und seine Aktualisierung abgeschlossen hat. Daher gibt es in Situationen, in denen pessimistische Parallelität verwendet wird, in der Regel ein Timeout, das bei Erreichen die Sperre aufbricht. Ticketverkaufswebsites, die einen bestimmten Sitzplatz für einen kurzen Zeitraum sperren, während der Benutzer den Bestellvorgang abgeschlossen hat, sind ein Beispiel für eine pessimistische Parallelitätssteuerung.

Schritt 1: Wie optimistische Parallelität implementiert wird

Die optimistische Parallelitätssteuerung funktioniert, indem sichergestellt wird, dass der zu aktualisierende oder gelöschte Datensatz die gleichen Werte aufweist wie beim Starten des Aktualisierungs- oder Löschvorgangs. Wenn Sie beispielsweise in einer bearbeitbaren GridView auf die Schaltfläche Bearbeiten klicken, werden die Werte des Datensatzes aus der Datenbank gelesen und in TextBoxes und anderen Websteuerelementen angezeigt. Diese ursprünglichen Werte werden von GridView gespeichert. Später, nachdem der Benutzer seine Änderungen vorgenommen und auf die Schaltfläche Aktualisieren klickt, werden die ursprünglichen Werte und die neuen Werte an die Geschäftslogikebene und dann nach unten zur Datenzugriffsebene gesendet. Die Datenzugriffsebene muss eine SQL-Anweisung ausgeben, die den Datensatz nur aktualisiert, wenn die ursprünglichen Werte, die der Benutzer mit der Bearbeitung begonnen hat, mit den Werten identisch sind, die sich noch in der Datenbank befinden. Abbildung 2 zeigt diese Ereignissequenz.

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

Abbildung 2: Damit das Update oder Löschen erfolgreich ist, müssen die ursprünglichen Werte den aktuellen Datenbankwerten entsprechen (Klicken Sie hier, um das bild in voller Größe anzuzeigen).

Es gibt verschiedene Ansätze, um optimistische Parallelität zu implementieren (siehe Peter A. BrombergsOptimistische Parallelitätsaktualisierungslogik für einen kurzen Blick auf eine Reihe von Optionen). Das ADO.NET Typisiertes DataSet bietet eine Implementierung, die nur mit dem Häkchen eines Kontrollkästchens konfiguriert werden kann. Durch aktivieren der optimistischen Parallelität für einen TableAdapter im Typed DataSet werden die TableAdapter- UPDATE und DELETE -Anweisungen erweitert, um einen Vergleich aller ursprünglichen Werte in der WHERE -Klausel aufzunehmen. Die folgende UPDATE Anweisung aktualisiert beispielsweise den Namen und den Preis eines Produkts nur, wenn die aktuellen Datenbankwerte den Werten entsprechen, die beim Aktualisieren des Datensatzes in GridView ursprünglich abgerufen wurden. Die @ProductName Parameter und @UnitPrice enthalten die vom Benutzer eingegebenen neuen Werte, während @original_ProductName und @original_UnitPrice die Werte enthalten, die ursprünglich in die GridView geladen wurden, als auf die Schaltfläche Bearbeiten geklickt wurde:

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

Hinweis

Diese UPDATE Anweisung wurde aus Gründen der Lesbarkeit vereinfacht. In der Praxis wäre die UnitPrice Überprüfung der WHERE -Klausel wichtiger, da UnitPrice s enthalten NULL kann und überprüft werden kann, wenn NULL = NULL immer False zurückgegeben wird (stattdessen müssen Sie verwenden IS NULL).

Zusätzlich zur Verwendung einer anderen zugrunde liegenden UPDATE Anweisung ändert das Konfigurieren eines TableAdapters für die Verwendung von optimistischer Parallelität auch die Signatur der direkten DB-Methoden. Erinnern Sie sich an unser erstes Tutorial Erstellen einer Datenzugriffsebene, dass direkte DB-Methoden eine Liste skalarer Werte als Eingabeparameter akzeptieren (anstelle eines stark typisierten DataRow- oder DataTable-instance). Bei Verwendung von optimistischer Parallelität enthalten die direkten Update() DB-Methoden und Delete() -Methoden auch Eingabeparameter für die ursprünglichen Werte. Darüber hinaus muss der Code in der BLL für die Verwendung des Batchaktualisierungsmusters (die Update() Methodenüberladungen, die DataRows und DataTables anstelle von skalaren Werten akzeptieren) ebenfalls geändert werden.

Anstatt die TableAdapters der vorhandenen DAL zu erweitern, um eine optimistische Parallelität zu verwenden (was eine Änderung der BLL erforderlich machen würde), erstellen wir stattdessen ein neues typisiertes DataSet mit dem Namen 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 Grundlagen geschaffen wurden, können Wir die ASP.NET-Seite erstellen.

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

Um ein neues typisiertes DataSet zu erstellen, klicken Sie mit der rechten Maustaste auf den DAL Ordner im App_Code Ordner, und fügen Sie ein neues DataSet mit dem Namen hinzu NorthwindOptimisticConcurrency. Wie wir im ersten Tutorial gesehen haben, wird dadurch dem typisierten DataSet ein neuer TableAdapter hinzugefügt, wodurch automatisch der TableAdapter-Konfigurations-Assistent gestartet wird. Auf dem ersten Bildschirm werden wir aufgefordert, die Datenbank anzugeben, mit der eine Verbindung hergestellt werden soll: Herstellen einer Verbindung mit derselben Northwind-Datenbank mithilfe der 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, die Daten abzufragen: über eine Ad-hoc-SQL-Anweisung, eine neue gespeicherte Prozedur oder eine vorhandene gespeicherte Prozedur. Da wir Ad-hoc-SQL-Abfragen in unserer 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 genau dieselbe SQL-Abfrage, die für den Products TableAdapter von unserer ursprünglichen DAL verwendet wird, die alle Spalten zusammen mit den Product 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 der gleichen SQL-Abfrage aus dem Products TableAdapter in der ursprünglichen DAL

Abbildung 5: Verwenden der gleichen SQL-Abfrage aus dem Products TableAdapter im ursprünglichen DAL (Klicken, um das vollständige Bild anzuzeigen)

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

Aktivieren Sie die Kontrolle für die optimistische Parallelität, indem Sie das CheckBox -Steuerelement

Abbildung 6: Aktivieren Sie die Kontrolle für die optimistische Parallelität, indem Sie das CheckBox "Optimistische Parallelität verwenden" überprüfen (Klicken Sie hier, um das vollständige Bild anzuzeigen)

Geben Sie schließlich an, dass der 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-Rückgabemuster von GetData in GetProducts, um die Benennungskonventionen zu Spiegel, die wir in unserer ursprünglichen DAL verwendet haben.

Lassen Sie den TableAdapter alle Datenzugriffsmuster nutzen

Abbildung 7: Verwenden sie alle Datenzugriffsmuster für den TableAdapter (Klicken Sie hier, um das bild in voller Größe anzuzeigen)

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

DataTable und TableAdapter wurden dem typisierten DataSet hinzugefügt

Abbildung 8: DataTable und TableAdapter wurden dem typisierten DataSet hinzugefügt (klicken, um das bild in voller Größe anzuzeigen)

Um die Unterschiede zwischen den UPDATE Abfragen und DELETE zwischen dem ProductsOptimisticConcurrency TableAdapter (der eine optimistische Parallelität verwendet) und dem TableAdapter -Produkt (was nicht der Fall ist) anzuzeigen, klicken Sie auf den TableAdapter, und wechseln Sie zum Eigenschaftenfenster. In den Untereigenschaften und UpdateCommand der DeleteCommand Eigenschaften CommandText sehen Sie die tatsächliche SQL-Syntax, die an die Datenbank gesendet wird, wenn die Aktualisierungs- oder Löschmethoden der 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 Aussage 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 eine optimistische Parallelität verwendet, einen Vergleich zwischen den vorhandenen Spaltenwerten der Product Tabelle und den ursprünglichen Werten zum Zeitpunkt der letzten Auffüllung von GridView (oder DetailsView oder FormView). Da alle Felder außer , und Werte enthalten NULL können, werden zusätzliche Parameter und Überprüfungen enthalten, um Werte in der WHERE -Klausel ordnungsgemäß zu vergleichenNULL.DiscontinuedProductNameProductID

Wir werden für dieses Tutorial keine zusätzlichen DataTables zum optimistischen Parallelitäts-fähigen DataSet hinzufügen, da unsere ASP.NET Seite nur Das Aktualisieren und Löschen von Produktinformationen enthält. Dennoch müssen wir die GetProductByProductID(productID) -Methode dem ProductsOptimisticConcurrency TableAdapter hinzufügen.

Klicken Sie hierzu mit der rechten Maustaste auf die Titelleiste des TableAdapters (den Bereich direkt über dem Namen und GetProducts den Fill Methodennamen), und wählen Sie im Kontextmenü Abfrage hinzufügen aus. Dadurch wird der TableAdapter-Abfragekonfigurations-Assistent gestartet. Wie bei der Anfänglichkonfiguration unseres TableAdapter können Sie die GetProductByProductID(productID) Methode mithilfe 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 diese Abfrage ein SELECT Abfragetyp ist, der Zeilen zurückgibt.

Markieren Sie den Abfragetyp als

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

Auf dem nächsten Bildschirm werden wir zur Verwendung der SQL-Abfrage aufgefordert, wobei die Standardabfrage des TableAdapter vorinstalliert 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 hier, 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 Sie hier, um das Bild in voller Größe anzuzeigen)

Nach Abschluss dieses Assistenten enthält der TableAdapter jetzt zwei Methoden zum Abrufen von Daten: GetProducts(), die alle Produkte zurückgibt, und GetProductByProductID(productID), das das angegebene Produkt zurückgibt.

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

Unsere vorhandene ProductsBLL Klasse enthält Beispiele für die Verwendung von Batchupdate- und DB-Direktmustern. Die AddProduct -Methode und UpdateProduct -Überladungen verwenden das Batchaktualisierungsmuster und übergeben eine ProductRow instance an die Update-Methode von TableAdapter. Die DeleteProduct -Methode verwendet dagegen das direkte DB-Muster und ruft die TableAdapter-Methode auf Delete(productID) .

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: , ProductID, SupplierIDProductName, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrderReorderLevel, und Discontinued. Es verwendet die Werte dieser zusätzlichen Eingabeparameter in WHERE der -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 des TableAdapter, die im Batchaktualisierungsmuster Update verwendet wird, hat sich jedoch nicht geändert, der Code, der zum Aufzeichnen der ursprünglichen und neuen Werte erforderlich ist. Anstatt daher zu versuchen, 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 dem Ordner innerhalb des BLL Ordners eine Klasse mit dem App_Code Namen ProductsOptimisticConcurrencyBLL 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 ProductsOptimisticConcurrencyBLL folgenden Code hinzu:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindOptimisticConcurrencyTableAdapters;
[System.ComponentModel.DataObject]
public class ProductsOptimisticConcurrencyBLL
{
    private ProductsOptimisticConcurrencyTableAdapter _productsAdapter = null;
    protected ProductsOptimisticConcurrencyTableAdapter Adapter
    {
        get
        {
            if (_productsAdapter == null)
                _productsAdapter = new ProductsOptimisticConcurrencyTableAdapter();
            return _productsAdapter;
        }
    }
    [System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Select, true)]
    public NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable GetProducts()
    {
        return Adapter.GetProducts();
    }
}

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

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

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

Wenn Sie das direkte DB-Muster für eine DAL verwenden, die optimistische Parallelität verwendet, müssen die Methoden die neuen und ursprünglichen Werte übergeben werden. Zum Löschen gibt es keine neuen Werte, sodass nur die ursprünglichen Werte übergeben werden müssen. In unserer BLL müssen wir dann alle ursprünglichen Parameter als Eingabeparameter akzeptieren. Lassen Sie uns, dass die DeleteProduct -Methode in der ProductsOptimisticConcurrencyBLL -Klasse die direkte DB-Methode verwendet. Dies bedeutet, dass diese Methode alle zehn Produktdatenfelder als Eingabeparameter aufnehmen und diese an die DAL übergeben muss, wie im folgenden Code gezeigt:

[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteProduct
    (int original_productID, string original_productName,
    int? original_supplierID, int? original_categoryID,
    string original_quantityPerUnit, decimal? original_unitPrice,
    short? original_unitsInStock, short? original_unitsOnOrder,
    short? original_reorderLevel, bool original_discontinued)
{
    int rowsAffected = 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;
}

Wenn sich die ursprünglichen Werte – die Werte, die zuletzt in GridView (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 gibt die TableAdapter-Methode Delete zurück 0 , und die BLL-Methode DeleteProduct gibt zurück false.

Aktualisieren eines Produkts mithilfe des Batchupdatemusters mit optimistischer Parallelität

Wie bereits erwähnt, weist die TableAdapter-Methode für das Batchaktualisierungsmuster Update die gleiche Methodensignatur auf, unabhängig davon, ob optimistische Parallelität verwendet wird. Die -Methode erwartet nämlich Update ein DataRow-Objekt, ein Array von DataRows, eine DataTable oder ein typisiertes 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 ihre DataRow(s) 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 DataRow-Werten aufgefüllt werden.

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

  1. Lesen der aktuellen Datenbankproduktinformationen in eine ProductRow instance mithilfe der TableAdapter-Methode GetProductByProductID(productID)
  2. Zuweisen der neuen Werte zum ProductRow instance aus Schritt 1
  3. Rufen Sie die TableAdapter-Methode aufUpdate, und übergeben Sie die ProductRow instance

Diese Abfolge von Schritten unterstützt jedoch die optimistische Parallelität nicht ordnungsgemäß, da die ProductRow in Schritt 1 aufgefüllte 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 zu Beginn des Bearbeitungsprozesses an gridView gebunden waren. Wenn Sie stattdessen eine DAL mit aktivierter optimistischer Parallelität verwenden, müssen sie die UpdateProduct Methodenüberladungen ändern, um die folgenden Schritte auszuführen:

  1. Lesen der aktuellen Datenbankproduktinformationen in eine ProductsOptimisticConcurrencyRow instance mithilfe der TableAdapter-Methode GetProductByProductID(productID)
  2. Zuweisen der ursprünglichen Werte zum ProductsOptimisticConcurrencyRow instance aus Schritt 1
  3. Rufen Sie die Methode des ProductsOptimisticConcurrencyRow instance aufAcceptChanges(), die DataRow anweist, dass die aktuellen Werte die "ursprünglichen" sind.
  4. Weisen Sie der instance die ProductsOptimisticConcurrencyRowneuen Werte zu.
  5. Rufen Sie die TableAdapter-Methode aufUpdate, und übergeben Sie die ProductsOptimisticConcurrencyRow instance

Schritt 1 liest alle aktuellen Datenbankwerte für den angegebenen Produktdatensatz ein. Dieser Schritt ist in der UpdateProduct Überladung überflüssig, die alle Produktspalten aktualisiert (da diese Werte in Schritt 2 überschrieben werden), ist aber für die Überladungen unerlässlich, bei denen nur eine Teilmenge der Spaltenwerte als Eingabeparameter übergeben wird. Nachdem die ursprünglichen Werte dem ProductsOptimisticConcurrencyRow instance zugewiesen wurden, wird die AcceptChanges() -Methode aufgerufen, die die aktuellen DataRow-Werte als die ursprünglichen Werte markiert, 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 dataRow übergeben wird.

Der folgende Code zeigt die UpdateProduct Überladung, die alle Produktdatenfelder als Eingabeparameter akzeptiert. Die im Download für dieses Tutorial enthaltene Klasse wird hier zwar nicht angezeigt, ProductsOptimisticConcurrencyBLL enthält aber auch eine UpdateProduct Überladung, die nur den Namen und den Preis des Produkts als Eingabeparameter akzeptiert.

protected void AssignAllProductValues
    (NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,
    string productName, int? supplierID, int? categoryID, string quantityPerUnit,
    decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
    short? reorderLevel, bool discontinued)
{
    product.ProductName = productName;
    if (supplierID == null)
        product.SetSupplierIDNull();
    else
        product.SupplierID = supplierID.Value;
    if (categoryID == null)
        product.SetCategoryIDNull();
    else
        product.CategoryID = categoryID.Value;
    if (quantityPerUnit == null)
        product.SetQuantityPerUnitNull();
    else
        product.QuantityPerUnit = quantityPerUnit;
    if (unitPrice == null)
        product.SetUnitPriceNull();
    else
        product.UnitPrice = unitPrice.Value;
    if (unitsInStock == null)
        product.SetUnitsInStockNull();
    else
        product.UnitsInStock = unitsInStock.Value;
    if (unitsOnOrder == null)
        product.SetUnitsOnOrderNull();
    else
        product.UnitsOnOrder = unitsOnOrder.Value;
    if (reorderLevel == null)
        product.SetReorderLevelNull();
    else
        product.ReorderLevel = reorderLevel.Value;
    product.Discontinued = discontinued;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateProduct(
    // new parameter values
    string productName, int? supplierID, int? categoryID, string quantityPerUnit,
    decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
    short? reorderLevel, bool discontinued, int productID,
    // original parameter values
    string original_productName, int? original_supplierID, int? original_categoryID,
    string original_quantityPerUnit, decimal? original_unitPrice,
    short? original_unitsInStock, short? original_unitsOnOrder,
    short? original_reorderLevel, bool original_discontinued,
    int original_productID)
{
    // STEP 1: Read in the current database product information
    NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable products =
        Adapter.GetProductByProductID(original_productID);
    if (products.Count == 0)
        // no matching record found, return false
        return false;
    NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product = 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
    int rowsAffected = Adapter.Update(product);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}

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

Wenn DAL und BLL abgeschlossen sind, müssen Sie nur noch eine ASP.NET Seite erstellen, die die in das System integrierte Logik für optimistische Parallelität nutzen kann. Insbesondere muss das Datenwebsteuerelement (GridView, DetailsView oder FormView) seine ursprünglichen Werte speichern, und objectDataSource muss beide Wertesätze an die Geschäftslogikebene übergeben. Darüber hinaus muss die seite ASP.NET so konfiguriert werden, dass Verstöße gegen Parallelität 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 die ID -Eigenschaft auf festProductsGrid. Wählen Sie über das Smarttag von GridView aus, um eine neue ObjectDataSource mit dem Namen ProductsOptimisticConcurrencyDataSourcezu erstellen. Da diese ObjectDataSource die DAL verwenden soll, die optimistische Parallelität unterstützt, konfigurieren Sie sie für die Verwendung des ProductsOptimisticConcurrencyBLL -Objekts.

Lassen Sie objectDataSource das ProductsOptimisticConcurrencyBLL-Objekt verwenden.

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

Wählen Sie die GetProductsMethoden , UpdateProductund DeleteProduct aus den Dropdownlisten im Assistenten aus. Verwenden Sie für die UpdateProduct-Methode die Überladung, 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 instance für jeden der zehn Eingabeparameter in der -Methode der ProductsOptimisticConcurrencyBLL KlasseDeleteProduct. Ebenso enthält die UpdateParameters Auflistung eine Parameter instance für jeden der Eingabeparameter in UpdateProduct.

Für die vorherigen Tutorials zur Datenänderung würden wir an dieser Stelle die ObjectDataSource-Eigenschaft OldValuesParameterFormatString entfernen, da diese Eigenschaft angibt, dass die BLL-Methode erwartet, dass sowohl die alten (oder ursprünglichen) Werte 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_productName, original_supplierIDusw. benannt haben, können Sie den OldValuesParameterFormatString -Eigenschaftswert als original_{0}belassen. Wenn die Eingabeparameter der BLL-Methoden jedoch Namen wie old_productName, old_supplierIDusw. aufweisen, 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 festzulegen CompareAllValues.

Konfigurieren der Eigenschaften und Felder von GridView

Nachdem die Eigenschaften der ObjectDataSource ordnungsgemäß konfiguriert sind, richten wir uns auf die Einrichtung von GridView. Da GridView das Bearbeiten und Löschen unterstützen soll, klicken Sie zunächst im Smarttag von GridView auf die Kontrollkästchen Bearbeitung aktivieren und Löschen aktivieren. Dadurch wird ein CommandField-Element hinzugefügt, dessen ShowEditButton und ShowDeleteButton auf truefestgelegt sind.

Wenn sie an objectDataSource ProductsOptimisticConcurrencyDataSource gebunden ist, enthält GridView ein Feld für jedes der Datenfelder des Produkts. Obwohl eine solche GridView bearbeitet werden kann, ist die Benutzererfahrung alles andere als akzeptabel. BoundFields CategoryID und SupplierID werden als TextBoxes gerendert, sodass der Benutzer die entsprechende Kategorie und den Lieferanten als ID-Nummern eingeben muss. Es gibt keine Formatierung für die numerischen Felder und keine Validierungssteuerelemente, um sicherzustellen, dass der Name des Produkts angegeben wurde und dass der Einzelpreis, die Lagereinheiten, die Einheiten in der Bestellung und die Werte der Neuanordnungsebene sowohl richtige numerische Werte als auch größer oder gleich Null sind.

Wie in den Tutorials Hinzufügen von Validierungssteuerelementen zu den Bearbeitungs- und Einfügeschnittstellen und Anpassen der Datenänderungsschnittstelle erläutert, kann die Benutzeroberfläche angepasst werden, indem BoundFields durch TemplateFields ersetzt wird. Ich habe dieses GridView und seine Bearbeitungsoberfläche auf folgende Weise geändert:

  • Die BoundFields, SupplierName, und CategoryName wurden entferntProductID.
  • Das BoundField wurde in ein TemplateField konvertiert ProductName und ein RequiredFieldValidation-Steuerelement hinzugefügt.
  • Konvertierte boundFields CategoryID und SupplierID in TemplateFields und passte die Bearbeitungsoberfläche so an, dass DropDownLists anstelle von TextBoxes verwendet wird. In diesen TemplateFields werden ItemTemplatesdie CategoryName Datenfelder und SupplierName angezeigt.
  • Konvertiert die UnitPrice, UnitsInStock, UnitsOnOrderund ReorderLevel BoundFields-Steuerelemente in TemplateFields und hinzugefügte CompareValidator-Steuerelemente.

Da wir bereits in den vorherigen Tutorials untersucht haben, wie diese Aufgaben ausgeführt werden, liste ich hier nur 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 dran, ein voll funktionsfähiges Beispiel zu haben. Es gibt jedoch ein paar Feinheiten, die sich einschleichen und uns Probleme bereiten werden. 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 übergibt (die dann an die BLL übergeben werden), ist es wichtig, dass die GridView-Eigenschaft EnableViewState auf true (Standard) festgelegt ist. Wenn Sie den Ansichtszustand deaktivieren, gehen die ursprünglichen Werte beim Postback verloren.

Übergeben der korrekten Ursprünglichen Werte an objectDataSource

Es gibt eine Reihe von Problemen mit der Art und Weise, wie GridView konfiguriert wurde. Wenn die ObjectDataSource-Eigenschaft ConflictDetection auf CompareAllValues festgelegt ist (wie unsere), wenn die ObjectDataSource- Update() oder Delete() -Methoden von GridView (oder DetailsView oder FormView) aufgerufen werden, versucht die ObjectDataSource, die ursprünglichen Werte von GridView in die entsprechenden Parameter Instanzen zu kopieren. Eine grafische Darstellung dieses Prozesses finden Sie in Abbildung 2.

Insbesondere werden den ursprünglichen Werten von GridView die Werte in den bidirektionalen Datenbindungsanweisungen zugewiesen, wenn die Daten an gridView gebunden werden. Daher ist es wichtig, dass die erforderlichen Originalwerte alle ü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 sind in einer GridView aufgeführt.

Abbildung 14: Die Produkte sind in einer GridView aufgeführt (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

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

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

Abbildung 15: Versuch, beliebige Produktergebnisse in einem FormatException zu löschen (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Wird FormatException ausgelöst, wenn objectDataSource versucht, den ursprünglichen UnitPrice Wert einzulesen. Da die ItemTemplateUnitPrice als Währung () formatiert ist,<%# Bind("UnitPrice", "{0:C}") %> enthält sie ein Währungssymbol, z. B. 19,95 USD. Tritt FormatException auf, wenn objectDataSource versucht, diese Zeichenfolge in eine decimalzu konvertieren. Um dieses Problem zu umgehen, haben wir eine Reihe von Optionen:

  • Entfernen Sie die Währungsformatierung aus dem ItemTemplate. Das heißt, anstatt zu verwenden <%# Bind("UnitPrice", "{0:C}") %>, verwenden Sie <%# Bind("UnitPrice") %>einfach . Der Nachteil ist, dass der Preis nicht mehr formatiert ist.
  • Zeigen Sie die UnitPrice als Währung formatiert in der ItemTemplatean, verwenden Sie jedoch die Eval Schlüsselwort (keyword), um dies zu erreichen. Denken Sie daran, dass eine Eval unidirektionale Datenbindung durchführt. Wir müssen weiterhin den UnitPrice Wert für die ursprünglichen Werte angeben, sodass wir weiterhin eine bidirektionale Datenbindungsanweisung in der ItemTemplatebenötigen. Dies kann jedoch in einem Label Web-Steuerelement platziert werden, dessen Visible Eigenschaft auf falsefestgelegt ist. Wir könnten das folgende Markup in 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 , ItemTemplateindem Sie verwenden <%# Bind("UnitPrice") %>. Greifen Sie im GridView-Ereignishandler RowDataBound programmgesteuert auf das Label Web-Steuerelement zu, in dem der UnitPrice Wert angezeigt wird, und legen Sie dessen Text Eigenschaft auf die formatierte Version fest.
  • Lassen Sie die UnitPrice als Währung formatiert. Ersetzen Sie im GridView-Ereignishandler RowDeleting den vorhandenen ursprünglichen UnitPrice Wert ($19,95) mithilfe Decimal.Parsevon durch einen tatsächlichen Dezimalwert. Im Tutorial Behandeln von RowUpdatingBLL- und DAL-Level-Ausnahmen in einem ASP.NET Page haben wir erfahren, wie Sie etwas Ähnliches im Ereignishandler erreichen können.

Für mein Beispiel habe ich mich für den zweiten Ansatz entschieden, indem ich ein ausgeblendetes Label Web-Steuerelement hinzufügte, dessen Text Eigenschaft bidirektionale Daten sind, die an den unformatierten UnitPrice Wert gebunden sind.

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

Die ObjectDataSource kann keine Methode mit den Eingabeparametern finden, die gesendet werden soll.

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

Wenn Sie die Meldung der Ausnahme betrachten, ist klar, dass ObjectDataSource eine BLL-Methode DeleteProduct aufrufen möchte, die - und original_SupplierName -Eingabeparameter enthältoriginal_CategoryName. Dies liegt daran, dass die ItemTemplate s für und CategoryIDSupplierID TemplateFields derzeit bidirektionale Bind-Anweisungen mit den CategoryName Datenfeldern und SupplierName enthalten. Stattdessen müssen anweisungen in die CategoryID Datenfelder und SupplierID eingeschlossen Bind werden. Ersetzen Sie dazu die vorhandenen Bind-Anweisungen durch Eval -Anweisungen, und fügen Sie dann ausgeblendete Label-Steuerelemente hinzu, deren Text Eigenschaften an die CategoryID Datenfelder und SupplierID gebunden sind, indem Sie die bidirektionale Datenbindung verwenden, wie unten gezeigt:

<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 sind wir nun in der Lage, Produktinformationen erfolgreich zu löschen und zu bearbeiten! In Schritt 5 erfahren Sie, wie Sie überprüfen, ob Parallelitätsverletzungen erkannt werden. Es dauert jedoch einige Minuten, um zu 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 Unterstützung für optimistische Parallelität

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

Im anderen Browserfenster instance, zeigt der Produktname TextBox jedoch weiterhin "Chai" an. Aktualisieren Sie in diesem zweiten Browserfenster auf UnitPrice25.00. Ohne Unterstützung für optimistische Parallelität würde durch Klicken auf Aktualisieren im zweiten Browser instance der Produktname wieder in "Chai" geändert, wodurch die Vom ersten Browser vorgenommenen Änderungen instance überschrieben werden. Wenn jedoch optimistische Parallelität verwendet wird, führt das Klicken auf die Schaltfläche Aktualisieren im zweiten Browser instance zu einer DBConcurrencyException.

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

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

Wird DBConcurrencyException nur ausgelöst, wenn das Batchupdatemuster der 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, setzen Sie gridView für beide Browserinstanzen in den Vorbearbeitungszustand zurück. Klicken Sie als Nächstes im ersten Browser instance auf die Schaltfläche Bearbeiten, ändern Sie den Produktnamen von "Chai Tea" zurück in "Chai", und klicken Sie auf Aktualisieren. Klicken Sie im zweiten Browserfenster auf die Schaltfläche Löschen für Chai.

Wenn Sie auf Löschen klicken, sendet die Seite zurück, gridView ruft die ObjectDataSource-Methode Delete() auf, und die ObjectDataSource ruft die -Methode der ProductsOptimisticConcurrencyBLL Klasse DeleteProduct auf und übergibt die ursprünglichen Werte. Der ursprüngliche ProductName Wert für die zweite Browser-instance ist "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 es keinen Datensatz in der Datenbank gibt, den die WHERE -Klausel erfüllt. Die DeleteProduct -Methode gibt zurück false , und die ObjectDataSource-Daten werden an GridView zurückgegeben.

Aus Sicht des Endbenutzers hat das Klicken auf die Schaltfläche Löschen für Chai Tea im zweiten Browserfenster den Bildschirm blinken lassen, und nach der Rückkehr ist das Produkt immer noch vorhanden, obwohl es jetzt als "Chai" aufgeführt ist (die Produktnamenänderung vom ersten Browser instance). Wenn der Benutzer erneut auf die Schaltfläche Löschen klickt, wird löschen erfolgreich ausgeführt, da der ursprüngliche ProductName Wert von GridView ("Chai") jetzt mit dem Wert in der Datenbank übereinstimmt.

In beiden Fällen ist die Benutzererfahrung alles andere als ideal. Bei Verwendung des Batchaktualisierungsmusters möchten wir dem Benutzer eindeutig nicht die ausführlichen 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 Label Web-Steuerelemente auf der Seite erstellen, die eine Erklärung dafür liefern, warum ein Update oder Löschfehler aufgetreten ist. Für das Batchaktualisierungsmuster können wir bestimmen, ob im Post-Level-Ereignishandler von GridView eine DBConcurrencyException Ausnahme aufgetreten ist, wobei die Warnungsbezeichnung nach Bedarf angezeigt wird. Für die direkte DB-Methode können wir den Rückgabewert der BLL-Methode untersuchen (wenn andernfalls true eine Zeile betroffen false ist) und bei Bedarf eine Informationsmeldung anzeigen.

Schritt 6: Hinzufügen von Informationsmeldungen und Anzeigen dieser Nachrichten im Angesicht einer Nebenläufigkeitsverletzung

Wenn eine Parallelitätsverletzung auftritt, hängt das gezeigte Verhalten davon ab, ob das Batchupdate des DAL oder das direkte DB-Muster verwendet wurde. In unserem Tutorial werden beide Muster verwendet, wobei das Batchaktualisierungsmuster für die Aktualisierung und das direkte Datenbankmuster zum Löschen verwendet wird. Fügen Sie zunächst zwei Label Web-Steuerelemente zu unserer Seite hinzu, die erklären, dass beim Löschen oder Aktualisieren von Daten ein Parallelitätsverstoß aufgetreten ist. Legen Sie die Eigenschaften und EnableViewState des Label-Steuerelements Visible auf falsefest. Dies führt dazu, dass sie bei jedem Seitenbesuch ausgeblendet werden, mit Ausnahme der speziellen Seitenbesuche, bei denen die Visible Eigenschaft programmgesteuert auf truefestgelegt 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 der VisibleEigenschaften , EnabledViewStateund Text habe ich auch die CssClass -Eigenschaft auf Warningfestgelegt, wodurch die Bezeichnungen in einer großen, roten, kursiven, fett formatierten Schriftart angezeigt werden. Diese CSS-Klasse Warning wurde definiert und Styles.css im Tutorial Untersuchen der Ereignisse im Zusammenhang mit Einfügen, Aktualisieren und Löschen hinzugefügt.

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

Der Seite wurden zwei Bezeichnungssteuerelemente hinzugefügt.

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

Wenn diese Label Web-Steuerelemente vorhanden sind, können wir untersuchen, wie ermittelt werden kann, wann ein Parallelitätsverstoß Visible aufgetreten ist, an dem die entsprechende Label-Eigenschaft auf truefestgelegt werden kann, wobei die Informationsmeldung angezeigt wird.

Behandeln von Parallelitätsverletzungen beim Aktualisieren

Sehen wir uns zunächst an, wie Beiläufigkeitsverletzungen bei Verwendung des Batchaktualisierungsmusters behandelt werden. Da solche Verstöße gegen das Batchaktualisierungsmuster dazu führen, dass eine DBConcurrencyException Ausnahme ausgelöst wird, müssen wir der seite ASP.NET Code hinzufügen, um zu ermitteln, ob während des Aktualisierungsprozesses eine DBConcurrencyException Ausnahme aufgetreten ist. Wenn ja, sollten wir dem Benutzer eine Meldung anzeigen, in der erklärt wird, dass seine Änderungen nicht gespeichert wurden, da ein anderer Benutzer die gleichen Daten zwischen dem Beginn der Bearbeitung des Datensatzes und dem Klicken auf die Schaltfläche Aktualisieren geändert hatte.

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

protected void ProductsGrid_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
    if (e.Exception != null && e.Exception.InnerException != null)
    {
        if (e.Exception.InnerException is System.Data.DBConcurrencyException)
        {
            // Display the warning message and note that the
            // exception has been handled...
            UpdateConflictMessage.Visible = true;
            e.ExceptionHandled = true;
        }
    }
}

Im Angesicht einer DBConcurrencyException Ausnahme zeigt dieser Ereignishandler das UpdateConflictMessage Label-Steuerelement an und gibt an, dass die Ausnahme behandelt wurde. Wenn dieser Code vorhanden ist, gehen die Änderungen des Benutzers verloren, wenn beim Aktualisieren eines Datensatzes ein Parallelitätsverstoß auftritt, da er gleichzeitig die Änderungen eines anderen Benutzers überschrieben hätte. Insbesondere wird gridView in den Zustand vor der Bearbeitung 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 erklärt das UpdateConflictMessage Label-Steuerelement dem Benutzer, was gerade passiert ist. Diese Abfolge von Ereignissen ist in Abbildung 19 beschrieben.

Die Updates eines Benutzers gehen im Angesicht einer Nebenläufigkeitsverletzung verloren

Abbildung 19: Updates eines Benutzers im Gesicht einer Parallelitätsverletzung verloren (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Hinweis

Alternativ können wir gridView nicht in den Zustand vor der Bearbeitung zurückversetzen, sondern das GridView-Objekt in seinem Bearbeitungszustand belassen, indem wir die KeepInEditMode -Eigenschaft des übergebenen GridViewUpdatedEventArgs Objekts auf true festlegen. Wenn Sie diesen Ansatz verwenden, sollten Sie jedoch sicher sein, dass die Daten (durch Aufrufen DataBind() der -Methode) neu an gridView gebunden werden, damit die Werte des anderen Benutzers in die Bearbeitungsoberfläche geladen werden. Der Code, der in diesem Tutorial heruntergeladen werden kann, enthält diese beiden Codezeilen im RowUpdated Ereignishandler auskommentiert. Heben Sie einfach die Auskommentierung dieser Codezeilen auf, damit gridView nach einer Parallelitätsverletzung im Bearbeitungsmodus verbleibt.

Reagieren auf Parallelitätsverletzungen beim Löschen

Mit dem direkten Db-Muster wird keine Ausnahme aufgrund eines Parallelitätsverstoßes ausgelöst. Stattdessen wirkt sich die Datenbank-Anweisung einfach auf keine Datensätze aus, da die WHERE-Klausel mit keinem Datensatz übereinstimmt. Alle in der BLL erstellten Datenänderungsmethoden wurden so konzipiert, dass sie einen booleschen Wert zurückgeben, der angibt, ob sie genau einen Datensatz beeinflusst haben oder nicht. Um zu ermitteln, ob beim Löschen eines Datensatzes ein Parallelitätsverstoß DeleteProduct aufgetreten ist, können wir daher den Rückgabewert der BLL-Methode untersuchen.

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

protected void ProductsOptimisticConcurrencyDataSource_Deleted(
    object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.ReturnValue != null && e.ReturnValue is bool)
    {
        bool deleteReturnValue = (bool)e.ReturnValue;
        if (deleteReturnValue == false)
        {
            // No row was deleted, display the warning message
            DeleteConflictMessage.Visible = true;
        }
    }
}

Angesichts einer Parallelitätsverletzung wird die Löschanforderung des Benutzers abgebrochen. GridView wird aktualisiert und zeigt die Änderungen an, die zwischen dem Laden der Seite und dem Klicken auf die Schaltfläche Löschen für diesen Datensatz aufgetreten sind. Wenn ein solcher Verstoß auftritt, wird die DeleteConflictMessage Bezeichnung angezeigt, die erklärt, was gerade passiert ist (siehe Abbildung 20).

Das Löschen eines Benutzers wird im Angesicht einer Parallelitätsverletzung abgebrochen.

Abbildung 20: Das Löschen eines Benutzers wird im Angesicht einer Parallelitätsverletzung abgebrochen (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Zusammenfassung

Möglichkeiten für Parallelitätsverletzungen bestehen in jeder Anwendung, die es mehreren gleichzeitigen Benutzern ermöglicht, Daten zu aktualisieren oder zu löschen. Wenn solche Verstöße nicht berücksichtigt werden, wenn zwei Benutzer gleichzeitig die gleichen Daten aktualisieren, die beim letzten Schreibvorgang "gewinnt", überschreiben sie die Änderungen des anderen Benutzers. Alternativ können Entwickler entweder eine optimistische oder eine pessimistische Parallelitätssteuerung implementieren. Bei der Kontrolle der optimistischen Parallelität wird davon ausgegangen, dass Parallelitätsverletzungen selten auftreten, und ein Update- oder Löschbefehl, der eine Parallelitätsverletzung darstellen würde, einfach nicht zulässig. Bei der Pessimistischen Parallelitätssteuerung wird davon ausgegangen, dass Parallelitätsverletzungen häufig auftreten, und das einfache Ablehnen des Aktualisierungs- oder Löschbefehls eines Benutzers ist nicht akzeptabel. Bei der pessimistischen Parallelitätssteuerung umfasst das Aktualisieren eines Datensatzes eine Sperrung, 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 der Steuerung der optimistischen Parallelität. Insbesondere enthalten die UPDATE für die Datenbank ausgegebenen Anweisungen und DELETE alle Spalten der Tabelle, wodurch sichergestellt wird, dass das Aktualisieren oder Löschen nur erfolgt, wenn die aktuellen Daten des Datensatzes mit den ursprünglichen Daten übereinstimmen, die der Benutzer beim Aktualisieren oder Löschen hatte. Nachdem die DAL so konfiguriert wurde, dass sie optimistische Parallelität unterstützt, müssen die BLL-Methoden aktualisiert werden. Darüber hinaus muss die ASP.NET Seite, die nach unten in die BLL aufruft, so konfiguriert werden, dass ObjectDataSource die ursprünglichen Werte aus dem Datenwebsteuerelement abruft und an die BLL übergibt.

Wie in diesem Tutorial gezeigt, umfasst die Implementierung der Steuerung der optimistischen Parallelität in einer ASP.NET Webanwendung das Aktualisieren von DAL und BLL und das Hinzufügen von Unterstützung auf der Seite ASP.NET. Ob diese zusätzliche Arbeit eine sinnvolle Investition Ihrer Zeit und Ihres Aufwands ist oder nicht, hängt von Ihrer Anwendung ab. Wenn Sie nur selten gleichzeitige Benutzer haben, die Daten aktualisieren oder sich die von ihnen aktualisierten Daten voneinander unterscheiden, ist die Parallelitätssteuerung kein wichtiges Problem. Wenn jedoch routinemäßig mehrere Benutzer auf Ihrer Website mit denselben Daten arbeiten, kann die Parallelitätssteuerung dazu beitragen, zu verhindern, dass die Aktualisierungen oder Löschungen eines Benutzers unwissentlich die eines anderen Benutzers überschreiben.

Viel Spaß beim Programmieren!

Zum Autor

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