Implementieren von optimistischer Parallelität (C#)
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 eine optimistische Parallelitätssteuerung, um dieses Risiko zu bewältigen.
Einführung
Für Webanwendungen, die es Benutzern nur ermöglichen, Daten anzuzeigen, oder für Webanwendungen, die nur einen einzelnen Benutzer enthalten, der Daten ändern kann, besteht keine Gefahr, dass zwei gleichzeitige Benutzer versehentlich die Änderungen des anderen überschreiben. Bei Webanwendungen, die es mehreren Benutzern ermöglichen, Daten zu aktualisieren oder zu löschen, besteht jedoch das Potenzial, dass die Änderungen eines Benutzers mit der eines anderen gleichzeitigen Benutzers kollidieren. Wenn zwei Benutzer gleichzeitig einen einzelnen Datensatz bearbeiten, wird ohne Parallelitätsrichtlinie der Benutzer, der seine Änderungen zuletzt committet, die vom ersten vorgenommenen Änderungen überschrieben.
Stellen Sie sich beispielsweise vor, dass zwei Benutzer, Jisun und Sam, beide eine Seite in unserer Anwendung besuchen, die es Besuchern ermöglicht, die Produkte über ein GridView-Steuerelement zu aktualisieren und zu löschen. Beide klicken ungefähr zur gleichen Zeit in der GridView auf die Schaltfläche Bearbeiten. Jisun ändert den Produktnamen in "Chai Tea" und klickt auf die Schaltfläche Aktualisieren. Das Nettoergebnis ist eine UPDATE
Anweisung, die an die Datenbank gesendet wird, die alle aktualisierbaren Felder des Produkts festlegt (obwohl Jisun nur ein Feld aktualisiert hat, ProductName
). Zu diesem Zeitpunkt hat die Datenbank die Werte "Chai Tea", die Kategorie Getränke, der Lieferant Exotic Liquids usw. für dieses spezielle Produkt. Die GridView auf dem Bildschirm von Sam zeigt jedoch weiterhin den Produktnamen in der bearbeitbaren GridView-Zeile als "Chai" an. Einige Sekunden nach dem Commit von Jisuns Änderungen aktualisiert Sam die Kategorie in Condiments und klickt auf Aktualisieren. Dies führt zu einer UPDATE
an die Datenbank gesendeten Anweisung, die den Produktnamen auf "Chai", die auf die CategoryID
entsprechende Getränkekategorie-ID usw. festlegt. Jisuns Änderungen am Produktnamen wurden überschrieben. Abbildung 1 zeigt diese Ereignisreihe grafisch.
Abbildung 1: Wenn zwei Benutzer gleichzeitig einen Datensatz aktualisieren, besteht die Möglichkeit, dass die Änderungen eines Benutzers den anderen überschreiben (Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Wenn zwei Benutzer eine Seite besuchen, kann ein Benutzer gerade dabei sein, einen Datensatz zu aktualisieren, wenn er von einem anderen Benutzer gelöscht wird. Oder zwischen dem Laden einer Seite und dem Klicken auf die Schaltfläche Löschen hat ein anderer Benutzer den Inhalt dieses Datensatzes geändert.
Es stehen drei Strategien zur Parallelitätssteuerung zur Verfügung:
- Nichts tun : Wenn gleichzeitige Benutzer denselben Datensatz ändern, lassen Sie den letzten Commit gewinnen (das Standardverhalten).
- Optimistische Parallelität : Gehen Sie davon aus, dass es zwar hin und wieder zu Parallelitätskonflikten kommen kann, aber in der überwiegenden Mehrheit der Zeit werden solche Konflikte nicht auftreten; Wenn also ein Konflikt auftritt, informieren Sie den Benutzer einfach darüber, dass seine Änderungen nicht gespeichert werden können, da ein anderer Benutzer dieselben Daten geändert hat.
- Pessimistische Parallelität : Angenommen, dass Parallelitätskonflikte üblich sind und Benutzer nicht tolerieren, dass ihre Änderungen aufgrund der gleichzeitigen Aktivität eines anderen Benutzers nicht gespeichert wurden; Wenn ein Benutzer daher mit der Aktualisierung eines Datensatzes beginnt, sperren Sie ihn, sodass andere Benutzer diesen Datensatz nicht bearbeiten oder löschen können, bis der Benutzer seine Änderungen committet.
In allen unseren Tutorials haben wir bisher die Standardstrategie für parallele Parallelitätsauflösung verwendet– nämlich haben wir den letzten Schreibvorgang gewinnen lassen. In diesem Tutorial untersuchen wir, wie Sie die Steuerung für optimistische Parallelität implementieren.
Hinweis
In dieser Tutorialreihe werden keine Beispiele für pessimistische Parallelität behandelt. Pessimistische Parallelität wird selten verwendet, da solche Sperren, wenn sie nicht ordnungsgemäß aufgehoben werden, andere Benutzer daran hindern können, Daten zu aktualisieren. Wenn ein Benutzer beispielsweise einen Datensatz für die Bearbeitung sperrt und dann für den Tag verlässt, bevor er entsperrt wird, kann kein anderer Benutzer diesen Datensatz aktualisieren, bis der ursprüngliche Benutzer zurückgibt und seine Aktualisierung abgeschlossen hat. Daher gibt es in Situationen, in denen pessimistische Parallelität verwendet wird, in der Regel ein Timeout, das bei Erreichen die Sperre aufbricht. Ticketverkaufswebsites, die einen bestimmten Sitzplatz für einen kurzen Zeitraum sperren, während der Benutzer den Bestellvorgang abgeschlossen hat, sind ein Beispiel für eine pessimistische Parallelitätssteuerung.
Schritt 1: Wie optimistische Parallelität implementiert wird
Die optimistische Parallelitätssteuerung funktioniert, indem sichergestellt wird, dass der zu aktualisierende oder gelöschte Datensatz die gleichen Werte aufweist wie beim Starten des Aktualisierungs- oder Löschvorgangs. Wenn Sie beispielsweise in einer bearbeitbaren GridView auf die Schaltfläche Bearbeiten klicken, werden die Werte des Datensatzes aus der Datenbank gelesen und in TextBoxes und anderen Websteuerelementen angezeigt. Diese ursprünglichen Werte werden von GridView gespeichert. Später, nachdem der Benutzer seine Änderungen vorgenommen und auf die Schaltfläche Aktualisieren klickt, werden die ursprünglichen Werte und die neuen Werte an die Geschäftslogikebene und dann nach unten zur Datenzugriffsebene gesendet. Die Datenzugriffsebene muss eine SQL-Anweisung ausgeben, die den Datensatz nur aktualisiert, wenn die ursprünglichen Werte, die der Benutzer mit der Bearbeitung begonnen hat, mit den Werten identisch sind, die sich noch in der Datenbank befinden. Abbildung 2 zeigt diese Ereignissequenz.
Abbildung 2: Damit das Update oder Löschen erfolgreich ist, müssen die ursprünglichen Werte den aktuellen Datenbankwerten entsprechen (Klicken Sie hier, um das bild in voller Größe anzuzeigen).
Es gibt verschiedene Ansätze, um optimistische Parallelität zu implementieren (siehe Peter A. BrombergsOptimistische Parallelitätsaktualisierungslogik für einen kurzen Blick auf eine Reihe von Optionen). Das ADO.NET Typisiertes DataSet bietet eine Implementierung, die nur mit dem Häkchen eines Kontrollkästchens konfiguriert werden kann. Durch aktivieren der optimistischen Parallelität für einen TableAdapter im Typed DataSet werden die TableAdapter- UPDATE
und DELETE
-Anweisungen erweitert, um einen Vergleich aller ursprünglichen Werte in der WHERE
-Klausel aufzunehmen. Die folgende UPDATE
Anweisung aktualisiert beispielsweise den Namen und den Preis eines Produkts nur, wenn die aktuellen Datenbankwerte den Werten entsprechen, die beim Aktualisieren des Datensatzes in GridView ursprünglich abgerufen wurden. Die @ProductName
Parameter und @UnitPrice
enthalten die vom Benutzer eingegebenen neuen Werte, während @original_ProductName
und @original_UnitPrice
die Werte enthalten, die ursprünglich in die GridView geladen wurden, als auf die Schaltfläche Bearbeiten geklickt wurde:
UPDATE Products SET
ProductName = @ProductName,
UnitPrice = @UnitPrice
WHERE
ProductID = @original_ProductID AND
ProductName = @original_ProductName AND
UnitPrice = @original_UnitPrice
Hinweis
Diese UPDATE
Anweisung wurde aus Gründen der Lesbarkeit vereinfacht. In der Praxis wäre die UnitPrice
Überprüfung der WHERE
-Klausel wichtiger, da UnitPrice
s enthalten NULL
kann und überprüft werden kann, wenn NULL = NULL
immer False zurückgegeben wird (stattdessen müssen Sie verwenden IS NULL
).
Zusätzlich zur Verwendung einer anderen zugrunde liegenden UPDATE
Anweisung ändert das Konfigurieren eines TableAdapters für die Verwendung von optimistischer Parallelität auch die Signatur der direkten DB-Methoden. Erinnern Sie sich an unser erstes Tutorial Erstellen einer Datenzugriffsebene, dass direkte DB-Methoden eine Liste skalarer Werte als Eingabeparameter akzeptieren (anstelle eines stark typisierten DataRow- oder DataTable-instance). Bei Verwendung von optimistischer Parallelität enthalten die direkten Update()
DB-Methoden und Delete()
-Methoden auch Eingabeparameter für die ursprünglichen Werte. Darüber hinaus muss der Code in der BLL für die Verwendung des Batchaktualisierungsmusters (die Update()
Methodenüberladungen, die DataRows und DataTables anstelle von skalaren Werten akzeptieren) ebenfalls geändert werden.
Anstatt die TableAdapters der vorhandenen DAL zu erweitern, um eine optimistische Parallelität zu verwenden (was eine Änderung der BLL erforderlich machen würde), erstellen wir stattdessen ein neues typisiertes DataSet mit dem Namen NorthwindOptimisticConcurrency
, dem wir einen Products
TableAdapter hinzufügen, der optimistische Parallelität verwendet. Anschließend erstellen wir eine ProductsOptimisticConcurrencyBLL
Business Logic Layer-Klasse, die über die entsprechenden Änderungen verfügt, um die optimistische Parallelität DAL zu unterstützen. Sobald diese Grundlagen geschaffen wurden, können Wir die ASP.NET-Seite erstellen.
Schritt 2: Erstellen einer Datenzugriffsebene, die eine optimistische Parallelität unterstützt
Um ein neues typisiertes DataSet zu erstellen, klicken Sie mit der rechten Maustaste auf den DAL
Ordner im App_Code
Ordner, und fügen Sie ein neues DataSet mit dem Namen hinzu NorthwindOptimisticConcurrency
. Wie wir im ersten Tutorial gesehen haben, wird dadurch dem typisierten DataSet ein neuer TableAdapter hinzugefügt, wodurch automatisch der TableAdapter-Konfigurations-Assistent gestartet wird. Auf dem ersten Bildschirm werden wir aufgefordert, die Datenbank anzugeben, mit der eine Verbindung hergestellt werden soll: Herstellen einer Verbindung mit derselben Northwind-Datenbank mithilfe der NORTHWNDConnectionString
Einstellung von Web.config
.
Abbildung 3: Herstellen einer Verbindung mit derselben Northwind-Datenbank (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Als Nächstes werden wir aufgefordert, die Daten abzufragen: über eine Ad-hoc-SQL-Anweisung, eine neue gespeicherte Prozedur oder eine vorhandene gespeicherte Prozedur. Da wir Ad-hoc-SQL-Abfragen in unserer ursprünglichen DAL verwendet haben, verwenden Sie diese Option auch hier.
Abbildung 4: Angeben der abzurufenden Daten mithilfe einer Ad-hoc-SQL-Anweisung (Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Geben Sie auf dem folgenden Bildschirm die SQL-Abfrage ein, die zum Abrufen der Produktinformationen verwendet werden soll. Verwenden Wir genau dieselbe SQL-Abfrage, die für den Products
TableAdapter von unserer ursprünglichen DAL verwendet wird, die alle Spalten zusammen mit den Product
Lieferanten- und Kategorienamen des Produkts zurückgibt:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories
WHERE Categories.CategoryID = Products.CategoryID)
as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID)
as SupplierName
FROM Products
Abbildung 5: Verwenden der gleichen SQL-Abfrage aus dem Products
TableAdapter im ursprünglichen DAL (Klicken, um das vollständige Bild anzuzeigen)
Bevor Sie zum nächsten Bildschirm wechseln, klicken Sie auf die Schaltfläche Erweiterte Optionen. Damit dieser TableAdapter eine optimistische Parallelitätssteuerung verwendet, aktivieren Sie einfach das Kontrollkästchen "Optimistische Parallelität verwenden".
Abbildung 6: Aktivieren Sie die Kontrolle für die optimistische Parallelität, indem Sie das CheckBox "Optimistische Parallelität verwenden" überprüfen (Klicken Sie hier, um das vollständige Bild anzuzeigen)
Geben Sie schließlich an, dass der TableAdapter die Datenzugriffsmuster verwenden soll, die sowohl eine DataTable ausfüllen als auch eine DataTable zurückgeben. Geben Sie außerdem an, dass die direkten DB-Methoden erstellt werden sollen. Ändern Sie den Methodennamen für das DataTable-Rückgabemuster von GetData in GetProducts, um die Benennungskonventionen zu Spiegel, die wir in unserer ursprünglichen DAL verwendet haben.
Abbildung 7: Verwenden sie alle Datenzugriffsmuster für den TableAdapter (Klicken Sie hier, um das bild in voller Größe anzuzeigen)
Nach Abschluss des Assistenten enthält das DataSet-Designer eine stark typisierte Products
DataTable und TableAdapter. Nehmen Sie sich einen Moment Zeit, um die DataTable von Products
in umzubenennen ProductsOptimisticConcurrency
, was Sie tun können, indem Sie mit der rechten Maustaste auf die Titelleiste der DataTable klicken und im Kontextmenü Umbenennen auswählen.
Abbildung 8: DataTable und TableAdapter wurden dem typisierten DataSet hinzugefügt (klicken, um das bild in voller Größe anzuzeigen)
Um die Unterschiede zwischen den UPDATE
Abfragen und DELETE
zwischen dem ProductsOptimisticConcurrency
TableAdapter (der eine optimistische Parallelität verwendet) und dem TableAdapter -Produkt (was nicht der Fall ist) anzuzeigen, klicken Sie auf den TableAdapter, und wechseln Sie zum Eigenschaftenfenster. In den Untereigenschaften und UpdateCommand
der DeleteCommand
Eigenschaften CommandText
sehen Sie die tatsächliche SQL-Syntax, die an die Datenbank gesendet wird, wenn die Aktualisierungs- oder Löschmethoden der DAL aufgerufen werden. Für den ProductsOptimisticConcurrency
TableAdapter lautet die DELETE
verwendete Anweisung:
DELETE FROM [Products]
WHERE (([ProductID] = @Original_ProductID)
AND ([ProductName] = @Original_ProductName)
AND ((@IsNull_SupplierID = 1 AND [SupplierID] IS NULL)
OR ([SupplierID] = @Original_SupplierID))
AND ((@IsNull_CategoryID = 1 AND [CategoryID] IS NULL)
OR ([CategoryID] = @Original_CategoryID))
AND ((@IsNull_QuantityPerUnit = 1 AND [QuantityPerUnit] IS NULL)
OR ([QuantityPerUnit] = @Original_QuantityPerUnit))
AND ((@IsNull_UnitPrice = 1 AND [UnitPrice] IS NULL)
OR ([UnitPrice] = @Original_UnitPrice))
AND ((@IsNull_UnitsInStock = 1 AND [UnitsInStock] IS NULL)
OR ([UnitsInStock] = @Original_UnitsInStock))
AND ((@IsNull_UnitsOnOrder = 1 AND [UnitsOnOrder] IS NULL)
OR ([UnitsOnOrder] = @Original_UnitsOnOrder))
AND ((@IsNull_ReorderLevel = 1 AND [ReorderLevel] IS NULL)
OR ([ReorderLevel] = @Original_ReorderLevel))
AND ([Discontinued] = @Original_Discontinued))
Während die DELETE
Aussage für den Product TableAdapter in unserem ursprünglichen DAL viel einfacher ist:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))
Wie Sie sehen können, enthält die WHERE
-Klausel in der DELETE
-Anweisung für den TableAdapter, die eine optimistische Parallelität verwendet, einen Vergleich zwischen den vorhandenen Spaltenwerten der Product
Tabelle und den ursprünglichen Werten zum Zeitpunkt der letzten Auffüllung von GridView (oder DetailsView oder FormView). Da alle Felder außer , und Werte enthalten NULL
können, werden zusätzliche Parameter und Überprüfungen enthalten, um Werte in der WHERE
-Klausel ordnungsgemäß zu vergleichenNULL
.Discontinued
ProductName
ProductID
Wir werden für dieses Tutorial keine zusätzlichen DataTables zum optimistischen Parallelitäts-fähigen DataSet hinzufügen, da unsere ASP.NET Seite nur Das Aktualisieren und Löschen von Produktinformationen enthält. Dennoch müssen wir die GetProductByProductID(productID)
-Methode dem ProductsOptimisticConcurrency
TableAdapter hinzufügen.
Klicken Sie hierzu mit der rechten Maustaste auf die Titelleiste des TableAdapters (den Bereich direkt über dem Namen und GetProducts
den Fill
Methodennamen), und wählen Sie im Kontextmenü Abfrage hinzufügen aus. Dadurch wird der TableAdapter-Abfragekonfigurations-Assistent gestartet. Wie bei der Anfänglichkonfiguration unseres TableAdapter können Sie die GetProductByProductID(productID)
Methode mithilfe einer Ad-hoc-SQL-Anweisung erstellen (siehe Abbildung 4). Da die GetProductByProductID(productID)
-Methode Informationen zu einem bestimmten Produkt zurückgibt, geben Sie an, dass diese Abfrage ein SELECT
Abfragetyp ist, der Zeilen zurückgibt.
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:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindOptimisticConcurrencyTableAdapters;
[System.ComponentModel.DataObject]
public class ProductsOptimisticConcurrencyBLL
{
private ProductsOptimisticConcurrencyTableAdapter _productsAdapter = null;
protected ProductsOptimisticConcurrencyTableAdapter Adapter
{
get
{
if (_productsAdapter == null)
_productsAdapter = new ProductsOptimisticConcurrencyTableAdapter();
return _productsAdapter;
}
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, true)]
public NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable GetProducts()
{
return Adapter.GetProducts();
}
}
Beachten Sie die using-Anweisung NorthwindOptimisticConcurrencyTableAdapters
über dem Anfang der Klassendeklaration. Der NorthwindOptimisticConcurrencyTableAdapters
-Namespace enthält die ProductsOptimisticConcurrencyTableAdapter
-Klasse, die die Methoden des DAL bereitstellt. Außerdem finden Sie vor der Klassendeklaration das System.ComponentModel.DataObject
-Attribut, das Visual Studio anweist, diese Klasse in die Dropdownliste des ObjectDataSource-Assistenten einzuschließen.
Die ProductsOptimisticConcurrencyBLL
-Eigenschaft von Adapter
bietet schnellen Zugriff auf eine instance der ProductsOptimisticConcurrencyTableAdapter
-Klasse und folgt dem Muster, das in unseren ursprünglichen BLL-Klassen (ProductsBLL
, 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 bool DeleteProduct
(int original_productID, string original_productName,
int? original_supplierID, int? original_categoryID,
string original_quantityPerUnit, decimal? original_unitPrice,
short? original_unitsInStock, short? original_unitsOnOrder,
short? original_reorderLevel, bool original_discontinued)
{
int rowsAffected = Adapter.Delete(original_productID,
original_productName,
original_supplierID,
original_categoryID,
original_quantityPerUnit,
original_unitPrice,
original_unitsInStock,
original_unitsOnOrder,
original_reorderLevel,
original_discontinued);
// Return true if precisely one row was deleted, otherwise false
return rowsAffected == 1;
}
Wenn sich die ursprünglichen Werte – die Werte, die zuletzt in GridView (oder DetailsView oder FormView) geladen wurden , von den Werten in der Datenbank unterscheiden, wenn der Benutzer auf die Schaltfläche Löschen klickt, stimmt die WHERE
Klausel nicht mit einem Datenbankdatensatz überein, und es sind keine Datensätze betroffen. Daher gibt die TableAdapter-Methode Delete
zurück 0
, und die BLL-Methode DeleteProduct
gibt zurück false
.
Aktualisieren eines Produkts mithilfe des Batchupdatemusters mit optimistischer Parallelität
Wie bereits erwähnt, weist die TableAdapter-Methode für das Batchaktualisierungsmuster Update
die gleiche Methodensignatur auf, unabhängig davon, ob optimistische Parallelität verwendet wird. Die -Methode erwartet nämlich Update
ein DataRow-Objekt, ein Array von DataRows, eine DataTable oder ein typisiertes DataSet. Es gibt keine zusätzlichen Eingabeparameter zum Angeben der ursprünglichen Werte. Dies ist möglich, da die DataTable die ursprünglichen und geänderten Werte für ihre DataRow(s) nachverfolgt. Wenn die DAL ihre UPDATE
Anweisung ausgibt, werden die @original_ColumnName
Parameter mit den ursprünglichen Werten von DataRow aufgefüllt, während die @ColumnName
Parameter mit den geänderten DataRow-Werten aufgefüllt werden.
In der ProductsBLL
-Klasse (die unsere ursprüngliche, nicht optimistische Parallelität DAL verwendet) führt der Code bei Verwendung des Batchaktualisierungsmusters zum Aktualisieren von Produktinformationen die folgende Abfolge von Ereignissen aus:
- 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 void AssignAllProductValues
(NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,
string productName, int? supplierID, int? categoryID, string quantityPerUnit,
decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
short? reorderLevel, bool discontinued)
{
product.ProductName = productName;
if (supplierID == null)
product.SetSupplierIDNull();
else
product.SupplierID = supplierID.Value;
if (categoryID == null)
product.SetCategoryIDNull();
else
product.CategoryID = categoryID.Value;
if (quantityPerUnit == null)
product.SetQuantityPerUnitNull();
else
product.QuantityPerUnit = quantityPerUnit;
if (unitPrice == null)
product.SetUnitPriceNull();
else
product.UnitPrice = unitPrice.Value;
if (unitsInStock == null)
product.SetUnitsInStockNull();
else
product.UnitsInStock = unitsInStock.Value;
if (unitsOnOrder == null)
product.SetUnitsOnOrderNull();
else
product.UnitsOnOrder = unitsOnOrder.Value;
if (reorderLevel == null)
product.SetReorderLevelNull();
else
product.ReorderLevel = reorderLevel.Value;
product.Discontinued = discontinued;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateProduct(
// new parameter values
string productName, int? supplierID, int? categoryID, string quantityPerUnit,
decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
short? reorderLevel, bool discontinued, int productID,
// original parameter values
string original_productName, int? original_supplierID, int? original_categoryID,
string original_quantityPerUnit, decimal? original_unitPrice,
short? original_unitsInStock, short? original_unitsOnOrder,
short? original_reorderLevel, bool original_discontinued,
int original_productID)
{
// STEP 1: Read in the current database product information
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable products =
Adapter.GetProductByProductID(original_productID);
if (products.Count == 0)
// no matching record found, return false
return false;
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product = products[0];
// STEP 2: Assign the original values to the product instance
AssignAllProductValues(product, original_productName, original_supplierID,
original_categoryID, original_quantityPerUnit, original_unitPrice,
original_unitsInStock, original_unitsOnOrder, original_reorderLevel,
original_discontinued);
// STEP 3: Accept the changes
product.AcceptChanges();
// STEP 4: Assign the new values to the product instance
AssignAllProductValues(product, productName, supplierID, categoryID,
quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel,
discontinued);
// STEP 5: Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
Schritt 4: Übergeben der ursprünglichen und neuen Werte von der seite ASP.NET an die BLL-Methoden
Wenn DAL und BLL abgeschlossen sind, müssen Sie nur noch eine ASP.NET Seite erstellen, die die in das System integrierte Logik für optimistische Parallelität nutzen kann. Insbesondere muss das Datenwebsteuerelement (GridView, DetailsView oder FormView) seine ursprünglichen Werte speichern, und objectDataSource muss beide Wertesätze an die Geschäftslogikebene übergeben. Darüber hinaus muss die seite ASP.NET so konfiguriert werden, dass Verstöße gegen Parallelität ordnungsgemäß behandelt werden.
Öffnen Sie zunächst die OptimisticConcurrency.aspx
Seite im EditInsertDelete
Ordner, fügen Sie dem Designer eine GridView hinzu, und legen Sie die ID
-Eigenschaft auf festProductsGrid
. Wählen Sie über das Smarttag von GridView aus, um eine neue ObjectDataSource mit dem Namen 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 void ProductsGrid_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
if (e.Exception != null && e.Exception.InnerException != null)
{
if (e.Exception.InnerException is System.Data.DBConcurrencyException)
{
// Display the warning message and note that the
// exception has been handled...
UpdateConflictMessage.Visible = true;
e.ExceptionHandled = true;
}
}
}
Im Angesicht einer DBConcurrencyException
Ausnahme zeigt dieser Ereignishandler das UpdateConflictMessage
Label-Steuerelement an und gibt an, dass die Ausnahme behandelt wurde. Wenn dieser Code vorhanden ist, gehen die Änderungen des Benutzers verloren, wenn beim Aktualisieren eines Datensatzes ein Parallelitätsverstoß auftritt, da er gleichzeitig die Änderungen eines anderen Benutzers überschrieben hätte. Insbesondere wird gridView in den Zustand vor der Bearbeitung zurückgegeben und an die aktuellen Datenbankdaten gebunden. Dadurch wird die GridView-Zeile mit den Änderungen des anderen Benutzers aktualisiert, die zuvor nicht sichtbar waren. Darüber hinaus erklärt das UpdateConflictMessage
Label-Steuerelement dem Benutzer, was gerade passiert ist. Diese Abfolge von Ereignissen ist in Abbildung 19 beschrieben.
Abbildung 19: Updates eines Benutzers im Gesicht einer Parallelitätsverletzung verloren (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Hinweis
Alternativ können wir gridView nicht in den Zustand vor der Bearbeitung zurückversetzen, sondern das GridView-Objekt in seinem Bearbeitungszustand belassen, indem wir die KeepInEditMode
-Eigenschaft des übergebenen GridViewUpdatedEventArgs
Objekts auf true festlegen. Wenn Sie diesen Ansatz verwenden, sollten Sie jedoch sicher sein, dass die Daten (durch Aufrufen DataBind()
der -Methode) neu an gridView gebunden werden, damit die Werte des anderen Benutzers in die Bearbeitungsoberfläche geladen werden. Der Code, der in diesem Tutorial heruntergeladen werden kann, enthält diese beiden Codezeilen im RowUpdated
Ereignishandler auskommentiert. Heben Sie einfach die Auskommentierung dieser Codezeilen auf, damit gridView nach einer Parallelitätsverletzung im Bearbeitungsmodus verbleibt.
Reagieren auf Parallelitätsverletzungen beim Löschen
Mit dem direkten Db-Muster wird keine Ausnahme aufgrund eines Parallelitätsverstoßes ausgelöst. Stattdessen wirkt sich die Datenbank-Anweisung einfach auf keine Datensätze aus, da die WHERE-Klausel mit keinem Datensatz übereinstimmt. Alle in der BLL erstellten Datenänderungsmethoden wurden so konzipiert, dass sie einen booleschen Wert zurückgeben, der angibt, ob sie genau einen Datensatz beeinflusst haben oder nicht. Um zu ermitteln, ob beim Löschen eines Datensatzes ein Parallelitätsverstoß DeleteProduct
aufgetreten ist, können wir daher den Rückgabewert der BLL-Methode untersuchen.
Der Rückgabewert für eine BLL-Methode kann in den Ereignishandlern nach der Ebene von ObjectDataSource über die ReturnValue
-Eigenschaft des ObjectDataSourceStatusEventArgs
an den Ereignishandler übergebenen Objekts untersucht werden. Da wir daran interessiert sind, den Rückgabewert aus der DeleteProduct
-Methode zu bestimmen, müssen wir einen Ereignishandler für das ObjectDataSource-Ereignis Deleted
erstellen. Die ReturnValue
-Eigenschaft ist vom Typ object
und kann sein null
, wenn eine Ausnahme ausgelöst wurde und die Methode unterbrochen wurde, bevor sie einen Wert zurückgeben konnte. Daher sollten wir zunächst sicherstellen, dass die ReturnValue
Eigenschaft nicht null
und ein boolescher Wert ist. Wenn diese Überprüfung erfolgreich ist, wird das DeleteConflictMessage
Label-Steuerelement angezeigt, wenn der ReturnValue
ist false
. Dies kann mithilfe des folgenden Codes erreicht werden:
protected void ProductsOptimisticConcurrencyDataSource_Deleted(
object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.ReturnValue != null && e.ReturnValue is bool)
{
bool deleteReturnValue = (bool)e.ReturnValue;
if (deleteReturnValue == false)
{
// No row was deleted, display the warning message
DeleteConflictMessage.Visible = true;
}
}
}
Angesichts einer Parallelitätsverletzung wird die Löschanforderung des Benutzers abgebrochen. GridView wird aktualisiert und zeigt die Änderungen an, die zwischen dem Laden der Seite und dem Klicken auf die Schaltfläche Löschen für diesen Datensatz aufgetreten sind. Wenn ein solcher Verstoß auftritt, wird die DeleteConflictMessage
Bezeichnung angezeigt, die erklärt, was gerade passiert ist (siehe Abbildung 20).
Abbildung 20: Das Löschen eines Benutzers wird im Angesicht einer Parallelitätsverletzung abgebrochen (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Zusammenfassung
Möglichkeiten für Parallelitätsverletzungen bestehen in jeder Anwendung, die es mehreren gleichzeitigen Benutzern ermöglicht, Daten zu aktualisieren oder zu löschen. Wenn solche Verstöße nicht berücksichtigt werden, wenn zwei Benutzer gleichzeitig die gleichen Daten aktualisieren, die beim letzten Schreibvorgang "gewinnt", überschreiben sie die Änderungen des anderen Benutzers. Alternativ können Entwickler entweder eine optimistische oder eine pessimistische Parallelitätssteuerung implementieren. Bei der Kontrolle der optimistischen Parallelität wird davon ausgegangen, dass Parallelitätsverletzungen selten auftreten, und ein Update- oder Löschbefehl, der eine Parallelitätsverletzung darstellen würde, einfach nicht zulässig. Bei der Pessimistischen Parallelitätssteuerung wird davon ausgegangen, dass Parallelitätsverletzungen häufig auftreten, und das einfache Ablehnen des Aktualisierungs- oder Löschbefehls eines Benutzers ist nicht akzeptabel. Bei der pessimistischen Parallelitätssteuerung umfasst das Aktualisieren eines Datensatzes eine Sperrung, wodurch verhindert wird, dass andere Benutzer den Datensatz ändern oder löschen, während er gesperrt ist.
Das typisierte DataSet in .NET bietet Funktionen zur Unterstützung der Steuerung der optimistischen Parallelität. Insbesondere enthalten die UPDATE
für die Datenbank ausgegebenen Anweisungen und DELETE
alle Spalten der Tabelle, wodurch sichergestellt wird, dass das Aktualisieren oder Löschen nur erfolgt, wenn die aktuellen Daten des Datensatzes mit den ursprünglichen Daten übereinstimmen, die der Benutzer beim Aktualisieren oder Löschen hatte. Nachdem die DAL so konfiguriert wurde, dass sie optimistische Parallelität unterstützt, müssen die BLL-Methoden aktualisiert werden. Darüber hinaus muss die ASP.NET Seite, die nach unten in die BLL aufruft, so konfiguriert werden, dass ObjectDataSource die ursprünglichen Werte aus dem Datenwebsteuerelement abruft und an die BLL übergibt.
Wie in diesem Tutorial gezeigt, umfasst die Implementierung der Steuerung der optimistischen Parallelität in einer ASP.NET Webanwendung das Aktualisieren von DAL und BLL und das Hinzufügen von Unterstützung auf der Seite ASP.NET. Ob diese zusätzliche Arbeit eine sinnvolle Investition Ihrer Zeit und Ihres Aufwands ist oder nicht, hängt von Ihrer Anwendung ab. Wenn Sie nur selten gleichzeitige Benutzer haben, die Daten aktualisieren oder sich die von ihnen aktualisierten Daten voneinander unterscheiden, ist die Parallelitätssteuerung kein wichtiges Problem. Wenn jedoch routinemäßig mehrere Benutzer auf Ihrer Website mit denselben Daten arbeiten, kann die Parallelitätssteuerung dazu beitragen, zu verhindern, dass die Aktualisierungen oder Löschungen eines Benutzers unwissentlich die eines anderen Benutzers überschreiben.
Viel Spaß beim Programmieren!
Zum Autor
Scott Mitchell, Autor von sieben ASP/ASP.NET-Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft-Webtechnologien. Scott arbeitet als unabhängiger Berater, Trainer und Autor. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Er kann unter mitchell@4GuysFromRolla.comoder über seinen Blog erreicht werden, der unter http://ScottOnWriting.NETzu finden ist.
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für