Implementieren von optimistischer Parallelität (VB)
von Scott Mitchell
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 die Steuerung der optimistischen Parallelität, um dieses Risiko zu bewältigen.
Einführung
Für Webanwendungen, die benutzern nur das Anzeigen von Daten ermöglichen, 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 dem eines anderen gleichzeitigen Benutzers in Konflikt stehen. Wenn zwei Benutzer gleichzeitig einen einzelnen Datensatz bearbeiten, überschreibt der Benutzer, der seine Änderungen zuletzt committet, die änderungen, die vom ersten benutzer vorgenommen wurden, ohne dass eine Parallelitätsrichtlinie vorhanden ist.
Angenommen, zwei Benutzer, Jisun und Sam, besuchen beide eine Seite in unserer Anwendung, auf der Besucher die Produkte über ein GridView-Steuerelement aktualisieren und löschen können. Beide klicken ungefähr zur gleichen Zeit in 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, den Lieferanten Exotic Liquids und so weiter für dieses spezielle Produkt. Auf dem Bildschirm von Sam wird der Produktname in der bearbeitbaren GridView-Zeile jedoch weiterhin als "Chai" angezeigt. Einige Sekunden nach dem Commit von Jisuns Änderungen aktualisiert Sam die Kategorie in Condiments und klickt auf Aktualisieren. Dies führt zu einer UPDATE
Anweisung, die an die Datenbank gesendet wird, die den Produktnamen auf "Chai", die auf CategoryID
die entsprechende Getränkekategorie-ID usw. festlegt. Jisuns Änderungen am Produktnamen wurden überschrieben. Abbildung 1 zeigt diese Ereignisreihe grafisch.
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)
Ähnlich verhält es sich, wenn zwei Benutzer eine Seite besuchen, ein Benutzer gerade dabei ist, 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 möglicherweise 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 : Angenommen, es kann zwar hin und wieder Zu Parallelitätskonflikte geben, 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 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, sodass andere Benutzer diesen Datensatz bearbeiten oder löschen können, bis der Benutzer seine Änderungen committet.
In allen unseren Tutorials wurde bisher die Standardstrategie für parallele Parallelitätsauflösung verwendet, d. h. wir haben den letzten Schreibvorgang gewinnen lassen. In diesem Tutorial erfahren Sie, wie Sie die Steuerung der optimistischen Parallelität implementieren.
Hinweis
Beispiele für pessimistische Parallelität werden in dieser Tutorialreihe nicht behandelt. Pessimistische Parallelität wird selten verwendet, da solche Sperren, wenn sie nicht ordnungsgemäß aufgegeben werden, andere Benutzer daran hindern können, Daten zu aktualisieren. Wenn ein Benutzer beispielsweise einen Datensatz für die Bearbeitung sperrt und ihn dann für den Tag verlässt, bevor er entsperrt wird, kann kein anderer Benutzer diesen Datensatz aktualisieren, bis der ursprüngliche Benutzer zurückkommt und sein Update abgeschlossen hat. Daher gibt es in Situationen, in denen pessimistische Parallelität verwendet wird, in der Regel ein Timeout, das die Sperre bei Erreichen aufbricht. Ticketverkaufswebsites, die einen bestimmten Sitzplatz für einen kurzen Zeitraum sperren, während der Benutzer den Bestellvorgang abschließt, ist ein Beispiel für eine pessimistische Parallelitätssteuerung.
Schritt 1: Wie optimistische Parallelität implementiert wird
Die Steuerung der optimistischen Parallelität funktioniert, indem sichergestellt wird, dass der datensatz, der aktualisiert oder gelöscht wird, dieselben Werte aufweist wie beim Starten des Aktualisierungs- oder Löschprozesses. Wenn Sie beispielsweise in einem bearbeitbaren GridView auf die Schaltfläche Bearbeiten klicken, werden die Werte des Datensatzes aus der Datenbank gelesen und in TextBoxes und anderen Websteuerelementen angezeigt. Diese ursprünglichen Werte werden von GridView gespeichert. Später, nachdem der Benutzer seine Änderungen vorgenommen und auf die Schaltfläche Aktualisieren klickt, 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 bearbeitet hat, mit den Werten identisch sind, die sich noch in der Datenbank befinden. Abbildung 2 zeigt diese Abfolge von Ereignissen.
Abbildung 2: Damit das Update oder das 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 für die Implementierung optimistischer Parallelität (siehe Peter A. BrombergsOptimistische Parallelitätsaktualisierungslogik für eine kurze Übersicht über eine Reihe von Optionen). Das ADO.NET Typisiertes DataSet stellt eine Implementierung bereit, die nur mit einem Häkchen eines Kontrollkästchens konfiguriert werden kann. Durch aktivieren der optimistischen Parallelität für einen TableAdapter im typisierten DataSet werden die TableAdapter- UPDATE
und DELETE
-Anweisungen erweitert, um einen Vergleich aller ursprünglichen Werte in der WHERE
-Klausel einzuschließen. Die folgende UPDATE
Anweisung aktualisiert z. B. den Namen und den Preis eines Produkts nur, wenn die aktuellen Datenbankwerte den Werten entsprechen, die beim Aktualisieren des Datensatzes in GridView ursprünglich abgerufen wurden. Die @ProductName
Parameter und @UnitPrice
enthalten die vom Benutzer eingegebenen neuen Werte, während @original_ProductName
und @original_UnitPrice
die Werte enthalten, die beim Klicken auf die Schaltfläche Bearbeiten ursprünglich in gridView geladen wurden:
UPDATE Products SET
ProductName = @ProductName,
UnitPrice = @UnitPrice
WHERE
ProductID = @original_ProductID AND
ProductName = @original_ProductName AND
UnitPrice = @original_UnitPrice
Hinweis
Diese UPDATE
Anweisung wurde aus Gründen der Lesbarkeit vereinfacht. In der Praxis wäre die UnitPrice
Überprüfung in der WHERE
-Klausel komplexer, 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 optimistischer Parallelität auch die Signatur der direkten DB-Methoden. Erinnern Sie sich an unser erstes Tutorial Erstellen einer Datenzugriffsebene, dass direkte DB-Methoden die waren, die eine Liste von Skalarwerten als Eingabeparameter akzeptieren (anstelle eines stark typisierten DataRow- oder DataTable-instance). Bei verwendung der optimistischen Parallelität enthalten die direkten Update()
Db-Methoden und Delete()
die Methoden Auch Eingabeparameter für die ursprünglichen Werte. Darüber hinaus muss auch der Code in der BLL für die Verwendung des Batchaktualisierungsmusters (die Update()
Methodenüberladungen, die DataRows und DataTables anstelle von Skalarwerten akzeptieren) geändert werden.
Anstatt die TableAdapters unserer vorhandenen DAL zu erweitern, um optimistische Parallelität zu verwenden (was eine Änderung der BLL erfordern würde, um die Anpassung der BLL zu ermöglichen), erstellen wir stattdessen ein neues typisiertes 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. Nachdem dieser Grundstein gelegt wurde, können wir die seite ASP.NET erstellen.
Schritt 2: Erstellen einer Datenzugriffsebene, die 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 im ersten Tutorial gezeigt, 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. Stellen Sie mithilfe der Einstellung von eine Verbindung mit derselben NORTHWNDConnectionString
Northwind-Datenbank her Web.config
.
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 unserem ursprünglichen DAL verwendet haben, verwenden Sie diese Option auch hier.
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. Wir verwenden genau dieselbe SQL-Abfrage, die für den Products
TableAdapter aus unserer ursprünglichen DAL verwendet wurde, 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
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 zum nächsten Bildschirm wechseln, klicken Sie auf die Schaltfläche Erweiterte Optionen. Aktivieren Sie einfach das Kontrollkästchen "Optimistische Parallelität verwenden", damit dieser TableAdapter die Kontrolle über die optimistische Parallelität verwendet.
Abbildung 6: Aktivieren der Optimistischen Parallelitätssteuerung durch Überprüfen des CheckBox "Optimistische Parallelität verwenden" (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Geben Sie schließlich an, dass der TableAdapter die Datenzugriffsmuster verwenden soll, die sowohl eine DataTable fü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 Muster DataTable zurückgeben von GetData in GetProducts, um die Namenskonventionen zu Spiegel, die wir in unserer ursprünglichen DAL verwendet haben.
Abbildung 7: Verwenden aller Datenzugriffsmuster durch 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.
Abbildung 8: DataTable und TableAdapter wurden dem typisierten DataSet hinzugefügt (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Um die Unterschiede zwischen den UPDATE
Abfragen und DELETE
zwischen dem ProductsOptimisticConcurrency
TableAdapter (der optimistische Parallelität verwendet) und dem Products TableAdapter anzuzeigen (was nicht der Fall ist), klicken Sie auf tableAdapter, und wechseln Sie zum Eigenschaftenfenster. In den Untereigenschaften der DeleteCommand
Eigenschaften CommandText
und UpdateCommand
sehen Sie die tatsächliche SQL-Syntax, die an die Datenbank gesendet wird, wenn die Update- oder Löschmethoden der DAL aufgerufen werden. Für den ProductsOptimisticConcurrency
TableAdapter wird die DELETE
folgende Anweisung verwendet:
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 die viel einfachere 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 die optimistische Parallelität verwendet, einen Vergleich zwischen den Product
einzelnen vorhandenen Spaltenwerten der 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 eingeschlossen, um Werte in der WHERE
-Klausel korrekt zu vergleichenNULL
.Discontinued
ProductName
ProductID
Für dieses Tutorial fügen wir dem DataSet mit aktivierter optimistischer Parallelität keine zusätzlichen DataTables hinzu, da unsere seite ASP.NET nur das Aktualisieren und Löschen von Produktinformationen enthält. Wir müssen die GetProductByProductID(productID)
-Methode jedoch weiterhin dem ProductsOptimisticConcurrency
TableAdapter hinzufügen.
Klicken Sie dazu mit der rechten Maustaste auf die Titelleiste des TableAdapters (den Bereich rechts über dem Fill
Methodennamen und GetProducts
), und wählen Sie im Kontextmenü Abfrage hinzufügen aus. Dadurch wird der TableAdapter-Abfragekonfigurations-Assistent gestartet. Wie bei der Anfänglichkonfiguration von 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.
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 = @ProductID
einzuschließen, wie in Abbildung 10 dargestellt.
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
.
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
, SupplierID
ProductName
, CategoryID
, QuantityPerUnit
, UnitPrice
, UnitsInStock
, UnitsOnOrder
ReorderLevel
, 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.
Abbildung 12: Hinzufügen der ProductsOptimisticConcurrencyBLL
Klasse zum BLL-Ordner
Fügen Sie als Nächstes der -Klasse den ProductsOptimisticConcurrencyBLL
folgenden Code hinzu:
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 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
, CategoriesBLL
usw.) 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 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 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:
- Lesen der aktuellen Datenbankproduktinformationen in eine
ProductRow
instance mithilfe der TableAdapter-MethodeGetProductByProductID(productID)
- Zuweisen der neuen Werte zum
ProductRow
instance aus Schritt 1 - Rufen Sie die TableAdapter-Methode auf
Update
, und übergeben Sie dieProductRow
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:
- Lesen der aktuellen Datenbankproduktinformationen in eine
ProductsOptimisticConcurrencyRow
instance mithilfe der TableAdapter-MethodeGetProductByProductID(productID)
- Zuweisen der ursprünglichen Werte zum
ProductsOptimisticConcurrencyRow
instance aus Schritt 1 - Rufen Sie die Methode des
ProductsOptimisticConcurrencyRow
instance aufAcceptChanges()
, die DataRow anweist, dass die aktuellen Werte die "ursprünglichen" sind. - Weisen Sie der instance die
ProductsOptimisticConcurrencyRow
neuen Werte zu. - Rufen Sie die TableAdapter-Methode auf
Update
, und übergeben Sie dieProductsOptimisticConcurrencyRow
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 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 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 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.
Abbildung 13: Verwenden Des ProductsOptimisticConcurrencyBLL
Objekts durch ObjectDataSource (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Wählen Sie die GetProducts
Methoden , UpdateProduct
und 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_supplierID
usw. benannt haben, können Sie den OldValuesParameterFormatString
-Eigenschaftswert als original_{0}
belassen. Wenn die Eingabeparameter der BLL-Methoden jedoch Namen wie old_productName
, old_supplierID
usw. 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 true
festgelegt 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
, undCategoryName
wurden entferntProductID
. - Das BoundField wurde in ein TemplateField konvertiert
ProductName
und ein RequiredFieldValidation-Steuerelement hinzugefügt. - Konvertierte boundFields
CategoryID
undSupplierID
in TemplateFields und passte die Bearbeitungsoberfläche so an, dass DropDownLists anstelle von TextBoxes verwendet wird. In diesen TemplateFields werdenItemTemplates
dieCategoryName
Datenfelder undSupplierName
angezeigt. - Konvertiert die
UnitPrice
,UnitsInStock
,UnitsOnOrder
undReorderLevel
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.
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.
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 ItemTemplate
UnitPrice
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 decimal
zu 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 derItemTemplate
an, verwenden Sie jedoch dieEval
Schlüsselwort (keyword), um dies zu erreichen. Denken Sie daran, dass eineEval
unidirektionale Datenbindung durchführt. Wir müssen weiterhin denUnitPrice
Wert für die ursprünglichen Werte angeben, sodass wir weiterhin eine bidirektionale Datenbindungsanweisung in derItemTemplate
benötigen. Dies kann jedoch in einem Label Web-Steuerelement platziert werden, dessenVisible
Eigenschaft auffalse
festgelegt 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 ,
ItemTemplate
indem Sie verwenden<%# Bind("UnitPrice") %>
. Greifen Sie im GridView-EreignishandlerRowDataBound
programmgesteuert auf das Label Web-Steuerelement zu, in dem derUnitPrice
Wert angezeigt wird, und legen Sie dessenText
Eigenschaft auf die formatierte Version fest. - Lassen Sie die
UnitPrice
als Währung formatiert. Ersetzen Sie im GridView-EreignishandlerRowDeleting
den vorhandenen ursprünglichenUnitPrice
Wert ($19,95) mithilfeDecimal.Parse
von durch einen tatsächlichen Dezimalwert. Im Tutorial Behandeln vonRowUpdating
BLL- 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.
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 CategoryID
SupplierID
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 UnitPrice
25.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.
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 false
fest. Dies führt dazu, dass sie bei jedem Seitenbesuch ausgeblendet werden, mit Ausnahme der speziellen Seitenbesuche, bei denen die 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 der Visible
Eigenschaften , EnabledViewState
und Text
habe ich auch die CssClass
-Eigenschaft auf Warning
festgelegt, 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.
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 true
festgelegt 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 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 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 Ereignissequenz ist in Abbildung 19 beschrieben.
Abbildung 19: Die Updates eines Benutzers sind im Angesicht einer Parallelitätsverletzung verloren (Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Hinweis
Alternativ können wir die GridView im Bearbeitungszustand zurückstellen, indem wir die KeepInEditMode
Eigenschaft des übergebenen GridViewUpdatedEventArgs
Objekts auf true festlegen. Wenn Sie diesen Ansatz verwenden, sollten Sie jedoch sicher sein, dass sie die Daten an die GridView-Instanz neu binden (indem Sie dessen DataBind()
Methode aufrufen), damit die Werte des anderen Benutzers in die Bearbeitungsoberfläche geladen werden. Der code, der in diesem Tutorial zum Download verfügbar ist, enthält diese beiden Codezeilen im RowUpdated
Ereignishandler auskommentiert. Heben Sie einfach die Auskommentierung dieser Codezeilen auf, damit die GridView nach einer Parallelitätsverletzung im Bearbeitungsmodus verbleibt.
Reagieren auf Parallelitätsverletzungen beim Löschen
Beim direkten DB-Muster gibt es keine Ausnahme, die angesichts einer Parallelitätsverletzung ausgelöst wird. 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 betreffen oder nicht. Um zu ermitteln, ob beim Löschen eines Datensatzes eine Parallelitätsverletzung 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 Post-Level-Ereignishandlern von ObjectDataSource über die ReturnValue
-Eigenschaft des -Objekts untersucht werden, das ObjectDataSourceStatusEventArgs
an den Ereignishandler übergeben wird. 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 zuerst sicherstellen, dass die ReturnValue
Eigenschaft kein null
boolescher Wert ist und 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 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 Parallelitätsverletzung wird die Löschanforderung des Benutzers abgebrochen. Die GridView wird aktualisiert und zeigt die Änderungen an, die für diesen Datensatz zwischen dem Laden der Seite durch den Benutzer und dem Klicken auf die Schaltfläche Löschen aufgetreten sind. Wenn eine solche Verletzung auftritt, wird die DeleteConflictMessage
Bezeichnung angezeigt, die erklärt, was gerade passiert ist (siehe Abbildung 20).
Abbildung 20: Das Löschen eines Benutzers wird angesichts 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 dieselben Daten aktualisieren, die beim letzten Schreibvorgang "wins" erhalten, überschreiben Sie die Änderungen des anderen Benutzers. Alternativ können Entwickler entweder eine optimistische oder pessimistische Parallelitätssteuerung implementieren. Bei der Kontrolle der optimistischen Parallelität wird davon ausgegangen, dass Parallelitätsverletzungen selten vorkommen, und ein Update- oder Löschbefehl, der eine Parallelitätsverletzung darstellen würde, einfach nicht zulässig. Die pessimistische Parallelitätssteuerung geht davon aus, dass Parallelitätsverletzungen häufig auftreten und das einfache Ablehnen des Befehls zum Aktualisieren oder Löschen eines Benutzers nicht akzeptabel ist. Bei der pessimistischen Parallelitätssteuerung beinhaltet 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 optimistischer 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 eine optimistische Parallelität unterstützt, müssen die BLL-Methoden aktualisiert werden. Darüber hinaus muss die ASP.NET Seite, die in die BLL aufruft, so konfiguriert werden, dass ObjectDataSource die ursprünglichen Werte aus dem Datenwebsteuerelement abruft und sie an die BLL weitergibt.
Wie wir in diesem Tutorial gezeigt haben, beinhaltet die Implementierung einer optimistischen Parallelitätssteuerung in einer ASP.NET Webanwendung das Aktualisieren der DAL und BLL und das Hinzufügen von Unterstützung auf der ASP.NET Seite. Ob diese zusätzliche Arbeit eine sinnvolle Investition Ihrer Zeit und Mühe ist oder nicht, hängt von Ihrer Anwendung ab. Wenn Nur selten gleichzeitige Benutzer Daten aktualisieren oder sich die von ihnen aktualisierten Daten voneinander unterscheiden, ist die Parallelitätssteuerung kein wichtiges Problem. Wenn Sie jedoch regelmäßig mehrere Benutzer auf Ihrer Website mit denselben Daten arbeiten, kann die Parallelitätssteuerung verhindern, dass Updates oder Löschvorgänge eines Benutzers unwissentlich einen anderen ü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 Stunden. Er kann unter mitchell@4GuysFromRolla.comoder über seinen Blog erreicht werden, der unter http://ScottOnWriting.NETzu finden ist.