Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
von Scott Mitchell
Für eine Webanwendung, die es mehreren Benutzern ermöglicht, Daten zu bearbeiten, besteht das Risiko, dass zwei Benutzer dieselben Daten gleichzeitig bearbeiten können. In diesem Lernprogramm implementieren wir optimistische Parallelitätssteuerung zur Behandlung dieses Risikos.
Einleitung
Für Webanwendungen, die es Benutzern nur ermöglichen, Daten anzuzeigen, oder für Diejenigen, die nur einen einzelnen Benutzer umfassen, der Daten ändern kann, besteht keine Gefahr, dass zwei gleichzeitige Benutzer versehentlich die Änderungen eines anderen überschreiben. Für Webanwendungen, die es mehreren Benutzern ermöglichen, Daten zu aktualisieren oder zu löschen, besteht jedoch die Möglichkeit, dass die Änderungen eines Benutzers mit den gleichzeitigen Änderungen eines anderen Benutzers in Konflikt geraten. Ohne eine Gleichzeitigkeitsrichtlinie kommt es beim gleichzeitigen Bearbeiten eines einzelnen Datensatzes dazu, dass der Benutzer, der seine Änderungen zuletzt speichert, die vorherigen Änderungen des ersten Benutzers überschreibt.
Stellen Sie sich beispielsweise vor, dass zwei Benutzer, Jisun und Sam, beide eine Seite in unserer Anwendung besucht haben, mit der Besucher die Produkte über ein GridView-Steuerelement aktualisieren und löschen konnten. Beide klicken auf die Schaltfläche "Bearbeiten" in der GridView um die gleiche Zeit. Jisun ändert den Produktnamen in "Chai Tea" und klickt auf die Schaltfläche "Aktualisieren". Das Nettoergebnis ist eine UPDATE
Anweisung, die an die Datenbank gesendet wird, die alle aktualisierbaren Felder des Produkts festlegt (obwohl Jisun nur ein Feld aktualisiert hat). ProductName
Zu diesem Zeitpunkt hat die Datenbank die Werte "Chai Tea", die Kategorie "Getränke", den Lieferanten Exotische Flüssigkeiten usw. für dieses bestimmte Produkt. Auf dem Bildschirm von Sam wird jedoch weiterhin der Produktname in der bearbeitbaren GridView-Zeile als "Chai" angezeigt. Ein paar Sekunden nach dem Commit von Jisuns Änderungen aktualisiert Sam die Kategorie auf "Condiments" und klickt auf "Aktualisieren". Dies führt zu einer UPDATE
Anweisung, die an die Datenbank gesendet wird, die den Produktnamen auf "Chai", die CategoryID
entsprechende Kategorie-ID für Getränke usw. festlegt. Jisuns Änderungen an dem Produktnamen wurden überschrieben. In Abbildung 1 wird diese Ereignisreihe grafisch dargestellt.
Abbildung 1: Wenn zwei Benutzer gleichzeitig einen Datensatz aktualisieren, besteht das Potenzial für Änderungen eines Benutzers, um die anderen zu überschreiben (Klicken, um das Bild in voller Größe anzuzeigen)
Wenn zwei Benutzer eine Seite besuchen, befindet sich ein Benutzer möglicherweise mitten in der Aktualisierung eines Datensatzes, wenn er von einem anderen Benutzer gelöscht wird. Oder zwischen dem Laden einer Seite und dem Klicken auf die Schaltfläche "Löschen" hat ein anderer Benutzer möglicherweise den Inhalt dieses Datensatzes geändert.
Es stehen drei Parallelitätskontrollstrategien zur Verfügung:
- Do Nothing -if gleichzeitige Benutzer ändern denselben Datensatz; der letzte Commit gewinnt (das Standardverhalten).
- Optimistische Parallelität - gehen Sie davon aus, dass es zwar ab und zu Parallelitätskonflikte gibt, die überwiegende Mehrheit der Zeit werden solche Konflikte nicht auftreten; Wenn ein Konflikt auftritt, teilen Sie dem Benutzer einfach mit, dass seine Änderungen nicht gespeichert werden können, da ein anderer Benutzer die gleichen Daten geändert hat.
- Pessimistische Parallelität – gehen Sie davon aus, dass Parallelitätskonflikte üblich sind und dass Benutzer nicht tolerieren, dass ihre Änderungen aufgrund der gleichzeitigen Aktivität eines anderen Benutzers nicht gespeichert wurden; Wenn ein Benutzer mit der Aktualisierung eines Datensatzes beginnt, sperren Sie ihn, wodurch verhindert wird, dass andere Benutzer diesen Datensatz bearbeiten oder löschen, bis der Benutzer seine Änderungen festnimmt.
Alle unsere Tutorials haben bisher die Standardstrategie zur Lösung von Gleichzeitigkeit verwendet – nämlich lassen wir den letzten Schreibzug gewinnen. In diesem Tutorial werden wir untersuchen, wie optimistische Synchronisationssteuerung implementiert wird.
Hinweis
In dieser Lernprogrammreihe werden wir keine pessimistischen Parallelitätsbeispiele betrachten. 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 z. B. einen Datensatz zum Bearbeiten sperrt und dann vor der Entsperrung für den Tag verlässt, kann kein anderer Benutzer diesen Datensatz aktualisieren, bis der ursprüngliche Benutzer seine Aktualisierung zurückgibt und abgeschlossen hat. Daher gibt es in Situationen, in denen pessimistische Parallelität verwendet wird, normalerweise ein Timeout, das beim Erreichen die Sperre aufhebt. Ticketverkaufswebsites, die einen bestimmten Sitzplatz für kurze Zeit sperren, während der Benutzer den Bestellvorgang abgeschlossen hat, ist ein Beispiel für pessimistische Parallelitätskontrolle.
Schritt 1: Untersuchen, wie optimistische Parallelität implementiert wird
Die optimistische Parallelitätssteuerung funktioniert, indem sichergestellt wird, dass der Datensatz, der aktualisiert oder gelöscht wird, dieselben Werte aufweist wie beim Start des Aktualisierungs- oder Löschvorgangs. Wenn Sie beispielsweise auf die Schaltfläche "Bearbeiten" in einer bearbeitbaren GridView klicken, werden die Werte des Datensatzes aus der Datenbank gelesen und in TextBoxes und anderen Websteuerelementen angezeigt. Diese ursprünglichen Werte werden von GridView gespeichert. Nachdem der Benutzer ihre Änderungen vorgenommen hat und auf die Schaltfläche "Aktualisieren" klickt, werden die ursprünglichen Werte sowie die neuen Werte an die Geschäftslogikebene und dann nach unten an die Datenzugriffsschicht gesendet. Die Datenzugriffsebene muss eine SQL-Anweisung ausgeben, die den Datensatz nur aktualisiert, wenn die ursprünglichen Werte, die der Benutzer bearbeitet hat, mit den Werten identisch sind, die sich noch in der Datenbank befinden. Abbildung 2 zeigt diese Abfolge von Ereignissen.
Abbildung 2: Damit "Aktualisieren" oder "Löschen erfolgreich" ist, müssen die ursprünglichen Werte den aktuellen Datenbankwerten entsprechen (Klicken Sie, um das Bild in voller Größe anzuzeigen)
Es gibt verschiedene Ansätze zur Implementierung optimistischer Parallelität (siehe Peter A. Brombergsoptimistische Parallelitätsaktualisierungslogik für einen kurzen Blick auf eine Reihe von Optionen). Das ADO.NET Typed DataSet bietet eine Implementierung, die durch einfaches Ankreuzen eines Kontrollkästchens konfiguriert werden kann. Durch Aktivieren der optimistischen Parallelität für ein TableAdapter-Objekt im Typed DataSet werden die Anweisungen des TableAdapters UPDATE
und DELETE
erweitert, 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 Parameter @ProductName
und @UnitPrice
enthalten die neuen Werte, die vom Benutzer eingegeben wurden, während @original_ProductName
und @original_UnitPrice
die Werte enthalten, die ursprünglich in die GridView geladen wurden, als auf die Schaltfläche "Bearbeiten" geklickt wurde.
UPDATE Products SET
ProductName = @ProductName,
UnitPrice = @UnitPrice
WHERE
ProductID = @original_ProductID AND
ProductName = @original_ProductName AND
UnitPrice = @original_UnitPrice
Hinweis
Diese UPDATE
Anweisung wurde zur Lesbarkeit vereinfacht. In der Praxis erfordert die UnitPrice
Überprüfung der WHERE
Klausel mehr Aufwand, da UnitPrice
NULL
s enthalten kann und überprüft werden muss, ob NULL = NULL
immer "False" zurückgibt (stattdessen müssen Sie IS NULL
verwenden).
Zusätzlich zur Verwendung einer anderen zugrunde liegenden UPDATE
Anweisung ändert das Konfigurieren eines TableAdapter für die Verwendung optimistischer Parallelität auch die Signatur seiner direkten DB-Methoden. Erinnern Sie sich an unser erstes Lernprogramm zum Erstellen einer Datenzugriffsschicht, dass db-direkte Methoden diejenigen waren, die eine Liste skalarer Werte als Eingabeparameter akzeptieren (anstelle einer stark typierten DataRow- oder DataTable-Instanz). Bei Verwendung der optimistischen Parallelität enthalten die direkten DB-Methoden Update()
und Delete()
auch Eingabeparameter für die ursprünglichen Werte. Darüber hinaus muss der Code in der BLL für die Verwendung des Batchaktualisierungsmusters (die Methodenüberladungen, die Update()
DataRows und DataTables akzeptieren, anstelle von Skalarwerten) ebenfalls geändert werden.
Anstatt die TableAdapters unserer vorhandenen DAL zu erweitern, um optimistische Parallelität zu verwenden (was eine Änderung der BLL erfordert), wollen wir stattdessen ein neues typisiertes DataSet namens NorthwindOptimisticConcurrency
erstellen, dem wir einen Products
TableAdapter hinzufügen, der optimistische Parallelität verwendet. Anschließend erstellen wir eine ProductsOptimisticConcurrencyBLL
Business Logic Layer-Klasse, die über die entsprechenden Änderungen verfügt, um die optimistische Parallelität DAL zu unterstützen. Sobald diese Grundarbeiten erstellt wurden, können wir die ASP.NET Seite erstellen.
Schritt 2: Erstellen einer Datenzugriffsebene, die optimistische Parallelität unterstützt
Um ein neues Typed DataSet zu erstellen, klicken Sie mit der rechten Maustaste auf den DAL
Ordner innerhalb des App_Code
Ordners, und fügen Sie ein neues DataSet mit dem Namen hinzu NorthwindOptimisticConcurrency
. Wie wir im ersten Tutorial gesehen haben, wird dadurch ein neuer TableAdapter zum typisierten DataSet hinzugefügt, und der TableAdapter Konfigurationsassistent wird automatisch gestartet. Im ersten Bildschirm werden wir aufgefordert, die Datenbank anzugeben, mit der eine Verbindung hergestellt werden soll – eine Verbindung mit derselben Northwind-Datenbank mithilfe der NORTHWNDConnectionString
Einstellung von 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, wie die Daten abgefragt werden: über eine Ad-hoc-SQL-Anweisung, eine neue gespeicherte Prozedur oder eine vorhandene gespeicherte Prozedur. Da wir Ad-hoc-SQL-Abfragen in unserem ursprünglichen DAL verwendet haben, verwenden Sie diese Option auch hier.
Abbildung 4: Angeben der abzurufenden Daten mithilfe einer Ad-hoc-SQL-Anweisung (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Geben Sie auf dem folgenden Bildschirm die SQL-Abfrage ein, die zum Abrufen der Produktinformationen verwendet werden soll. Verwenden wir die genaue SQL-Abfrage, die für den Products
TableAdapter aus unserer ursprünglichen DAL verwendet wird, die alle Product
Spalten zusammen mit den Lieferanten- und Kategorienamen des Produkts zurückgibt:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories
WHERE Categories.CategoryID = Products.CategoryID)
as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID)
as SupplierName
FROM Products
Abbildung 5: Verwenden der gleichen SQL-Abfrage aus dem Products
TableAdapter im ursprünglichen DAL (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Bevor Sie auf den nächsten Bildschirm wechseln, klicken Sie auf die Schaltfläche "Erweiterte Optionen". Damit dieser TableAdapter optimistische Parallelitätssteuerelemente verwendet, aktivieren Sie einfach das Kontrollkästchen "Optimistische Parallelität verwenden".
Abbildung 6: Aktivieren des optimistischen Parallelitätssteuerelements durch Überprüfen des CheckBox -Steuerelements "Optimistische Parallelität verwenden" (Klicken Sie, um das Bild mit voller Größe anzuzeigen)
Geben Sie schließlich an, dass "TableAdapter" die Datenzugriffsmuster verwenden soll, die sowohl eine DataTable ausfüllen als auch eine DataTable zurückgeben. Geben Sie außerdem an, dass die direkten DB-Methoden erstellt werden sollen. Ändern Sie den Methodennamen für das "DataTable-Muster zurückgeben" von "GetData" in "GetProducts", um die Namenskonventionen zu spiegeln, die wir in unserem ursprünglichen DAL verwendet haben.
Abbildung 7: Lassen Sie den TableAdapter alle Datenzugriffsmuster nutzen (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Nach Abschluss des Assistenten enthält der DataSet-Designer eine stark typierte Products
DataTable und TableAdapter. Nehmen Sie sich einen Moment Zeit, um die DataTable von Products
zu ProductsOptimisticConcurrency
umzubenennen. Dies können Sie tun, indem Sie mit der rechten Maustaste auf die Titelleiste der Datentabelle klicken und im Kontextmenü "Umbenennen" auswählen.
Abbildung 8: Ein DataTable- und TableAdapter-Objekt wurde dem typierten DataSet hinzugefügt (Klicken, um das Bild in voller Größe anzuzeigen)
Um die Unterschiede zwischen den UPDATE
- und DELETE
-Abfragen zwischen dem ProductsOptimisticConcurrency
-TableAdapter (der optimistische Parallelität verwendet) und dem Products-TableAdapter (der keine optimistische Parallelität verwendet) anzuzeigen, klicken Sie auf den TableAdapter und gehen Sie zum Eigenschaftenfenster. In den DeleteCommand
und UpdateCommand
Eigenschaften CommandText
Untereigenschaften können Sie die tatsächliche SQL-Syntax sehen, die an die Datenbank gesendet wird, wenn die Update- oder löschbezogenen Methoden des DAL aufgerufen werden. Für den ProductsOptimisticConcurrency
TableAdapter wird folgende DELETE
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
Deklaration für den Produkt TableAdapter in unserem ursprünglichen DAL wesentlich einfacher ist:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))
Wie Sie sehen können, enthält die WHERE
Klausel in der DELETE
Anweisung für den TableAdapter, die optimistische Parallelität verwendet, einen Vergleich zwischen den vorhandenen Spaltenwerten der Product
Tabelle und den ursprünglichen Werten zum Zeitpunkt, zu dem die GridView (oder DetailsView oder FormView) zuletzt aufgefüllt wurde. Da alle Felder außer ProductID
, ProductName
und Discontinued
NULL
-Werte enthalten können, werden zusätzliche Parameter und Prüfungen eingeschlossen, um NULL
-Werte in der WHERE
-Klausel ordnungsgemäß zu vergleichen.
Wir werden diesem Tutorial keine weiteren DataTables zum DataSet mit optimistischer Parallelitätsunterstützung hinzufügen, da unsere ASP.NET-Seite nur Produktinformationen aktualisiert und löscht. Wir müssen jedoch weiterhin die GetProductByProductID(productID)
Methode dem ProductsOptimisticConcurrency
TableAdapter hinzufügen.
Klicken Sie dazu mit der rechten Maustaste auf die Titelleiste des TableAdapters (den Bereich rechts über den Namen der Fill
GetProducts
Methoden), und wählen Sie im Kontextmenü "Abfrage hinzufügen" aus. Dadurch wird der Konfigurations-Assistent für TableAdapter-Abfragen gestartet. Wie bei der Erstkonfiguration von TableAdapter können Sie die GetProductByProductID(productID)
Methode mit einer Ad-hoc-SQL-Anweisung erstellen (siehe Abbildung 4). Da die GetProductByProductID(productID)
Methode Informationen zu einem bestimmten Produkt zurückgibt, geben Sie an, dass es sich bei dieser Abfrage um einen SELECT
Abfragetyp handelt, der Zeilen zurückgibt.
Abbildung 9: Markieren des Abfragetyps als "SELECT
,die Zeilen zurückgibt" (Klicken Sie, um das Bild in voller Größe anzuzeigen)
Auf dem nächsten Bildschirm werden wir aufgefordert, die sql-Abfrage zu verwenden, wobei die Standardabfrage von TableAdapter bereits geladen ist. Erweitern Sie die vorhandene Abfrage, um die Klausel WHERE ProductID = @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, 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, um das Bild in voller Größe anzuzeigen)
Nachdem dieser Assistent abgeschlossen ist, enthält "TableAdapter" nun zwei Methoden zum Abrufen von Daten: GetProducts()
, die alle Produkte zurückgibt und GetProductByProductID(productID)
die das angegebene Produkt zurückgibt.
Schritt 3: Erstellen einer Geschäftslogikschicht für die optimistische Concurrency-Enabled DAL
Unsere vorhandene ProductsBLL
Klasse enthält Beispiele für die Verwendung von Batchaktualisierungs- und DB-Direkten Mustern. Die AddProduct
-Methode und die UpdateProduct
-Überladungen verwenden beide das Batchaktualisierungsmuster, wobei eine ProductRow
-Instanz an die Update-Methode des TableAdapter übergeben wird. Die DeleteProduct
Methode verwendet dagegen das direkte DB-Muster und ruft die TableAdapter-Methode Delete(productID)
auf.
Mit dem neuen ProductsOptimisticConcurrency
TableAdapter erfordern die direkten DB-Methoden jetzt, dass auch die ursprünglichen Werte übergeben werden. Beispielsweise erwartet die Delete
-Methode jetzt zehn Eingabeparameter: die ursprünglichen ProductID
, ProductName
, SupplierID
, CategoryID
, QuantityPerUnit
, UnitPrice
, UnitsInStock
, UnitsOnOrder
, ReorderLevel
und Discontinued
. In der WHERE
-Klausel der an die Datenbank gesendeten DELETE
-Anweisung werden die Werte dieser zusätzlichen Eingabeparameter verwendet, wobei der angegebene Datensatz nur gelöscht wird, wenn die aktuellen Werte der Datenbank mit den ursprünglichen übereinstimmen.
Während sich die Methodensignatur für die im Batchaktualisierungsmuster verwendete Methode von TableAdapter zwar nicht geändert hat, hat sich der Code, der erforderlich ist, um die ursprünglichen und neuen Werte aufzuzeichnen, geändert. Anstatt daher die optimistische Parallelitäts-fähige DAL mit unserer vorhandenen ProductsBLL
Klasse zu verwenden, erstellen wir eine neue Business Logic Layer-Klasse für die Arbeit mit unserem neuen DAL.
Fügen Sie dem Ordner ProductsOptimisticConcurrencyBLL
innerhalb des Ordners BLL
eine Klasse namens App_Code
hinzu.
Abbildung 12: Hinzufügen der ProductsOptimisticConcurrencyBLL
Klasse zum BLL-Ordner
Fügen Sie als Nächstes der Klasse den folgenden Code hinzu ProductsOptimisticConcurrencyBLL
:
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 DAL-Methoden bereitstellt. Außerdem vor der Klassendeklaration finden Sie das System.ComponentModel.DataObject
Attribut, das Visual Studio anweist, diese Klasse in die Dropdownliste des ObjectDataSource-Assistenten einzuschließen.
Die Eigenschaft ProductsOptimisticConcurrencyBLL
des Adapter
bietet schnellen Zugriff auf eine Instanz der Klasse ProductsOptimisticConcurrencyTableAdapter
und folgt dem Muster, das in unseren ursprünglichen BLL-Klassen (ProductsBLL
, CategoriesBLL
usw.) verwendet wird. Schließlich ruft die GetProducts()
Methode einfach die DAL-Methode GetProducts()
auf und gibt ein ProductsOptimisticConcurrencyDataTable
Objekt zurück, das mit einer ProductsOptimisticConcurrencyRow
Instanz für jeden Produktdatensatz in der Datenbank gefüllt ist.
Löschen eines Produkts mithilfe des DB Direct-Musters mit optimistischer Parallelität
Bei der Verwendung des direkten Datenbankmusters mit einer DAL, die optimistische Parallelität verwendet, müssen die neuen und ursprünglichen Werte an die Methoden übergeben werden. Zum Löschen gibt es keine neuen Werte, daher müssen nur die ursprünglichen Werte übergeben werden. In unserer BLL müssen wir dann alle ursprünglichen Parameter als Eingabeparameter akzeptieren. Lassen Sie die Methode DeleteProduct
in der Klasse ProductsOptimisticConcurrencyBLL
die DB-Direct-Methode verwenden. Dies bedeutet, dass diese Methode alle zehn Produktdatenfelder als Eingabeparameter übernehmen und diese an die DAL übergeben muss, wie im folgenden Code gezeigt:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public 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 das GridView -Objekt (oder DetailsView oder FormView) geladen wurden – von den Werten in der Datenbank unterscheiden, wenn der Benutzer auf die Schaltfläche "Löschen" klickt, stimmt die WHERE
Klausel nicht mit einem Datenbankdatensatz überein, und es sind keine Datensätze betroffen. Daher wird die Delete
Methode des TableAdapters 0
zurückgeben, und die DeleteProduct
Methode der BLL wird false
zurückgeben.
Aktualisieren eines Produkts mithilfe des Batchaktualisierungsmusters mit optimistischer Parallelität
Wie bereits erwähnt, weist die TableAdapter-Methode für das Batchaktualisierungsmuster Update
dieselbe Methodensignatur auf, unabhängig davon, ob optimistische Parallelität verwendet wird. Nämlich erwartet die Methode eine DataRow, 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 seine DataRow(n) nachverfolgt. Wenn die DAL ihre UPDATE
Anweisung ausgibt, werden die @original_ColumnName
Parameter mit den ursprünglichen Werten von DataRow aufgefüllt, während die @ColumnName
Parameter mit den geänderten Werten von DataRow aufgefüllt werden.
In der ProductsBLL
Klasse (die unsere ursprüngliche, nicht optimistische Parallelität DAL verwendet) bei Verwendung des Batchaktualisierungsmusters zum Aktualisieren von Produktinformationen führt unser Code die folgende Abfolge von Ereignissen aus:
- Lesen Sie die aktuellen Produktinformationen der Datenbank in eine
ProductRow
-Instanz mithilfe der MethodeGetProductByProductID(productID)
des TableAdapters ein. - Zuweisen der neuen Werte zur
ProductRow
Instanz aus Schritt 1 - Rufen Sie die Methode
Update
des TableAdapters auf und übergeben Sie die InstanzProductRow
.
Diese Schrittabfolge unterstützt jedoch keine optimistische Parallelität, da die ProductRow
in Schritt 1 aufgefüllte Datei direkt aus der Datenbank aufgefüllt wird, was bedeutet, dass die ursprünglichen Werte, die von DataRow verwendet werden, diejenigen sind, die derzeit in der Datenbank vorhanden sind, und nicht diejenigen, die am Anfang des Bearbeitungsprozesses an gridView gebunden waren. Stattdessen müssen wir bei Verwendung einer optimistischen Parallelitäts-fähigen DAL die UpdateProduct
Methodenüberladungen ändern, um die folgenden Schritte auszuführen:
- Lesen Sie die aktuellen Produktinformationen der Datenbank in eine
ProductsOptimisticConcurrencyRow
-Instanz mithilfe der MethodeGetProductByProductID(productID)
des TableAdapters ein. - Zuweisen der ursprünglichen Werte zur
ProductsOptimisticConcurrencyRow
Instanz aus Schritt 1 - Rufen Sie die Methode der
ProductsOptimisticConcurrencyRow
InstanzAcceptChanges()
auf, die dataRow anweist, dass die aktuellen Werte die "ursprünglichen" werte sind. - Zuweisen der neuen Werte zur
ProductsOptimisticConcurrencyRow
Instanz - Rufen Sie die Methode
Update
des TableAdapters auf und übergeben Sie die InstanzProductsOptimisticConcurrencyRow
.
Schritt 1 liest alle aktuellen Datenbankwerte für den angegebenen Produktdatensatz vor. Dieser Schritt ist in der UpdateProduct
Überladung überflüssig, die alle Produktspalten aktualisiert (da diese Werte in Schritt 2 überschrieben werden), ist aber unerlässlich für jene Überladungen, bei denen nur eine Teilmenge der Spaltenwerte als Eingabeparameter übergeben wird. Sobald die ursprünglichen Werte der ProductsOptimisticConcurrencyRow
Instanz zugewiesen wurden, wird die AcceptChanges()
Methode aufgerufen, die die aktuellen DataRow-Werte als die ursprünglichen Werte kennzeichnet, die in den @original_ColumnName
Parametern in der UPDATE
Anweisung verwendet werden sollen. Als Nächstes werden die neuen Parameterwerte dem ProductsOptimisticConcurrencyRow
zugewiesen, und schließlich wird die Update
Methode aufgerufen, wobei die DataRow übergeben wird.
Der folgende Code zeigt die UpdateProduct
-Überladung, die alle Produktdatenfelder als Eingabeparameter akzeptiert. Obwohl hier nicht gezeigt, enthält die ProductsOptimisticConcurrencyBLL
klasse, die im Download für dieses Lernprogramm enthalten ist, auch eine UpdateProduct
Überladung, die nur den Namen und den Preis des Produkts als Eingabeparameter akzeptiert.
protected 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 ASP.NET Seite an die BLL-Methoden
Da die DAL und BLL abgeschlossen sind, bleibt nur noch, eine ASP.NET-Seite zu erstellen, die die im System integrierte optimistische Parallelitätslogik nutzen kann. Insbesondere muss das Datenwebsteuerelement (GridView, DetailsView oder FormView) seine ursprünglichen Werte speichern, und die ObjectDataSource muss beide Wertesätze an die Geschäftslogikebene übergeben. Darüber hinaus muss die ASP.NET Seite so konfiguriert werden, dass Parallelitätsverletzungen ordnungsgemäß behandelt werden.
Öffnen Sie zunächst die OptimisticConcurrency.aspx
Seite im EditInsertDelete
Ordner. Fügen Sie dann dem Designer eine GridView hinzu und legen Sie deren ID
Eigenschaft auf ProductsGrid
fest. Wählen Sie im Smarttag von GridView die Option, 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 "ObjectDataSource" (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Wählen Sie im Assistenten die Methoden aus den Dropdownlisten GetProducts
, UpdateProduct
und DeleteProduct
aus. Verwenden Sie für die Methode UpdateProduct die Überladungsversion, die alle Datenfelder des Produkts akzeptiert.
Konfigurieren der Eigenschaften des ObjectDataSource-Steuerelements
Nach Abschluss des Assistenten sollte das deklarative Markup von ObjectDataSource wie folgt aussehen:
<asp:ObjectDataSource ID="ProductsOptimisticConcurrencyDataSource" runat="server"
DeleteMethod="DeleteProduct" OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProducts" TypeName="ProductsOptimisticConcurrencyBLL"
UpdateMethod="UpdateProduct">
<DeleteParameters>
<asp:Parameter Name="original_productID" Type="Int32" />
<asp:Parameter Name="original_productName" Type="String" />
<asp:Parameter Name="original_supplierID" Type="Int32" />
<asp:Parameter Name="original_categoryID" Type="Int32" />
<asp:Parameter Name="original_quantityPerUnit" Type="String" />
<asp:Parameter Name="original_unitPrice" Type="Decimal" />
<asp:Parameter Name="original_unitsInStock" Type="Int16" />
<asp:Parameter Name="original_unitsOnOrder" Type="Int16" />
<asp:Parameter Name="original_reorderLevel" Type="Int16" />
<asp:Parameter Name="original_discontinued" Type="Boolean" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="supplierID" Type="Int32" />
<asp:Parameter Name="categoryID" Type="Int32" />
<asp:Parameter Name="quantityPerUnit" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="unitsInStock" Type="Int16" />
<asp:Parameter Name="unitsOnOrder" Type="Int16" />
<asp:Parameter Name="reorderLevel" Type="Int16" />
<asp:Parameter Name="discontinued" Type="Boolean" />
<asp:Parameter Name="productID" Type="Int32" />
<asp:Parameter Name="original_productName" Type="String" />
<asp:Parameter Name="original_supplierID" Type="Int32" />
<asp:Parameter Name="original_categoryID" Type="Int32" />
<asp:Parameter Name="original_quantityPerUnit" Type="String" />
<asp:Parameter Name="original_unitPrice" Type="Decimal" />
<asp:Parameter Name="original_unitsInStock" Type="Int16" />
<asp:Parameter Name="original_unitsOnOrder" Type="Int16" />
<asp:Parameter Name="original_reorderLevel" Type="Int16" />
<asp:Parameter Name="original_discontinued" Type="Boolean" />
<asp:Parameter Name="original_productID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
Wie Sie sehen können, enthält die DeleteParameters
Auflistung eine Parameter
Instanz für jeden der zehn Eingabeparameter in der Methode der ProductsOptimisticConcurrencyBLL
Klasse DeleteProduct
. Ebenso enthält die UpdateParameters
Auflistung eine Parameter
Instanz für jeden eingabeparameter in UpdateProduct
.
Für diese vorherigen Lernprogramme, die mit der Datenänderung verbunden sind, entfernen wir die Eigenschaft von OldValuesParameterFormatString
ObjectDataSource an diesem Punkt, da diese Eigenschaft angibt, dass die BLL-Methode erwartet, dass die alten (oder ursprünglichen) Werte sowie 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
benannt haben, original_supplierID
usw. können Sie den OldValuesParameterFormatString
Eigenschaftswert als original_{0}
belassen. Wenn die Eingabeparameter der BLL-Methoden jedoch Namen wie old_productName
, old_supplierID
und so weiter hatten, müssten Sie die Eigenschaft OldValuesParameterFormatString
auf old_{0}
aktualisieren.
Es gibt eine endgültige Eigenschaftseinstellung, die vorgenommen werden muss, damit ObjectDataSource die ursprünglichen Werte ordnungsgemäß an die BLL-Methoden übergibt. Die ObjectDataSource verfügt über eine ConflictDetection-Eigenschaft , die einem von zwei Werten zugewiesen werden kann:
-
OverwriteChanges
- der Standardwert; sendet die ursprünglichen Werte nicht an die ursprünglichen Eingabeparameter der BLL-Methoden. -
CompareAllValues
- sendet die ursprünglichen Werte an die BLL-Methoden; Wählen Sie diese Option aus, wenn Sie optimistische Parallelität verwenden.
Nehmen Sie sich einen Moment Zeit, um die ConflictDetection
Eigenschaft auf CompareAllValues
festzulegen.
Konfigurieren der Eigenschaften und Felder von GridView
Da die Eigenschaften des ObjectDataSource korrekt konfiguriert sind, konzentrieren wir uns nun darauf, das GridView einzurichten. Zuerst, da wir möchten, dass die GridView das Bearbeiten und Löschen unterstützt, klicken Sie auf die Kontrollkästchen "Bearbeiten aktivieren" und "Löschen aktivieren" im Smarttag der GridView. Dadurch wird ein CommandField hinzugefügt, dessen ShowEditButton
und ShowDeleteButton
beide auf true
.
Wenn sie an objectDataSource ProductsOptimisticConcurrencyDataSource
gebunden ist, enthält die GridView ein Feld für jedes Der Datenfelder des Produkts. Während eine solche GridView bearbeitet werden kann, ist die Benutzeroberfläche alles andere als akzeptabel. Die CategoryID
und SupplierID
BoundFields werden als TextBoxes gerendert, sodass der Benutzer die entsprechende Kategorie und den entsprechenden Lieferanten als ID-Nummern eingeben muss. Es gibt keine Formatierung für die numerischen Felder und keine Überprüfungssteuerelemente, um sicherzustellen, dass der Name des Produkts angegeben wurde und dass der Einzelpreis, die Lagereinheiten, die bestellten Einheiten und der Meldebestand sowohl korrekte numerische Werte sind als auch größer oder gleich Null sind.
Wie wir in den Lernprogrammen zum Bearbeiten und Einfügen von Schnittstellen zum Bearbeiten und Einfügen von Validierungssteuerelementen und zum Anpassen der Datenänderungsschnittstelle erläutert haben, kann die Benutzeroberfläche angepasst werden, indem die BoundFields durch TemplateFields ersetzt werden. Ich habe dieses GridView und seine Bearbeitungsschnittstelle auf folgende Weise geändert:
- Die
ProductID
,SupplierName
undCategoryName
BoundFields wurden entfernt. - Konvertierte das
ProductName
BoundField in ein TemplateField und fügte ein RequiredFieldValidation-Steuerelement hinzu. - Konvertierte die
CategoryID
undSupplierID
BoundFields in TemplateFields und passte die Bearbeitungsschnittstelle an die Verwendung von DropDownLists anstelle von TextBoxes an. In diesenItemTemplates
TemplateFields werden die DatenfelderCategoryName
undSupplierName
angezeigt. - Konvertierte die
UnitPrice
,UnitsInStock
,UnitsOnOrder
undReorderLevel
BoundFields in TemplateFields und fügte CompareValidator-Steuerelemente hinzu.
Da wir bereits untersucht haben, wie diese Aufgaben in früheren Lernprogrammen ausgeführt werden können, listee ich hier einfach die endgültige deklarative Syntax auf und belassen die Implementierung als Praxis.
<asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsOptimisticConcurrencyDataSource"
OnRowUpdated="ProductsGrid_RowUpdated">
<Columns>
<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
<asp:TemplateField HeaderText="Product" SortExpression="ProductName">
<EditItemTemplate>
<asp:TextBox ID="EditProductName" runat="server"
Text='<%# Bind("ProductName") %>'></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1"
ControlToValidate="EditProductName"
ErrorMessage="You must enter a product name."
runat="server">*</asp:RequiredFieldValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("ProductName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
<EditItemTemplate>
<asp:DropDownList ID="EditCategoryID" runat="server"
DataSourceID="CategoriesDataSource" AppendDataBoundItems="true"
DataTextField="CategoryName" DataValueField="CategoryID"
SelectedValue='<%# Bind("CategoryID") %>'>
<asp:ListItem Value=">(None)</asp:ListItem>
</asp:DropDownList><asp:ObjectDataSource ID="CategoriesDataSource"
runat="server" OldValuesParameterFormatString="original_{0}"
SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server"
Text='<%# Bind("CategoryName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
<EditItemTemplate>
<asp:DropDownList ID="EditSuppliersID" runat="server"
DataSourceID="SuppliersDataSource" AppendDataBoundItems="true"
DataTextField="CompanyName" DataValueField="SupplierID"
SelectedValue='<%# Bind("SupplierID") %>'>
<asp:ListItem Value=">(None)</asp:ListItem>
</asp:DropDownList><asp:ObjectDataSource ID="SuppliersDataSource"
runat="server" OldValuesParameterFormatString="original_{0}"
SelectMethod="GetSuppliers" TypeName="SuppliersBLL">
</asp:ObjectDataSource>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label3" runat="server"
Text='<%# Bind("SupplierName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
SortExpression="QuantityPerUnit" />
<asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
<EditItemTemplate>
<asp:TextBox ID="EditUnitPrice" runat="server"
Text='<%# Bind("UnitPrice", "{0:N2}") %>' Columns="8" />
<asp:CompareValidator ID="CompareValidator1" runat="server"
ControlToValidate="EditUnitPrice"
ErrorMessage="Unit price must be a valid currency value without the
currency symbol and must have a value greater than or equal to zero."
Operator="GreaterThanEqual" Type="Currency"
ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label4" runat="server"
Text='<%# Bind("UnitPrice", "{0:C}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Units In Stock" SortExpression="UnitsInStock">
<EditItemTemplate>
<asp:TextBox ID="EditUnitsInStock" runat="server"
Text='<%# Bind("UnitsInStock") %>' Columns="6"></asp:TextBox>
<asp:CompareValidator ID="CompareValidator2" runat="server"
ControlToValidate="EditUnitsInStock"
ErrorMessage="Units in stock must be a valid number
greater than or equal to zero."
Operator="GreaterThanEqual" Type="Integer"
ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label5" runat="server"
Text='<%# Bind("UnitsInStock", "{0:N0}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Units On Order" SortExpression="UnitsOnOrder">
<EditItemTemplate>
<asp:TextBox ID="EditUnitsOnOrder" runat="server"
Text='<%# Bind("UnitsOnOrder") %>' Columns="6"></asp:TextBox>
<asp:CompareValidator ID="CompareValidator3" runat="server"
ControlToValidate="EditUnitsOnOrder"
ErrorMessage="Units on order must be a valid numeric value
greater than or equal to zero."
Operator="GreaterThanEqual" Type="Integer"
ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label6" runat="server"
Text='<%# Bind("UnitsOnOrder", "{0:N0}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Reorder Level" SortExpression="ReorderLevel">
<EditItemTemplate>
<asp:TextBox ID="EditReorderLevel" runat="server"
Text='<%# Bind("ReorderLevel") %>' Columns="6"></asp:TextBox>
<asp:CompareValidator ID="CompareValidator4" runat="server"
ControlToValidate="EditReorderLevel"
ErrorMessage="Reorder level must be a valid numeric value
greater than or equal to zero."
Operator="GreaterThanEqual" Type="Integer"
ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label7" runat="server"
Text='<%# Bind("ReorderLevel", "{0:N0}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
</Columns>
</asp:GridView>
Wir sind sehr nah an einem vollständig funktionierenden Beispiel. Es gibt jedoch ein paar Nuancen, die sich einschleichen und uns Probleme bereiten. 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 (welche dann an die BLL weitergegeben wird) überträgt, ist es wichtig, dass die EnableViewState
-Eigenschaft von GridView den Wert true
(Standardeinstellung) hat. Wenn Sie den Ansichtszustand deaktivieren, gehen die ursprünglichen Werte beim Postback verloren.
Übergeben der korrekten Originalwerte an die ObjectDataSource
Es gibt ein paar Probleme mit der Konfiguration von GridView. Wenn die Eigenschaft ConflictDetection
von ObjectDataSource auf CompareAllValues
festgelegt ist (wie bei uns), versucht die ObjectDataSource, wenn die Methoden Update()
oder Delete()
von der GridView (oder DetailsView oder FormView) aufgerufen werden, die ursprünglichen Werte der GridView in ihre entsprechenden Parameter
Instanzen zu kopieren. Eine grafische Darstellung dieses Prozesses finden Sie in Abbildung 2.
Insbesondere werden den ursprünglichen Werten der GridView die Werte in den bidirektionalen Datenbindungsanweisungen jedes Mal zugewiesen, wenn die Daten an die GridView gebunden werden. Daher ist es wichtig, dass alle erforderlichen Originalwerte über bidirektionale Datenbindung erfasst und in einem konvertierbaren Format bereitgestellt werden.
Um zu sehen, warum dies wichtig ist, nehmen Sie sich einen Moment Zeit, um unsere Seite in einem Browser zu besuchen. Wie erwartet, listet gridView jedes Produkt mit einer Schaltfläche "Bearbeiten" und "Löschen" in der spalte ganz links auf.
Abbildung 14: Die Produkte werden in einer GridView aufgelistet (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Wenn Sie auf die Schaltfläche "Löschen" für ein beliebiges Produkt klicken, wird ein FormatException
Fehler ausgelöst.
Abbildung 15: Der Versuch, ein beliebiges Produkt zu löschen, führt zu einem FormatException
(Klicken, um das Bild in voller Größe anzuzeigen)
Die FormatException
wird ausgelöst, wenn die ObjectDataSource versucht, den ursprünglichen Wert von UnitPrice
zu lesen. Da das ItemTemplate
als Währung formatiert ist (UnitPrice
), enthält es ein Währungssymbol wie 19,95 $. Der FormatException
tritt auf, wenn die ObjectDataSource versucht, diese Zeichenfolge in ein 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, verwenden Sie statt<%# Bind("UnitPrice", "{0:C}") %>
einfach<%# Bind("UnitPrice") %>
. Der Nachteil ist, dass der Preis nicht mehr formatiert ist. - Zeigen Sie die
UnitPrice
formatierte Währung in derItemTemplate
, verwenden Sie jedoch dasEval
Schlüsselwort, um dies zu erreichen. Erinnern Sie sich daran, dassEval
eine unidirektionale Datenbindung durchführt. Wir müssen denUnitPrice
Wert für die ursprünglichen Werte weiterhin angeben. Daher benötigen wir in derItemTemplate
weiterhin eine bidirektionale Datenbindungsanweisung. Dies lässt sich jedoch in einem Label-Web-Steuerelement platzieren, bei dem dieVisible
-Eigenschaft auffalse
gesetzt ist. Wir könnten das folgende Markup in der ItemTemplate verwenden:
<ItemTemplate>
<asp:Label ID="DummyUnitPrice" runat="server"
Text='<%# Bind("UnitPrice") %>' Visible="false"></asp:Label>
<asp:Label ID="Label4" runat="server"
Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:Label>
</ItemTemplate>
- Entfernen Sie die Währungsformatierung aus dem
ItemTemplate
, indem Sie<%# Bind("UnitPrice") %>
verwenden. Greifen Sie im Ereignishandler vonRowDataBound
GridView programmgesteuert auf das Label-Websteuerelement zu, in dem derUnitPrice
-Wert angezeigt wird, und legen Sie dessenText
-Eigenschaft auf die formatierte Version fest. - Lassen Sie das
UnitPrice
als Währung formatieren. Ersetzen Sie imRowDeleting
Ereignishandler von GridView den vorhandenen ursprünglichenUnitPrice
Wert ($19,95) durch einen tatsächlichen Dezimalwert mitDecimal.Parse
. Wir haben imRowUpdating
Ereignishandler in dem Tutorial: Behandlung von BLL- und DAL-Level-Ausnahmen auf einer ASP.NET-Seite gesehen, wie ähnliche Schritte ausgeführt werden können.
Für mein Beispiel habe ich mich entschieden, den zweiten Ansatz zu verwenden und ein ausgeblendetes Websteuerelement für eine Beschriftung hinzuzufügen, dessen Text
-Eigenschaft bidirektional an den unformatierten UnitPrice
-Wert gebunden ist.
Nachdem Sie dieses Problem behoben haben, versuchen Sie erneut, auf die Schaltfläche "Löschen" für ein beliebiges Produkt zu klicken. Dieses Mal erhalten Sie eine InvalidOperationException
, wenn die ObjectDataSource versucht, die BLL-Methode UpdateProduct
aufzurufen.
Abbildung 16: Die ObjectDataSource Kann eine Methode mit den Eingabeparametern nicht finden, die gesendet werden soll (Klicken Sie, um das Bild in voller Größe anzuzeigen)
Wenn Sie sich die Meldung der Ausnahme ansehen, ist klar, dass die ObjectDataSource eine BLL-Methode DeleteProduct
aufrufen möchte, die die Eingabeparameter original_CategoryName
und original_SupplierName
enthält. Dies liegt daran, dass die ItemTemplate
s für die CategoryID
und SupplierID
TemplateFields derzeit bidirektionale Bindungen mit den CategoryName
und SupplierName
Datenfeldern enthalten. Stattdessen müssen wir Bind
Anweisungen mit den CategoryID
und SupplierID
Datenfeldern einfügen. Ersetzen Sie dazu die vorhandenen Bind-Anweisungen durch Eval
-Anweisungen, und fügen Sie dann ausgeblendete Beschriftungssteuerelemente hinzu, deren Text
-Eigenschaften mithilfe der bidirektionalen Datenbindung an die CategoryID
- und SupplierID
-Datenfelder gebunden sind, 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 können wir jetzt Produktinformationen erfolgreich löschen und bearbeiten! In Schritt 5 sehen wir uns an, wie überprüft wird, ob Parallelitätsverletzungen erkannt werden. Es dauert jedoch einige Minuten, bis Sie versuchen, einige Datensätze zu aktualisieren und zu löschen, um sicherzustellen, dass das Aktualisieren und Löschen für einen einzelnen Benutzer wie erwartet funktioniert.
Schritt 5: Testen der optimistischen Parallelitätsunterstützung
Um zu überprüfen, ob Parallelitätsverletzungen erkannt werden (und nicht dazu führen, dass Daten blind überschrieben werden), müssen wir zwei Browserfenster auf dieser Seite öffnen. Klicken Sie in beiden Browserinstanzen auf die Schaltfläche "Bearbeiten" für "Chai". Ändern Sie dann in nur einem der Browser den Namen in "Chai Tea", und klicken Sie auf "Aktualisieren". Das Update sollte erfolgreich sein und das GridView-Element in den Vorbearbeitungszustand mit "Chai Tea" als neuen Produktnamen zurückgeben.
In der anderen Browserfensterinstanz wird im TextBox-Produktnamen jedoch weiterhin "Chai" angezeigt. Aktualisieren Sie in diesem zweiten Browserfenster die UnitPrice
Datei auf 25.00
. Ohne optimistische Parallelitätsunterstützung würde durch Klicken auf "Update" in der zweiten Browserinstanz der Produktname wieder in "Chai" geändert, wodurch die von der ersten Browserinstanz vorgenommenen Änderungen überschrieben werden. Bei der Verwendung von optimistischer Parallelität führt das Klicken auf die Schaltfläche "Aktualisieren" in der zweiten Browserinstanz jedoch zu einer DBConcurrencyException.
Abbildung 17: Wenn eine Parallelitätsverletzung erkannt wird, wird ein DBConcurrencyException
-Fehler ausgelöst (Klicken Sie, um das Bild in voller Größe anzuzeigen).
Dies DBConcurrencyException
wird nur ausgelöst, wenn das Batchaktualisierungsmuster des DAL verwendet wird. Das direkte DB-Muster löst keine Ausnahme aus, sondern gibt lediglich an, dass keine Zeilen betroffen waren. Um dies zu veranschaulichen, geben Sie die GridView der beiden Browserinstanzen in ihren Zustand vor der Bearbeitung zurück. Klicken Sie als Nächstes in der ersten Browserinstanz auf die Schaltfläche "Bearbeiten", und ändern Sie den Produktnamen von "Chai Tea" wieder in "Chai", und klicken Sie auf "Aktualisieren". Klicken Sie im zweiten Browserfenster auf die Schaltfläche "Löschen" für "Chai".
Beim Klicken auf "Löschen" wird die Seite zurückgesendet, das GridView ruft die Methode Delete()
der ObjectDataSource auf, und die ObjectDataSource ruft die Methode ProductsOptimisticConcurrencyBLL
der DeleteProduct
-Klasse auf und übergibt die ursprünglichen Werte. Der ursprüngliche ProductName
Wert für die zweite Browserinstanz lautet "Chai Tea", der nicht mit dem aktuellen ProductName
Wert in der Datenbank übereinstimmt. Daher wirkt sich die DELETE
an die Datenbank ausgegebene Anweisung auf null Zeilen aus, da keine Datensätze in der Datenbank vorhanden sind, die die WHERE
Klausel erfüllt. Die DeleteProduct
Methode gibt zurück false
, und die Daten von ObjectDataSource werden an das GridView-Objekt zurückgegeben.
Aus Sicht des Endbenutzers hat das Klicken auf die Schaltfläche "Löschen" für "Chai Tea" im zweiten Browserfenster dazu geführt, dass der Bildschirm blinkt und das Produkt nach der Rückkehr noch vorhanden ist, obwohl es jetzt als "Chai" aufgeführt ist (die Änderung des Produktnamens, die von der ersten Browserinstanz vorgenommen wurde). Wenn der Benutzer erneut auf die Schaltfläche "Löschen" klickt, wird der Löschvorgang erfolgreich abgeschlossen, da der ursprüngliche GridView-Wert ("Chai") nun mit dem Wert in der Datenbank übereinstimmt.
In beiden Fällen ist die Benutzererfahrung nicht ideal. Wir möchten dem Benutzer bei Verwendung des Batch-Update-Musters eindeutig nicht die Details der DBConcurrencyException
Ausnahme anzeigen. Und das Verhalten bei der Verwendung des direkten DB-Musters ist etwas verwirrend, da der Benutzerbefehl fehlgeschlagen ist, aber es gab keinen genauen Hinweis darauf, warum.
Um diese beiden Probleme zu beheben, können wir Bezeichnungswebsteuerelemente auf der Seite erstellen, die erläutern, warum ein Fehler beim Aktualisieren oder Löschen aufgetreten ist. Für das Batchaktualisierungsmuster können wir ermitteln, ob im Ereignishandler auf Post-Level-Ebene von GridView eine DBConcurrencyException
Ausnahme aufgetreten ist, dabei wird der Warnhinweis bei Bedarf angezeigt. Für die direkte DB-Methode können wir den Rückgabewert der BLL-Methode (d. h true
. wenn eine Zeile betroffen ist, false
andernfalls) untersuchen und bei Bedarf eine Informationsmeldung anzeigen.
Schritt 6: Hinzufügen von Informationen und Anzeigen dieser Nachrichten bei einer Parallelitätsverletzung
Wenn eine Parallelitätsverletzung auftritt, hängt das angezeigte Verhalten davon ab, ob die Batchaktualisierung des DAL oder das direkte DB-Muster verwendet wurde. Unser Lernprogramm verwendet beide Muster, wobei das Batchaktualisierungsmuster für die Aktualisierung und das direkte DB-Muster zum Löschen verwendet wird. Zu Beginn fügen wir unserer Seite zwei Label-Websteuerelemente hinzu, die erklären, dass eine Parallelitätsverletzung beim Versuch, Daten zu löschen oder zu aktualisieren, aufgetreten ist. Legen Sie die Visible
- und EnableViewState
-Eigenschaften des Bezeichnungssteuerelements auf false
fest. Dadurch werden sie bei jedem Seitenbesuch ausgeblendet, außer bei denjenigen Seitenbesuchen, bei denen ihre Visible
-Eigenschaft programmgesteuert auf true
festgelegt ist.
<asp:Label ID="DeleteConflictMessage" runat="server" Visible="False"
EnableViewState="False" CssClass="Warning"
Text="The record you attempted to delete has been modified by another user
since you last visited this page. Your delete was cancelled to allow
you to review the other user's changes and determine if you want to
continue deleting this record." />
<asp:Label ID="UpdateConflictMessage" runat="server" Visible="False"
EnableViewState="False" CssClass="Warning"
Text="The record you attempted to update has been modified by another user
since you started the update process. Your changes have been replaced
with the current values. Please review the existing values and make
any needed changes." />
Zusätzlich zum Festlegen ihrer Eigenschaften Visible
, EnabledViewState
und Text
habe ich auch die CssClass
Eigenschaft auf Warning
gesetzt, was bewirkt, dass das Label in einer großen, roten, kursiven, fett formatierten Schriftart angezeigt wird. Diese CSS-Klasse Warning
wurde ursprünglich im Lernprogramm Untersuchen der Ereignisse im Zusammenhang mit Einfügen, Aktualisieren und Löschen definiert und zu Styles.css hinzugefügt.
Nach dem Hinzufügen dieser Bezeichnungen sollte der Designer in Visual Studio ähnlich aussehen wie in Abbildung 18.
Abbildung 18: Zwei Bezeichnungssteuerelemente wurden der Seite hinzugefügt (Klicken Sie, um das Bild in voller Größe anzuzeigen)
Mit diesen Label-Websteuerungen sind wir bereit zu untersuchen, wie man feststellen kann, wann eine Parallelitätsverletzung aufgetreten ist. In diesem Fall kann die entsprechende Eigenschaft des Labels auf Visible
gesetzt werden, um die Informationsmeldung anzuzeigen.
Umgang mit Parallelitätsverletzungen beim Aktualisieren
Sehen wir uns zunächst an, wie Parallelitätsverletzungen beim Verwenden des Batchaktualisierungsmusters behandelt werden. Da solche Verstöße mit dem Batchaktualisierungsmuster zu einer DBConcurrencyException
Ausnahme führen, müssen wir der seite ASP.NET Code hinzufügen, um zu bestimmen, ob während des Aktualisierungsprozesses eine DBConcurrencyException
Ausnahme aufgetreten ist. Wenn ja, sollte dem Benutzer eine Meldung angezeigt werden, in der erläutert wird, dass seine Änderungen nicht gespeichert wurden, da ein anderer Benutzer die gleichen Daten zwischen dem Bearbeiten des Datensatzes und dem Klicken auf die Schaltfläche "Aktualisieren" geändert hatte.
Wie wir im Tutorial Behandlung von BLL- und DAL-Level-Ausnahmen in einer ASP.NET-Seite gesehen haben, können solche Ausnahmen in den Ereignishandlern des Web-Datensteuerelements erkannt und unterdrückt werden. Daher müssen wir einen Ereignishandler für das GridView-Ereignis RowUpdated
erstellen, das überprüft, ob eine DBConcurrencyException
Ausnahme ausgelöst wurde. Dieser Ereignishandler wird einen Verweis auf jede Ausnahme übergeben, die während des Aktualisierungsvorgangs ausgelöst wurde, wie im folgenden Ereignishandlercode gezeigt:
protected 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;
}
}
}
Bei einer DBConcurrencyException
Ausnahme zeigt dieser Ereignishandler das UpdateConflictMessage
Label-Steuerelement an und gibt an, dass die Ausnahme behandelt wurde. Wenn bei diesem Code beim Aktualisieren eines Datensatzes eine Parallelitätsverletzung auftritt, gehen die Änderungen des Benutzers verloren, da sie gleichzeitig die Änderungen eines anderen Benutzers überschrieben hätten. Insbesondere wird gridView an den Vorbearbeitungszustand zurückgegeben und an die aktuellen Datenbankdaten gebunden. Dadurch wird die GridView-Zeile mit den Änderungen des anderen Benutzers aktualisiert, die zuvor nicht sichtbar waren. Darüber hinaus wird das UpdateConflictMessage
Bezeichnungssteuerelement dem Benutzer erklären, was gerade passiert ist. Diese Abfolge von Ereignissen ist in Abbildung 19 detailliert dargestellt.
Abbildung 19: Aktualisierungen eines Nutzers gehen angesichts einer Parallelitätsverletzung verloren (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Hinweis
Alternativ können wir das GridView-Objekt nicht an den Vorbearbeitungszustand zurückgeben, sondern das GridView in seinem Bearbeitungszustand belassen, indem die KeepInEditMode
Eigenschaft des übergebenen GridViewUpdatedEventArgs
Objekts auf "true" festgelegt wird. Wenn Sie diesen Ansatz jedoch anwenden, müssen Sie die Daten mit gridView (durch Aufrufen der DataBind()
Methode) neu verknüpfen, damit die Werte des anderen Benutzers in die Bearbeitungsoberfläche geladen werden. Der Code, der für den Download mit diesem Lernprogramm verfügbar ist, enthält diese beiden Codezeilen im RowUpdated
Ereignishandler auskommentiert. Heben Sie einfach die Kommentare dieser Codezeilen auf, damit die GridView nach einer Parallelitätsverletzung weiterhin im Bearbeitungsmodus bleibt.
Reagieren auf Parallelitätsverletzungen beim Löschen
Mit dem direkten DB-Muster wird keine Ausnahme bei einer Parallelitätsverletzung ausgelöst. Stattdessen beeinflusst die Datenbankanweisung einfach keine Datensätze, da die WHERE-Klausel mit keinem Datensatz übereinstimmt. Alle in der BLL erstellten Datenänderungsmethoden wurden so entworfen, dass sie einen booleschen Wert zurückgeben, der angibt, ob sie genau einen Datensatz betroffen haben. Um zu ermitteln, ob beim Löschen eines Datensatzes eine Parallelitätsverletzung aufgetreten ist, können wir den Rückgabewert der BLL-Methode DeleteProduct
untersuchen.
Dann kann der Rückgabewert einer BLL-Methode in den Ereignishandlern auf Ebene nach der Verarbeitung von ObjectDataSource über die Eigenschaft ReturnValue
des an den Ereignishandler übergebenen Objekts ObjectDataSourceStatusEventArgs
untersucht werden. Da wir an der Bestimmung des Rückgabewerts DeleteProduct
aus der Deleted
Methode interessiert sind, müssen wir einen Ereignishandler für das ObjectDataSource-Ereignis erstellen. Die ReturnValue
Eigenschaft ist vom Typ object
und kann sein null
, wenn eine Ausnahme ausgelöst wurde und die Methode unterbrochen wurde, bevor ein Wert zurückgegeben werden konnte. Daher sollten wir zuerst sicherstellen, dass die ReturnValue
Eigenschaft nicht null
und ein boolescher Wert ist. Angenommen, diese Überprüfung ist erfolgreich, zeigen wir das DeleteConflictMessage
-Label an, wenn ReturnValue
gleich false
ist. 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;
}
}
}
Bei einer Parallelitätsverletzung wird die Löschanfrage des Benutzers storniert. Das GridView-Objekt wird aktualisiert und zeigt die Änderungen an, die für diesen Datensatz zwischen dem Laden der Seite und dem Klicken auf die Schaltfläche "Löschen" aufgetreten sind. Wenn eine solche Verletzung auftritt, wird das DeleteConflictMessage
Etikett angezeigt, das erklärt, was gerade passiert ist (siehe Abbildung 20).
Abbildung 20: Die Löschung eines Benutzers wird angesichts einer Parallelitätsverletzung abgebrochen (Klicken Sie hier, um das Bild mit voller Größe anzuzeigen)
Zusammenfassung
Es gibt in jeder Anwendung Möglichkeiten für Parallelitätsverletzungen, die es mehreren gleichzeitigen Benutzern ermöglichen, Daten zu aktualisieren oder zu löschen. Wenn solche Verstöße nicht berücksichtigt werden, gewinnt der Benutzer, der den letzten Schreibvorgang durchführt, sodass die Änderungen des anderen Benutzers überschrieben werden, wenn zwei Benutzer gleichzeitig die gleichen Daten aktualisieren. Alternativ können Entwickler entweder optimistische oder pessimistische Parallelitätssteuerung implementieren. Optimistische Parallelitätssteuerung geht davon aus, dass Parallelitätsverletzungen selten vorkommen, und verhindert einfach einen Aktualisierungs- oder Löschbefehl, der eine Parallelitätsverletzung darstellen würde. Pessimistische Parallelitätssteuerung geht davon aus, dass Parallelitätsverletzungen häufig sind und das einfache Ablehnen des Befehls zum Aktualisieren oder Löschen eines Benutzers nicht akzeptabel ist. Bei pessimistischem Parallelitätssteuerelement schließt das Aktualisieren eines Datensatzes das Sperren des Datensatzes ein, wodurch verhindert wird, dass andere Benutzer den Datensatz ändern oder löschen, während er gesperrt ist.
Das typierte DataSet in .NET bietet Funktionen zur Unterstützung optimistischer Parallelitätssteuerelemente. Insbesondere umfassen die an die Datenbank ausgegebenen UPDATE
Anweisungen alle Spalten der Tabelle, wodurch sichergestellt wird, dass die Aktualisierung oder Löschung nur dann erfolgt, wenn die aktuellen Daten des Datensatzes mit den ursprünglichen Daten übereinstimmen, die der Benutzer beim Ausführen der Aktualisierung oder Löschung vorliegen hatte. Nachdem die DAL für die Unterstützung optimistischer Parallelität konfiguriert wurde, müssen die BLL-Methoden aktualisiert werden. Darüber hinaus muss die ASP.NET Seite, die in die BLL aufruft, so konfiguriert werden, dass die ObjectDataSource die ursprünglichen Werte aus dem Datenwebsteuerelement abruft und sie an die BLL übergibt.
Wie wir in diesem Tutorial gesehen haben, umfasst die Implementierung der optimistischen Parallelitätskontrolle in einer ASP.NET-Webanwendung die Aktualisierung der Datenzugriffsschicht (DAL) und der Geschäftsschicht (BLL) sowie das Hinzufügen von Unterstützung auf der ASP.NET-Seite. Ob diese hinzugefügte Arbeit eine kluge Investition ihrer Zeit und Ihres Aufwands ist, hängt von Ihrer Anwendung ab. Wenn Sie selten gleichzeitige Benutzer haben, die Daten aktualisieren, oder die Daten, die sie aktualisieren, unterscheiden sich voneinander, dann ist die Parallelitätssteuerung kein Schlüsselproblem. Wenn jedoch regelmäßig mehrere Benutzer auf Ihrer Website mit denselben Daten arbeiten, kann die Parallelitätssteuerung dazu beitragen, dass die Aktualisierungen oder Löschungen eines Benutzers nicht unwissentlich die eines anderen überschreiben.
Glückliche Programmierung!
Zum Autor
Scott Mitchell, Autor von sieben ASP/ASP.NET Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft Web Technologies zusammen. Scott arbeitet als unabhängiger Berater, Trainer und Schriftsteller. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Stunden. Er kann bei mitchell@4GuysFromRolla.comerreicht werden.