Implementowanie optymistycznej współbieżności (VB)
W przypadku aplikacji internetowej, która umożliwia wielu użytkownikom edytowanie danych, istnieje ryzyko, że dwóch użytkowników może jednocześnie edytować te same dane. W tym samouczku zaimplementujemy optymistyczną kontrolę współbieżności, aby obsłużyć to ryzyko.
Wprowadzenie
W przypadku aplikacji internetowych, które zezwalają tylko użytkownikom na wyświetlanie danych, lub dla tych, którzy zawierają tylko jednego użytkownika, który może modyfikować dane, nie ma zagrożenia dla dwóch równoczesnych użytkowników przypadkowo zastępujących zmiany. W przypadku aplikacji internetowych, które umożliwiają wielu użytkownikom aktualizowanie lub usuwanie danych, istnieje jednak możliwość wystąpienia modyfikacji jednego użytkownika w celu starcia się z innymi współbieżnymi użytkownikami. Bez żadnych zasad współbieżności, gdy dwóch użytkowników jednocześnie edytuje pojedynczy rekord, użytkownik, który zatwierdza jej zmiany, ostatnio zastąpi zmiany wprowadzone przez pierwszy.
Załóżmy na przykład, że dwóch użytkowników, Jisun i Sam, odwiedzało stronę w naszej aplikacji, która zezwalała odwiedzającym na aktualizowanie i usuwanie produktów za pomocą kontrolki GridView. Oba przyciski kliknij przycisk Edytuj w elemecie GridView mniej więcej w tym samym czasie. Jisun zmienia nazwę produktu na "Chai Tea" i klika przycisk Aktualizuj. Wynikiem net jest UPDATE
instrukcja, która jest wysyłana do bazy danych, która ustawia wszystkie pola z możliwością aktualizacji produktu (mimo że Jisun zaktualizował tylko jedno pole, ProductName
). W tym momencie baza danych ma wartości "Chai Tea", kategorię Napoje, dostawca Egzotyczne płyny itd. dla tego konkretnego produktu. Jednak na ekranie Narzędzia GridView na ekranie Sam nadal wyświetlana jest nazwa produktu w edytowalnym wierszu GridView jako "Chai". Kilka sekund po zatwierdzeniu zmian jisun sam aktualizuje kategorię do condiments i klika pozycję Aktualizuj. UPDATE
Spowoduje to wysłanie instrukcji do bazy danych, która ustawia nazwę produktu na "Chai"CategoryID
, na odpowiadający identyfikator kategorii Napoje itd. Zmiany jisun w nazwie produktu zostały zastąpione. Rysunek 1 graficznie przedstawia tę serię zdarzeń.
Rysunek 1. Gdy dwóch użytkowników jednocześnie zaktualizuje rekord, istnieje możliwość zastąpienia zmian jednego użytkownika w celu zastąpienia pozostałych (kliknij, aby wyświetlić obraz pełnowymiarowy)
Podobnie, gdy dwóch użytkowników odwiedza stronę, jeden użytkownik może znajdować się w środku aktualizowania rekordu po usunięciu go przez innego użytkownika. Lub między, gdy użytkownik ładuje stronę i po kliknięciu przycisku Usuń, inny użytkownik mógł zmodyfikować zawartość tego rekordu.
Dostępne są trzy strategie kontroli współbieżności :
- Nie rób nic — jeśli równoczesni użytkownicy modyfikują ten sam rekord, niech ostatnie zatwierdzenie wygra (zachowanie domyślne)
- Optymistyczna współbieżność - załóżmy, że chociaż w tej chwili mogą występować konflikty współbieżności, zdecydowana większość czasu takich konfliktów nie wystąpi; w związku z tym, jeśli wystąpi konflikt, po prostu poinformuj użytkownika, że nie można zapisać ich zmian, ponieważ inny użytkownik zmodyfikował te same dane
- Pesymistyczne współbieżność — załóżmy, że konflikty współbieżności są powszechne i że użytkownicy nie będą tolerować wprowadzania zmian, nie zostały zapisane z powodu współbieżnej aktywności innego użytkownika; w związku z tym, gdy jeden użytkownik rozpocznie aktualizowanie rekordu, zablokuj go, uniemożliwiając innym użytkownikom edytowanie lub usuwanie tego rekordu, dopóki użytkownik nie zatwierdzi ich modyfikacji
Wszystkie nasze samouczki do tej pory używały domyślnej strategii rozpoznawania współbieżności — a mianowicie pozwoliliśmy ostatniemu zapisowi wygrać. W tym samouczku sprawdzimy, jak zaimplementować optymistyczną kontrolę współbieżności.
Uwaga
W tej serii samouczków nie przyjrzymy się pesymistycznym przykładom współbieżności. Pesymistyczna współbieżność jest rzadko używana, ponieważ takie blokady, jeśli nie zostały prawidłowo wycofane, mogą uniemożliwić innym użytkownikom aktualizowanie danych. Jeśli na przykład użytkownik zablokuje rekord do edycji, a następnie opuści dzień przed jego odblokowaniem, żaden inny użytkownik nie będzie mógł zaktualizować tego rekordu, dopóki oryginalny użytkownik nie zwróci i ukończy jego aktualizację. W związku z tym w sytuacjach, w których jest używana pesymistyczna współbieżność, zwykle występuje limit czasu, który w przypadku osiągnięcia tego problemu anuluje blokadę. Witryny internetowe sprzedaży biletów, które blokują konkretną lokalizację siedzenia przez krótki okres, gdy użytkownik ukończy proces zamówienia, jest przykładem pesymistycznej kontroli współbieżności.
Krok 1. Sprawdzanie, jak zaimplementowano optymistyczną współbieżność
Optymistyczna kontrola współbieżności działa, upewniając się, że rekord aktualizowany lub usuwany ma te same wartości co podczas uruchamiania procesu aktualizowania lub usuwania. Na przykład po kliknięciu przycisku Edytuj w edytowalnym elemecie GridView wartości rekordu są odczytywane z bazy danych i wyświetlane w polach TextBoxes i innych kontrolkach sieci Web. Te oryginalne wartości są zapisywane przez element GridView. Później, gdy użytkownik wprowadza zmiany i klika przycisk Aktualizuj, oryginalne wartości oraz nowe wartości są wysyłane do warstwy logiki biznesowej, a następnie w dół do warstwy dostępu do danych. Warstwa dostępu do danych musi wydać instrukcję SQL, która zaktualizuje rekord tylko wtedy, gdy oryginalne wartości, które użytkownik zaczął edytować, są identyczne z wartościami w bazie danych. Rysunek 2 przedstawia tę sekwencję zdarzeń.
Rysunek 2. Aby aktualizacja lub usunięcie zakończyło się powodzeniem, oryginalne wartości muszą być równe bieżącym wartościom bazy danych (kliknij, aby wyświetlić obraz o pełnym rozmiarze)
Istnieją różne podejścia do implementowania optymistycznej współbieżności (zobacz Optymistyczna logika aktualizacji współbieżnościPetera A. Bromberga, aby zapoznać się z kilkoma opcjami). Zestaw danych typu ADO.NET zapewnia jedną implementację, którą można skonfigurować tylko za pomocą znacznika pola wyboru. Włączenie optymistycznej współbieżności dla klasy TableAdapter w zestawie danych typed rozszerza instrukcje i TableAdapterUPDATE
, aby uwzględnić porównanie wszystkich oryginalnych wartości w klauzuli WHERE
.DELETE
Poniższa UPDATE
instrukcja aktualizuje na przykład nazwę i cenę produktu tylko wtedy, gdy bieżące wartości bazy danych są równe wartościom, które zostały pierwotnie pobrane podczas aktualizowania rekordu w elemencie GridView. Parametry @ProductName
i @UnitPrice
zawierają nowe wartości wprowadzone przez użytkownika, natomiast @original_ProductName
i @original_UnitPrice
zawierają wartości, które zostały pierwotnie załadowane do kontrolki GridView po kliknięciu przycisku Edytuj:
UPDATE Products SET
ProductName = @ProductName,
UnitPrice = @UnitPrice
WHERE
ProductID = @original_ProductID AND
ProductName = @original_ProductName AND
UnitPrice = @original_UnitPrice
Uwaga
Ta UPDATE
instrukcja została uproszczona w celu czytelności. W praktyce zaewidencjonowanie UnitPrice
klauzuli WHERE
byłoby bardziej zaangażowane, ponieważ UnitPrice
może zawierać NULL
wartości s i sprawdzać, czy NULL = NULL
zawsze zwraca wartość False (zamiast tego należy użyć IS NULL
metody ).
Oprócz używania innej instrukcji bazowej UPDATE
, skonfigurowanie klasy TableAdapter do korzystania z optymistycznej współbieżności modyfikuje również sygnaturę metod bezpośrednich bazy danych. Jak pamiętasz z naszego pierwszego samouczka, Tworzenie warstwy dostępu do danych, metody bezpośrednie bazy danych były tymi, które akceptują listę wartości skalarnych jako parametry wejściowe (a nie silnie typizowane wystąpienie DataRow lub DataTable). W przypadku korzystania z optymistycznej współbieżności metody bezpośrednie Update()
bazy danych i Delete()
zawierają również parametry wejściowe dla oryginalnych wartości. Ponadto kod w usłudze BLL do używania wzorca aktualizacji wsadowej ( Update()
przeciążenia metody akceptujące wartości DataRows i DataTables zamiast wartości skalarnych) również muszą zostać zmienione.
Zamiast rozszerzać tabele TableAdapters istniejącego dal do korzystania z optymistycznej współbieżności (co wymagałoby zmiany BLL w celu dostosowania), zamiast tego utwórzmy nowy zestaw danych typu o nazwie NorthwindOptimisticConcurrency
, do którego dodamy tabelę Products
TableAdapter, która korzysta z optymistycznej współbieżności. Następnie utworzymy klasę ProductsOptimisticConcurrencyBLL
Warstwy logiki biznesowej, która ma odpowiednie modyfikacje do obsługi optymistycznej współbieżności DAL. Po utworzeniu tej podstawy będziemy gotowi utworzyć stronę ASP.NET.
Krok 2. Tworzenie warstwy dostępu do danych, która obsługuje optymistyczną współbieżność
Aby utworzyć nowy typowy zestaw danych, kliknij prawym przyciskiem myszy DAL
folder w App_Code
folderze i dodaj nowy zestaw danych o nazwie NorthwindOptimisticConcurrency
. Jak pokazano w pierwszym samouczku, spowoduje to dodanie nowego elementu TableAdapter do zestawu danych Typed, automatycznie uruchamiając Kreatora konfiguracji TableAdapter. Na pierwszym ekranie zostanie wyświetlony monit o określenie bazy danych do nawiązania połączenia — połącz się z tą samą bazą danych Northwind przy użyciu ustawienia z Web.config
.NORTHWNDConnectionString
Rysunek 3. Nawiązywanie połączenia z tą samą bazą danych Northwind (kliknij, aby wyświetlić obraz pełnowymiarowy)
Następnie zostanie wyświetlony monit o wykonywanie zapytań dotyczących danych: za pośrednictwem instrukcji ad hoc SQL, nowej procedury składowanej lub istniejącej procedury składowanej. Ponieważ w oryginalnym języku DAL użyliśmy zapytań AD hoc SQL, użyj tej opcji również tutaj.
Rysunek 4. Określanie danych do pobrania przy użyciu instrukcji AD-Hoc SQL (kliknij, aby wyświetlić obraz pełnowymiarowy)
Na poniższym ekranie wprowadź zapytanie SQL, które ma być używane do pobierania informacji o produkcie. Użyjmy dokładnie tego samego zapytania SQL używanego dla Products
tabeli TableAdapter z oryginalnego dal, które zwraca wszystkie Product
kolumny wraz z nazwami dostawców i kategorii produktu:
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
Rysunek 5. Użyj tego samego zapytania SQL z tabeli Products
TableAdapter w oryginalnym dal (kliknij, aby wyświetlić obraz pełnowymiarowy)
Przed przejściem na następny ekran kliknij przycisk Opcje zaawansowane. Aby ten element TableAdapter używał optymistycznej kontrolki współbieżności, zaznacz pole wyboru "Użyj optymistycznej współbieżności".
Rysunek 6. Włącz optymistyczną kontrolkę współbieżności, sprawdzając pole wyboru "Użyj optymistycznej współbieżności" (kliknij, aby wyświetlić obraz pełnowymiarowy)
Na koniec wskaż, że tableAdapter powinien używać wzorców dostępu do danych, które wypełniają tabelę DataTable i zwracają tabelę Danych; wskazuje również, że należy utworzyć metody bezpośrednie bazy danych. Zmień nazwę metody dla wzorca Return a DataTable z GetData na GetProducts, aby zdublować konwencje nazewnictwa używane w oryginalnym języku DAL.
Rysunek 7. Korzystanie z wszystkich wzorców dostępu do danych w programie TableAdapter (kliknij, aby wyświetlić obraz pełnowymiarowy)
Po ukończeniu pracy kreatora zestaw danych Projektant będzie zawierać silnie typizowane Products
tabele danych i tableAdapter. Poświęć chwilę, aby zmienić nazwę tabeli DataTable z Products
na ProductsOptimisticConcurrency
, co można zrobić, klikając prawym przyciskiem myszy pasek tytułu tabeli DataTable i wybierając polecenie Zmień nazwę z menu kontekstowego.
Rysunek 8. Tabela danych i TabelaAdapter zostały dodane do zestawu danych typowych (kliknij, aby wyświetlić obraz pełnowymiarowy)
Aby zobaczyć różnice między zapytaniami UPDATE
i DELETE
między tabelą ProductsOptimisticConcurrency
TableAdapter (która używa optymistycznej współbieżności) i Produktami TableAdapter (co nie jest), kliknij tabelę TableAdapter i przejdź do okno Właściwości. DeleteCommand
W podwłaściwości właściwości i UpdateCommand
CommandText
można zobaczyć rzeczywistą składnię SQL, która jest wysyłana do bazy danych, gdy są wywoływane metody aktualizacji lub usuwania dal. W przypadku obiektu TableAdapter użyto DELETE
instrukcjiProductsOptimisticConcurrency
:
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))
DELETE
Natomiast instrukcja Product TableAdapter w naszym oryginalnym dal jest znacznie prostsza:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))
Jak widać, klauzula WHERE
w DELETE
instrukcji tableAdapter, która używa optymistycznej współbieżności, zawiera porównanie między poszczególnymi Product
istniejącymi wartościami kolumn tabeli a oryginalnymi wartościami w momencie ostatniego wypełnienia kontrolki GridView (lub DetailsView lub FormView). Ponieważ wszystkie pola inne niż ProductID
, ProductName
i Discontinued
mogą mieć NULL
wartości, dodatkowe parametry i kontrole są uwzględniane w celu poprawnego porównania NULL
wartości w klauzuli WHERE
.
Nie będziemy dodawać żadnych dodatkowych tabel danych do optymistycznego zestawu danych z obsługą współbieżności na potrzeby tego samouczka, ponieważ nasza strona ASP.NET będzie dostarczać tylko informacje o aktualizowaniu i usuwaniu produktów. Jednak nadal musimy dodać metodę GetProductByProductID(productID)
do klasy ProductsOptimisticConcurrency
TableAdapter.
Aby to osiągnąć, kliknij prawym przyciskiem myszy pasek tytułu TableAdapter (obszar tuż nad Fill
nazwami metod i GetProducts
), a następnie wybierz polecenie Dodaj zapytanie z menu kontekstowego. Spowoduje to uruchomienie Kreatora konfiguracji zapytań TableAdapter. Podobnie jak w przypadku konfiguracji początkowej tabeli TableAdapter, zdecyduj się utworzyć metodę GetProductByProductID(productID)
przy użyciu instrukcji ad hoc SQL (zobacz Rysunek 4). GetProductByProductID(productID)
Ponieważ metoda zwraca informacje o konkretnym produkcie, wskazuje, że to zapytanie jest typem SELECT
zapytania, który zwraca wiersze.
Rysunek 9. Oznaczanie typu zapytania jako "SELECT
zwracającego wiersze" (kliknij, aby wyświetlić obraz o pełnym rozmiarze)
Na następnym ekranie zostanie wyświetlony monit o użycie zapytania SQL ze wstępnie załadowanym zapytaniem TableAdapter. Rozszerz istniejące zapytanie, aby uwzględnić klauzulę WHERE ProductID = @ProductID
, jak pokazano na rysunku 10.
Rysunek 10. Dodawanie klauzuli WHERE
do wstępnie załadowanego zapytania w celu zwrócenia określonego rekordu produktu (kliknij, aby wyświetlić obraz o pełnym rozmiarze)
Na koniec zmień wygenerowane nazwy metod na FillByProductID
i GetProductByProductID
.
Rysunek 11. Zmiana nazwy metod na FillByProductID
i GetProductByProductID
(kliknij, aby wyświetlić obraz pełnowymiarowy)
Po zakończeniu pracy z tym kreatorem element TableAdapter zawiera teraz dwie metody pobierania danych: GetProducts()
, która zwraca wszystkie produkty, i GetProductByProductID(productID)
, która zwraca określony produkt.
Krok 3. Tworzenie warstwy logiki biznesowej dla optymistycznej Concurrency-Enabled DAL
Nasza istniejąca ProductsBLL
klasa zawiera przykłady użycia zarówno aktualizacji wsadowej, jak i wzorców bezpośrednich bazy danych. Metoda AddProduct
i UpdateProduct
przeciążenia używają wzorca aktualizacji wsadowej, przekazując ProductRow
wystąpienie do metody Update klasy TableAdapter. Z DeleteProduct
drugiej strony metoda używa wzorca bezpośredniego bazy danych, wywołując metodę TableAdapter Delete(productID)
.
W przypadku nowej ProductsOptimisticConcurrency
metody TableAdapter metody bezpośrednie bazy danych wymagają teraz przekazania oryginalnych wartości. Na przykład Delete
metoda oczekuje teraz dziesięciu parametrów wejściowych: oryginalnych ProductID
, , SupplierID
UnitPrice
CategoryID
ProductName
UnitsInStock
UnitsOnOrder
QuantityPerUnit
ReorderLevel
, i .Discontinued
Używa tych dodatkowych wartości parametrów wejściowych w WHERE
klauzuli DELETE
instrukcji wysyłanej do bazy danych, usuwając tylko określony rekord, jeśli bieżące wartości bazy danych są mapowanie na oryginalne.
Chociaż sygnatura metody dla metody TableAdapter Update
używana we wzorcu aktualizacji wsadowej nie uległa zmianie, kod potrzebny do zarejestrowania oryginalnych i nowych wartości. W związku z tym, zamiast próbować korzystać z optymistycznej współbieżności z istniejącą ProductsBLL
klasą DAL, utwórzmy nową klasę warstwy logiki biznesowej do pracy z naszym nowym dal.
Dodaj klasę o nazwie ProductsOptimisticConcurrencyBLL
do BLL
folderu w folderze App_Code
.
Rysunek 12. Dodawanie ProductsOptimisticConcurrencyBLL
klasy do folderu BLL
Następnie dodaj następujący kod do ProductsOptimisticConcurrencyBLL
klasy :
Imports NorthwindOptimisticConcurrencyTableAdapters
<System.ComponentModel.DataObject()> _
Public Class ProductsOptimisticConcurrencyBLL
Private _productsAdapter As ProductsOptimisticConcurrencyTableAdapter = Nothing
Protected ReadOnly Property Adapter() As ProductsOptimisticConcurrencyTableAdapter
Get
If _productsAdapter Is Nothing Then
_productsAdapter = New ProductsOptimisticConcurrencyTableAdapter()
End If
Return _productsAdapter
End Get
End Property
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Select, True)> _
Public Function GetProducts() As _
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable
Return Adapter.GetProducts()
End Function
End Class
Zanotuj instrukcję using NorthwindOptimisticConcurrencyTableAdapters
powyżej początku deklaracji klasy. NorthwindOptimisticConcurrencyTableAdapters
Przestrzeń nazw zawiera klasęProductsOptimisticConcurrencyTableAdapter
, która udostępnia metody dal. Również przed deklaracją System.ComponentModel.DataObject
klasy znajdziesz atrybut , który nakazuje programowi Visual Studio dołączenie tej klasy do listy rozwijanej kreatora ObjectDataSource.
Właściwość ProductsOptimisticConcurrencyBLL
"s Adapter
zapewnia szybki dostęp do wystąpienia ProductsOptimisticConcurrencyTableAdapter
klasy i jest zgodna ze wzorcem używanym w naszych oryginalnych klasach BLL (ProductsBLL
, CategoriesBLL
itd.). GetProducts()
Na koniec metoda po prostu wywołuje metodę w dół do metody dal GetProducts()
i zwraca ProductsOptimisticConcurrencyDataTable
obiekt wypełniony wystąpieniem ProductsOptimisticConcurrencyRow
dla każdego rekordu produktu w bazie danych.
Usuwanie produktu przy użyciu wzorca bezpośredniego bazy danych z optymistyczną współbieżnością
W przypadku używania wzorca bezpośredniego bazy danych względem dal używającego optymistycznej współbieżności metody muszą zostać przekazane nowe i oryginalne wartości. Do usunięcia nie ma nowych wartości, więc należy przekazać tylko oryginalne wartości. W naszym BLL musimy zaakceptować wszystkie oryginalne parametry jako parametry wejściowe. Użyjmy DeleteProduct
metody bezpośredniej ProductsOptimisticConcurrencyBLL
bazy danych w klasie . Oznacza to, że ta metoda musi przyjmować wszystkie dziesięć pól danych produktu jako parametry wejściowe i przekazać je do dal, jak pokazano w poniższym kodzie:
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Delete, True)> _
Public Function DeleteProduct( _
ByVal original_productID As Integer, ByVal original_productName As String, _
ByVal original_supplierID As Nullable(Of Integer), _
ByVal original_categoryID As Nullable(Of Integer), _
ByVal original_quantityPerUnit As String, _
ByVal original_unitPrice As Nullable(Of Decimal), _
ByVal original_unitsInStock As Nullable(Of Short), _
ByVal original_unitsOnOrder As Nullable(Of Short), _
ByVal original_reorderLevel As Nullable(Of Short), _
ByVal original_discontinued As Boolean) _
As Boolean
Dim rowsAffected As Integer = Adapter.Delete(
original_productID, _
original_productName, _
original_supplierID, _
original_categoryID, _
original_quantityPerUnit, _
original_unitPrice, _
original_unitsInStock, _
original_unitsOnOrder, _
original_reorderLevel, _
original_discontinued)
' Return true if precisely one row was deleted, otherwise false
Return rowsAffected = 1
End Function
Jeśli oryginalne wartości — te wartości, które zostały ostatnio załadowane do kontrolki GridView (lub DetailsView lub FormView) — różnią się od wartości w bazie danych, gdy użytkownik kliknie przycisk WHERE
Usuń, klauzula nie będzie zgodna z żadnym rekordem bazy danych i nie wpłynie to na żadne rekordy. W związku z tym metoda TableAdapter Delete
zwróci wartość 0
, a metoda BLL DeleteProduct
zwróci wartość false
.
Aktualizowanie produktu przy użyciu wzorca aktualizacji usługi Batch z optymistyczną współbieżnością
Jak wspomniano wcześniej, metoda TableAdapter Update
dla wzorca aktualizacji wsadowej ma ten sam podpis metody niezależnie od tego, czy jest stosowana optymistyczna współbieżność. Update
Metoda oczekuje wartości DataRow, tablicy DataRows, DataTable lub Typed DataSet. Brak dodatkowych parametrów wejściowych do określania oryginalnych wartości. Jest to możliwe, ponieważ tabela DataTable śledzi oryginalne i zmodyfikowane wartości elementów DataRow. Gdy dal wystawia instrukcję UPDATE
, @original_ColumnName
parametry są wypełniane oryginalnymi wartościami elementu DataRow, natomiast @ColumnName
parametry są wypełniane zmodyfikowanymi wartościami elementu DataRow.
ProductsBLL
W klasie (która używa oryginalnej, nie optymistycznej współbieżności DAL), w przypadku używania wzorca aktualizacji wsadowej do aktualizowania informacji o produkcie kod wykonuje następującą sekwencję zdarzeń:
- Odczytywanie bieżących informacji o produkcie bazy danych do
ProductRow
wystąpienia przy użyciu metody TableAdapterGetProductByProductID(productID)
- Przypisywanie nowych wartości do
ProductRow
wystąpienia z kroku 1 - Wywołaj metodę TableAdapter
Update
, przekazującProductRow
wystąpienie
Jednak ta sekwencja kroków nie będzie poprawnie obsługiwać optymistycznej współbieżności, ponieważ ProductRow
wypełnione w kroku 1 jest wypełniane bezpośrednio z bazy danych, co oznacza, że oryginalne wartości używane przez element DataRow to te, które obecnie istnieją w bazie danych, a nie te, które zostały powiązane z obiektem GridView na początku procesu edycji. Zamiast tego w przypadku korzystania z optymistycznej funkcji DAL z włączoną współbieżnością musimy zmienić UpdateProduct
przeciążenia metody, aby wykonać następujące kroki:
- Odczytywanie bieżących informacji o produkcie bazy danych do
ProductsOptimisticConcurrencyRow
wystąpienia przy użyciu metody TableAdapterGetProductByProductID(productID)
- Przypisywanie oryginalnych
ProductsOptimisticConcurrencyRow
wartości do wystąpienia z kroku 1 - Wywołaj metodę
ProductsOptimisticConcurrencyRow
wystąpieniaAcceptChanges()
, która instruuje metodę DataRow, że jej bieżące wartości to "oryginalne" - Przypisywanie nowych wartości do
ProductsOptimisticConcurrencyRow
wystąpienia - Wywołaj metodę TableAdapter
Update
, przekazującProductsOptimisticConcurrencyRow
wystąpienie
Krok 1 odczytuje wszystkie bieżące wartości bazy danych dla określonego rekordu produktu. Ten krok jest zbędny w UpdateProduct
przeciążeniu, które aktualizuje wszystkie kolumny produktu (ponieważ te wartości są zastępowane w kroku 2), ale jest niezbędne dla tych przeciążeń, w których tylko podzbiór wartości kolumny są przekazywane jako parametry wejściowe. Po przypisaniu oryginalnych wartości do ProductsOptimisticConcurrencyRow
wystąpienia wywoływana jest metoda , która oznacza bieżące wartości DataRow jako oryginalne wartości, AcceptChanges()
które mają być używane w @original_ColumnName
parametrach w instrukcji UPDATE
. Następnie nowe wartości parametrów są przypisywane do ProductsOptimisticConcurrencyRow
metody i, na koniec, Update
metoda jest wywoływana, przekazując element DataRow.
Poniższy kod przedstawia UpdateProduct
przeciążenie, które akceptuje wszystkie pola danych produktu jako parametry wejściowe. Chociaż nie pokazano tutaj, ProductsOptimisticConcurrencyBLL
klasa zawarta w pobieraniu dla tego samouczka zawiera UpdateProduct
również przeciążenie, które akceptuje tylko nazwę i cenę produktu jako parametry wejściowe.
Protected Sub AssignAllProductValues( _
ByVal product As NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow, _
ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _
ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _
ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
ByVal unitsOnOrder As Nullable(Of Short), ByVal reorderLevel As Nullable(Of Short), _
ByVal discontinued As Boolean)
product.ProductName = productName
If Not supplierID.HasValue Then
product.SetSupplierIDNull()
Else
product.SupplierID = supplierID.Value
End If
If Not categoryID.HasValue Then
product.SetCategoryIDNull()
Else
product.CategoryID = categoryID.Value
End If
If quantityPerUnit Is Nothing Then
product.SetQuantityPerUnitNull()
Else
product.QuantityPerUnit = quantityPerUnit
End If
If Not unitPrice.HasValue Then
product.SetUnitPriceNull()
Else
product.UnitPrice = unitPrice.Value
End If
If Not unitsInStock.HasValue Then
product.SetUnitsInStockNull()
Else
product.UnitsInStock = unitsInStock.Value
End If
If Not unitsOnOrder.HasValue Then
product.SetUnitsOnOrderNull()
Else
product.UnitsOnOrder = unitsOnOrder.Value
End If
If Not reorderLevel.HasValue Then
product.SetReorderLevelNull()
Else
product.ReorderLevel = reorderLevel.Value
End If
product.Discontinued = discontinued
End Sub
<System.ComponentModel.DataObjectMethodAttribute( _
System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct(
ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _
ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _
ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
ByVal unitsOnOrder As Nullable(Of Short), ByVal reorderLevel As Nullable(Of Short), _
ByVal discontinued As Boolean, ByVal productID As Integer, _
_
ByVal original_productName As String, _
ByVal original_supplierID As Nullable(Of Integer), _
ByVal original_categoryID As Nullable(Of Integer), _
ByVal original_quantityPerUnit As String, _
ByVal original_unitPrice As Nullable(Of Decimal), _
ByVal original_unitsInStock As Nullable(Of Short), _
ByVal original_unitsOnOrder As Nullable(Of Short), _
ByVal original_reorderLevel As Nullable(Of Short), _
ByVal original_discontinued As Boolean, _
ByVal original_productID As Integer) _
As Boolean
'STEP 1: Read in the current database product information
Dim products As _
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable = _
Adapter.GetProductByProductID(original_productID)
If products.Count = 0 Then
' no matching record found, return false
Return False
End If
Dim product As _
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow = products(0)
'STEP 2: Assign the original values to the product instance
AssignAllProductValues( _
product, original_productName, original_supplierID, _
original_categoryID, original_quantityPerUnit, original_unitPrice, _
original_unitsInStock, original_unitsOnOrder, original_reorderLevel, _
original_discontinued)
'STEP 3: Accept the changes
product.AcceptChanges()
'STEP 4: Assign the new values to the product instance
AssignAllProductValues( _
product, productName, supplierID, categoryID, quantityPerUnit, unitPrice, _
unitsInStock, unitsOnOrder, reorderLevel, discontinued)
'STEP 5: Update the product record
Dim rowsAffected As Integer = Adapter.Update(product)
' Return true if precisely one row was updated, otherwise false
Return rowsAffected = 1
End Function
Krok 4. Przekazywanie oryginalnych i nowych wartości ze strony ASP.NET do metod BLL
Po ukończeniu dal i BLL wystarczy utworzyć stronę ASP.NET, która może korzystać z optymistycznej logiki współbieżności wbudowanej w system. W szczególności kontrolka internetowa danych (GridView, DetailsView lub FormView) musi pamiętać swoje oryginalne wartości, a obiekt ObjectDataSource musi przekazać oba zestawy wartości do warstwy logiki biznesowej. Ponadto strona ASP.NET musi być skonfigurowana do bezproblemowego obsługi naruszeń współbieżności.
Zacznij od otwarcia OptimisticConcurrency.aspx
strony w folderze EditInsertDelete
i dodania kontrolki GridView do Projektant, ustawiając jej ID
właściwość na ProductsGrid
. Z poziomu tagu inteligentnego gridView wybierz opcję utworzenia nowego obiektu ObjectDataSource o nazwie ProductsOptimisticConcurrencyDataSource
. Ponieważ chcemy, aby ta wartość ObjectDataSource korzystała z dal, która obsługuje optymistyczną współbieżność, skonfiguruj ją do używania ProductsOptimisticConcurrencyBLL
obiektu .
Rysunek 13. Korzystanie z ProductsOptimisticConcurrencyBLL
obiektu ObjectDataSource (kliknij, aby wyświetlić obraz o pełnym rozmiarze)
Wybierz metody GetProducts
, UpdateProduct
i DeleteProduct
z list rozwijanych w kreatorze. W przypadku metody UpdateProduct użyj przeciążenia, które akceptuje wszystkie pola danych produktu.
Konfigurowanie właściwości kontrolki ObjectDataSource
Po zakończeniu pracy kreatora znacznik deklaratywny objectDataSource powinien wyglądać następująco:
<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>
Jak widać, DeleteParameters
kolekcja zawiera Parameter
wystąpienie dla każdego z dziesięciu parametrów wejściowych w metodzie ProductsOptimisticConcurrencyBLL
DeleteProduct
klasy. UpdateParameters
Podobnie kolekcja zawiera Parameter
wystąpienie dla każdego z parametrów wejściowych w pliku UpdateProduct
.
W przypadku poprzednich samouczków, które obejmowały modyfikację danych, w tym momencie usuniemy właściwość ObjectDataSource OldValuesParameterFormatString
, ponieważ ta właściwość wskazuje, że metoda BLL oczekuje przekazania starych (lub oryginalnych) wartości, a także nowych wartości. Ponadto ta wartość właściwości wskazuje nazwy parametrów wejściowych dla oryginalnych wartości. Ponieważ przekazujemy oryginalne wartości do usługi BLL, nie usuwaj tej właściwości.
Uwaga
Wartość OldValuesParameterFormatString
właściwości musi być mapowania na nazwy parametrów wejściowych w BLL, które oczekują oryginalnych wartości. Ponieważ nazwaliśmy te parametry original_productName
, original_supplierID
i tak dalej, możesz pozostawić OldValuesParameterFormatString
wartość właściwości jako original_{0}
. Jeśli jednak parametry wejściowe metod BLL mają nazwy takie jak old_productName
, old_supplierID
i tak dalej, należy zaktualizować OldValuesParameterFormatString
właściwość do old_{0}
.
Istnieje jedno ostateczne ustawienie właściwości, które należy wykonać w celu poprawnego przekazania oryginalnych wartości do metod BLL przez element ObjectDataSource. Właściwość ObjectDataSource ma właściwość ConflictDetection , którą można przypisać do jednej z dwóch wartości:
OverwriteChanges
- wartość domyślna; nie wysyła oryginalnych wartości do oryginalnych parametrów wejściowych metod BLLCompareAllValues
- wysyła oryginalne wartości do metod BLL; wybierz tę opcję podczas korzystania z optymistycznej współbieżności
Poświęć chwilę, aby ustawić ConflictDetection
właściwość na CompareAllValues
.
Konfigurowanie właściwości i pól kontrolki GridView
Po poprawnym skonfigurowaniu właściwości obiektu ObjectDataSource zwróćmy uwagę na konfigurowanie obiektu GridView. Najpierw, ponieważ chcemy, aby kontrolka GridView obsługiwała edytowanie i usuwanie, kliknij pola wyboru Włącz edytowanie i Włącz usuwanie z tagu inteligentnego GridView. Spowoduje to dodanie pola commandfield, którego ShowEditButton
wartości i ShowDeleteButton
są ustawione na true
wartość .
Po powiązaniu z obiektem ProductsOptimisticConcurrencyDataSource
ObjectDataSource obiekt GridView zawiera pole dla każdego pola danych produktu. Chociaż taki element GridView można edytować, środowisko użytkownika jest niczym, ale akceptowalnym. Wartości CategoryID
i SupplierID
BoundFields będą renderowane jako TextBoxes, co wymaga od użytkownika wprowadzenia odpowiedniej kategorii i dostawcy jako numerów identyfikacyjnych. Nie będzie formatowania pól liczbowych i żadnych kontrolek walidacji, aby upewnić się, że nazwa produktu została podana i że cena jednostkowa, jednostki w magazynie, jednostki na zamówienie i wartości na poziomie zmiany kolejności są zarówno odpowiednimi wartościami liczbowymi, jak i są większe lub równe zero.
Jak omówiono w samouczkach Dodawanie kontrolek weryfikacji do edycji i wstawiania interfejsów oraz dostosowywanie interfejsu modyfikacji danych , interfejs użytkownika można dostosować, zastępując pola boundFields polami szablonów. Element GridView został zmodyfikowany i jego interfejs edycji w następujący sposób:
- Usunięto pola
ProductID
,SupplierName
iCategoryName
BoundFields - Przekonwertowano pole
ProductName
BoundField na pole szablonu i dodano kontrolkę RequiredFieldValidation. - Przekonwertowano
CategoryID
elementy iSupplierID
BoundFields na TemplateFields i dostosowano interfejs edycji tak, aby używał list DropDownLists, a nie TextBoxes. W tych polach TemplateFieldsItemTemplates
CategoryName
zostaną wyświetlone pola danych iSupplierName
. - Przekonwertowano kontrolki
UnitPrice
, ,UnitsInStock
,UnitsOnOrder
iReorderLevel
BoundFields na TemplateFields i dodano kontrolki CompareValidator.
Ponieważ już sprawdziliśmy, jak wykonać te zadania w poprzednich samouczkach, po prostu wymienię tutaj ostateczną składnię deklaratywną i pozostawię implementację jako praktykę.
<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>
Jesteśmy bardzo blisko posiadania w pełni działającego przykładu. Istnieje jednak kilka subtelności, które skradają się i spowodują problemy. Ponadto nadal potrzebujemy interfejsu, który ostrzega użytkownika po wystąpieniu naruszenia współbieżności.
Uwaga
Aby kontrolka sieci Web danych prawidłowo przekazała oryginalne wartości do obiektu ObjectDataSource (które są następnie przekazywane do biblioteki BLL), należy ustawić właściwość GridView EnableViewState
na true
wartość (wartość domyślna). Jeśli wyłączysz stan widoku, oryginalne wartości zostaną utracone podczas ogłaszania zwrotnego.
Przekazywanie poprawnych oryginalnych wartości do obiektu ObjectDataSource
Istnieje kilka problemów ze sposobem konfigurowania kontrolki GridView. Jeśli właściwość ObjectDataSource jest ustawiona na CompareAllValues
(jak to jest nasza), gdy metody lub Delete()
ObjectDataSource ConflictDetection
Update()
są wywoływane przez obiekt GridView (lub DetailsView lub FormView), obiekt ObjectDataSource próbuje skopiować oryginalne wartości kontrolki GridView do odpowiednich Parameter
wystąpień. Zapoznaj się z rysunkiem 2, aby uzyskać graficzną reprezentację tego procesu.
W szczególności oryginalne wartości kontrolki GridView są przypisywane do wartości w dwukierunkowych instrukcjach powiązania danych za każdym razem, gdy dane są powiązane z obiektem GridView. W związku z tym istotne jest, aby wymagane oryginalne wartości były przechwytywane za pośrednictwem dwukierunkowego powiązania danych i że są one dostarczane w formacie kabrioletu.
Aby zobaczyć, dlaczego jest to ważne, poświęć chwilę, aby odwiedzić naszą stronę w przeglądarce. Zgodnie z oczekiwaniami kontrolka GridView wyświetla listę każdego produktu z przyciskiem Edytuj i Usuń w lewej kolumnie.
Rysunek 14. Produkty są wyświetlane w siatce (kliknij, aby wyświetlić obraz w pełnym rozmiarze)
Kliknięcie przycisku Usuń dla dowolnego produktu FormatException
spowoduje zgłoszenie.
Rysunek 15. Próba usunięcia jakichkolwiek wyników produktu w elemecie FormatException
(kliknij, aby wyświetlić obraz pełnowymiarowy)
Obiekt FormatException
jest zgłaszany, gdy obiekt ObjectDataSource próbuje odczytać wartość oryginalną UnitPrice
. Ponieważ element ItemTemplate
ma UnitPrice
format waluty (<%# Bind("UnitPrice", "{0:C}") %>
), zawiera symbol waluty, taki jak $19.95. Występuje FormatException
, gdy obiekt ObjectDataSource próbuje przekonwertować ten ciąg na decimal
. Aby obejść ten problem, mamy kilka opcji:
- Usuń formatowanie waluty z pliku
ItemTemplate
. Oznacza to, że zamiast używać<%# Bind("UnitPrice", "{0:C}") %>
polecenia , po prostu użyj polecenia<%# Bind("UnitPrice") %>
. Wadą tego jest to, że cena nie jest już sformatowana. UnitPrice
Wyświetl format jako walutę w elemecieItemTemplate
, ale użyj słowa kluczowegoEval
, aby to osiągnąć. Pamiętaj, żeEval
wykonuje jednokierunkowe powiązanie danych. Nadal musimy podaćUnitPrice
wartość oryginalnych wartości, więc nadal będziemy potrzebować dwukierunkowej instrukcji powiązania danych w elemecieItemTemplate
, ale można ją umieścić w kontrolce Sieci Web etykiety, którejVisible
właściwość jest ustawiona nafalse
wartość . Możemy użyć następującego znacznika w elemencie ItemTemplate:
<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>
- Usuń formatowanie waluty z
ItemTemplate
elementu , używając polecenia<%# Bind("UnitPrice") %>
. W procedurze obsługi zdarzeń obiektu GridViewRowDataBound
programowo uzyskaj dostęp do kontrolki Sieci Web etykiet, w którejUnitPrice
jest wyświetlana wartość, i ustaw jejText
właściwość na sformatowaną wersję. UnitPrice
Pozostaw formatowany jako waluta. W procedurze obsługi zdarzeń gridviewRowDeleting
zastąp istniejącą oryginalnąUnitPrice
wartość ($19.95) rzeczywistą wartością dziesiętną przy użyciu .Decimal.Parse
Zobaczyliśmy, jak wykonać coś podobnego w procedurze obsługi zdarzeń w samouczkuRowUpdating
Obsługa wyjątków BLL i DAL-Level na stronie ASP.NET .
W moim przykładzie wybrano drugą metodę, dodając ukrytą kontrolkę Sieć Web Etykieta, której Text
właściwość jest dwukierunkowa powiązana z niesformatowaną UnitPrice
wartością.
Po rozwiązaniu tego problemu spróbuj ponownie kliknąć przycisk Usuń dla dowolnego produktu. Tym razem zobaczysz InvalidOperationException
, kiedy obiekt ObjectDataSource próbuje wywołać metodę UpdateProduct
BLL.
Rysunek 16. Obiekt ObjectDataSource nie może odnaleźć metody z parametrami wejściowymi, które chce wysłać (kliknij, aby wyświetlić obraz o pełnym rozmiarze)
Patrząc na komunikat wyjątku, jasne jest, że obiekt ObjectDataSource chce wywołać metodę BLL DeleteProduct
, która zawiera original_CategoryName
parametry wejściowe i original_SupplierName
. Wynika to z tego, że ItemTemplate
s dla pól CategoryID
i SupplierID
TemplateFields zawierają obecnie dwukierunkowe instrukcje Bind z polami CategoryName
danych i SupplierName
. Zamiast tego musimy uwzględnić Bind
instrukcje z CategoryID
polami danych i .SupplierID
Aby to osiągnąć, zastąp istniejące instrukcje Bind instrukcjami Eval
, a następnie dodaj ukryte kontrolki Etykieta, których Text
właściwości są powiązane z CategoryID
polami danych i SupplierID
przy użyciu dwukierunkowego powiązania danych, jak pokazano poniżej:
<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>
Dzięki tym zmianom możemy teraz pomyślnie usunąć i edytować informacje o produkcie. W kroku 5 przyjrzymy się, jak sprawdzić, czy naruszenia współbieżności są wykrywane. Jednak na razie poświęć kilka minut, aby spróbować zaktualizować i usunąć kilka rekordów, aby upewnić się, że aktualizowanie i usuwanie pojedynczego użytkownika działa zgodnie z oczekiwaniami.
Krok 5. Testowanie optymistycznej obsługi współbieżności
Aby sprawdzić, czy naruszenia współbieżności są wykrywane (zamiast w wyniku ślepego zastępowania danych), musimy otworzyć dwa okna przeglądarki na tej stronie. W obu wystąpieniach przeglądarki kliknij przycisk Edytuj dla chai. Następnie w jednej z przeglądarek zmień nazwę na "Chai Tea" i kliknij przycisk Aktualizuj. Aktualizacja powinna zakończyć się pomyślnie i przywrócić element GridView do stanu wstępnej edycji z wartością "Chai Tea" jako nową nazwą produktu.
Jednak w innym wystąpieniu okna przeglądarki nazwa produktu TextBox nadal jest wyświetlana jako "Chai". W drugim oknie przeglądarki zaktualizuj element UnitPrice
na 25.00
. Bez optymistycznej obsługi współbieżności kliknięcie aktualizacji w drugim wystąpieniu przeglądarki spowoduje zmianę nazwy produktu z powrotem na "Chai", co spowoduje zastąpienie zmian wprowadzonych przez pierwsze wystąpienie przeglądarki. Przy użyciu optymistycznej współbieżności kliknięcie przycisku Aktualizuj w drugim wystąpieniu przeglądarki powoduje wyjątek DBConcurrencyException.
Rysunek 17. Po wykryciu naruszenia współbieżności jest zgłaszany (kliknij, DBConcurrencyException
aby wyświetlić obraz w pełnym rozmiarze)
Parametr DBConcurrencyException
jest zgłaszany tylko wtedy, gdy jest używany wzorzec aktualizacji wsadowej dal. Wzorzec bezpośredni bazy danych nie zgłasza wyjątku, ale jedynie wskazuje, że nie ma to wpływu na wiersze. Aby to zilustrować, zwróć obiekt GridView obu wystąpień przeglądarki do stanu przed edycją. Następnie w pierwszym wystąpieniu przeglądarki kliknij przycisk Edytuj i zmień nazwę produktu z "Chai Tea" z powrotem na "Chai" i kliknij przycisk Aktualizuj. W drugim oknie przeglądarki kliknij przycisk Usuń dla pozycji Chai.
Po kliknięciu przycisku Usuń obiekt GridView wywołuje metodę ObjectDataSource, a element ObjectDataSource Delete()
wywołuje metodę w dół do ProductsOptimisticConcurrencyBLL
metody klasy DeleteProduct
, przekazując oryginalne wartości. Oryginalna ProductName
wartość drugiego wystąpienia przeglądarki to "Chai Tea", która nie jest zgodna z bieżącą ProductName
wartością w bazie danych. DELETE
W związku z tym instrukcja wydana dla bazy danych ma wpływ na zero wierszy, ponieważ w bazie danych nie ma rekordu, który klauzula WHERE
spełnia. Metoda DeleteProduct
zwraca false
wartość , a dane obiektu ObjectDataSource są odbicia do obiektu GridView.
Z perspektywy użytkownika końcowego, klikając przycisk Usuń dla Chai Tea w drugim oknie przeglądarki spowodował miganie ekranu i po powrocie produkt jest nadal tam, chociaż teraz jest on wymieniony jako "Chai" (zmiana nazwy produktu wprowadzonych przez pierwsze wystąpienie przeglądarki). Jeśli użytkownik kliknie ponownie przycisk Usuń, polecenie Usuń zakończy się pomyślnie, ponieważ oryginalna ProductName
wartość kontrolki GridView ("Chai") jest teraz zgodna z wartością w bazie danych.
W obu tych przypadkach środowisko użytkownika jest dalekie od idealnego. Wyraźnie nie chcemy pokazywać użytkownikowi szczegółów nitty-gritty wyjątku DBConcurrencyException
podczas korzystania ze wzorca aktualizacji wsadowej. A zachowanie podczas korzystania z wzorca bezpośredniego bazy danych jest nieco mylące, ponieważ polecenie użytkownika nie powiodło się, ale nie było dokładnego wskazania, dlaczego.
Aby rozwiązać te dwa problemy, możemy utworzyć kontrolki sieci Web etykiet na stronie, które zawierają wyjaśnienie, dlaczego aktualizacja lub usunięcie nie powiodło się. W przypadku wzorca aktualizacji wsadowej możemy określić, czy DBConcurrencyException
wystąpił wyjątek w procedurze obsługi zdarzeń po poziomie usługi GridView, wyświetlając etykietę ostrzeżenia zgodnie z potrzebami. W przypadku metody bezpośredniej bazy danych możemy zbadać wartość zwracaną metody BLL (czyli true
jeśli dotyczy to jednego wiersza, false
w przeciwnym razie) i wyświetlić komunikat informacyjny zgodnie z potrzebami.
Krok 6. Dodawanie komunikatów informacyjnych i wyświetlanie ich w obliczu naruszenia współbieżności
W przypadku naruszenia współbieżności zachowanie, które miało miejsce, zależy od tego, czy użyto aktualizacji wsadowej dal, czy wzorca bezpośredniego bazy danych. W naszym samouczku używane są oba wzorce, z wzorcem aktualizacji wsadowej używanym do aktualizowania i wzorca bezpośredniego bazy danych używanego do usuwania. Aby rozpocząć, dodajmy do naszej strony dwie kontrolki sieci Web etykiet, które wyjaśniają, że wystąpiło naruszenie współbieżności podczas próby usunięcia lub zaktualizowania danych. Ustaw właściwości i kontrolki Visible
Etykieta na false
wartość . Spowoduje to ukrycie ich na każdej wizycie strony z wyjątkiem tych wizyt na określonej stronie, gdzie ich Visible
właściwość jest programowo ustawiona na true
.EnableViewState
<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." />
Oprócz ustawiania właściwości Visible
, EnabledViewState
i Text
ustawiono również CssClass
właściwość na Warning
, co powoduje, że etykieta ma być wyświetlana w dużej, czerwonej, kursywej, pogrubionej czcionki. Ta klasa CSS Warning
została zdefiniowana i dodana do Styles.css z powrotem w samouczku Badanie zdarzeń skojarzonych z wstawianiem, aktualizowaniem i usuwaniem .
Po dodaniu tych etykiet Projektant w programie Visual Studio powinny wyglądać podobnie do rysunku 18.
Rysunek 18. Dodano dwie kontrolki etykiety do strony (kliknij, aby wyświetlić obraz w pełnym rozmiarze)
Po utworzeniu tych kontrolek sieci Web etykiet możemy sprawdzić, jak określić, kiedy doszło do naruszenia współbieżności, w którym momencie można ustawić odpowiednią właściwość etykiety Visible
na true
, wyświetlając komunikat informacyjny.
Obsługa naruszeń współbieżności podczas aktualizowania
Najpierw przyjrzyjmy się sposobom obsługi naruszeń współbieżności podczas korzystania ze wzorca aktualizacji wsadowej. Ponieważ takie naruszenia wzorca aktualizacji wsadowej powodują DBConcurrencyException
zgłoszenie wyjątku, musimy dodać kod do naszej strony ASP.NET, aby określić, czy DBConcurrencyException
wystąpił wyjątek podczas procesu aktualizacji. Jeśli tak, powinniśmy wyświetlić użytkownikowi komunikat wyjaśniający, że ich zmiany nie zostały zapisane, ponieważ inny użytkownik zmodyfikował te same dane między rozpoczęciem edytowania rekordu a kliknięciem przycisku Aktualizuj.
Jak pokazano w samouczku Obsługa wyjątków BLL i DAL-Level w samouczku dotyczącym strony ASP.NET , takie wyjątki można wykryć i pominąć w procedurach obsługi zdarzeń po poziomie kontrolki internetowej danych. W związku z tym musimy utworzyć procedurę obsługi zdarzeń dla zdarzenia gridView RowUpdated
, które sprawdza, czy DBConcurrencyException
został zgłoszony wyjątek. Ta procedura obsługi zdarzeń jest przekazywana jako odwołanie do każdego wyjątku, który został zgłoszony podczas procesu aktualizacji, jak pokazano w poniższym kodzie procedury obsługi zdarzeń:
Protected Sub ProductsGrid_RowUpdated _
(ByVal sender As Object, ByVal e As GridViewUpdatedEventArgs) _
Handles ProductsGrid.RowUpdated
If e.Exception IsNot Nothing AndAlso e.Exception.InnerException IsNot Nothing Then
If TypeOf e.Exception.InnerException Is System.Data.DBConcurrencyException Then
' Display the warning message and note that the exception has
' been handled...
UpdateConflictMessage.Visible = True
e.ExceptionHandled = True
End If
End If
End Sub
W obliczu wyjątku ta procedura obsługi zdarzeń DBConcurrencyException
wyświetla kontrolkę UpdateConflictMessage
Etykieta i wskazuje, że wyjątek został obsłużony. Po wprowadzeniu tego kodu, gdy naruszenie współbieżności występuje podczas aktualizowania rekordu, zmiany użytkownika zostaną utracone, ponieważ zostałyby one zastąpione modyfikacjami innego użytkownika w tym samym czasie. W szczególności element GridView jest zwracany do stanu wstępnej edycji i powiązany z bieżącymi danymi bazy danych. Spowoduje to zaktualizowanie wiersza kontrolki GridView przy użyciu zmian innych użytkowników, które wcześniej nie były widoczne. Ponadto kontrolka UpdateConflictMessage
Etykieta wyjaśni użytkownikowi, co właśnie się stało. Ta sekwencja zdarzeń jest szczegółowo opisana na rysunku 19.
Rysunek 19. Aktualizacje użytkownika są utracone w obliczu naruszenia współbieżności (kliknij, aby wyświetlić obraz pełnowymiarowy)
Uwaga
Alternatywnie, zamiast zwracać element GridView do stanu przed edycją, możemy pozostawić element GridView w stanie edycji, ustawiając KeepInEditMode
właściwość przekazanego GridViewUpdatedEventArgs
obiektu na true. Jeśli jednak podejmiesz takie podejście, pamiętaj, aby ponownie połączyć dane z kontrolką GridView (wywołując jej DataBind()
metodę), aby wartości innych użytkowników zostały załadowane do interfejsu edycji. Kod dostępny do pobrania z tego samouczka zawiera te dwa wiersze kodu w RowUpdated
programie obsługi zdarzeń oznaczone jako komentarz. Po prostu usuń komentarz tych wierszy kodu, aby element GridView pozostał w trybie edycji po naruszeniu współbieżności.
Reagowanie na naruszenia współbieżności podczas usuwania
W przypadku wzorca bezpośredniego bazy danych nie zgłoszono wyjątku w obliczu naruszenia współbieżności. Zamiast tego instrukcja bazy danych po prostu nie ma wpływu na żadne rekordy, ponieważ klauzula WHERE nie jest zgodna z żadnym rekordem. Wszystkie metody modyfikacji danych utworzone w usłudze BLL zostały zaprojektowane tak, aby zwracały wartość logiczną wskazującą, czy dotyczyły dokładnie jednego rekordu. W związku z tym, aby ustalić, czy doszło do naruszenia współbieżności podczas usuwania rekordu, możemy zbadać wartość zwracaną metody BLL DeleteProduct
.
Wartość zwracana dla metody BLL można zbadać w programach obsługi zdarzeń po poziomie obiektu ObjectDataSource za pośrednictwem ReturnValue
właściwości ObjectDataSourceStatusEventArgs
obiektu przekazanego do procedury obsługi zdarzeń. Ponieważ interesuje nas określenie wartości zwracanej z DeleteProduct
metody, musimy utworzyć procedurę obsługi zdarzeń dla zdarzenia ObjectDataSource Deleted
. Właściwość ReturnValue
jest typu object
i może być null
, jeśli został zgłoszony wyjątek, a metoda została przerwana, zanim może zwrócić wartość. W związku z tym należy najpierw upewnić się, że ReturnValue
właściwość nie null
jest wartością logiczną i jest wartością logiczną. Przy założeniu, że to sprawdzenie przebiegnie, pokażemy kontrolkę DeleteConflictMessage
Etykieta, jeśli element ReturnValue
to false
. Można to zrobić za pomocą następującego kodu:
Protected Sub ProductsOptimisticConcurrencyDataSource_Deleted _
(ByVal sender As Object, ByVal e As ObjectDataSourceStatusEventArgs) _
Handles ProductsOptimisticConcurrencyDataSource.Deleted
If e.ReturnValue IsNot Nothing AndAlso TypeOf e.ReturnValue Is Boolean Then
Dim deleteReturnValue As Boolean = CType(e.ReturnValue, Boolean)
If deleteReturnValue = False Then
' No row was deleted, display the warning message
DeleteConflictMessage.Visible = True
End If
End If
End Sub
W obliczu naruszenia współbieżności żądanie usunięcia użytkownika zostanie anulowane. Widok GridView jest odświeżany, pokazując zmiany, które wystąpiły dla tego rekordu między czasem załadowania strony przez użytkownika, a po kliknięciu przycisku Usuń. Gdy takie naruszenie okaże się, DeleteConflictMessage
etykieta jest wyświetlana, wyjaśniając, co właśnie się stało (zobacz Rysunek 20).
Rysunek 20. Usunięcie użytkownika jest anulowane w obliczu naruszenia współbieżności (kliknij, aby wyświetlić obraz pełnowymiarowy)
Podsumowanie
Możliwości naruszeń współbieżności istnieją w każdej aplikacji, która umożliwia wielu współbieżnych użytkowników do aktualizowania lub usuwania danych. Jeśli takie naruszenia nie zostaną uwzględnione, gdy dwóch użytkowników jednocześnie zaktualizuje te same dane, które zostaną zapisane w ostatnim zapisie "wins", zastępując zmiany innych użytkowników. Alternatywnie deweloperzy mogą zaimplementować optymistyczną lub pesymistyczną kontrolę współbieżności. Optymistyczna kontrola współbieżności zakłada, że naruszenia współbieżności są rzadko występujące i po prostu nie zezwalają na aktualizację lub usuwanie polecenia, które stanowiłoby naruszenie współbieżności. Pesymistyczna kontrola współbieżności zakłada, że naruszenia współbieżności są częste i po prostu odrzucanie aktualizacji lub usuwania jednego użytkownika polecenia nie jest dopuszczalne. W przypadku pesymistycznej kontrolki współbieżności aktualizacja rekordu obejmuje zablokowanie go, co uniemożliwia innym użytkownikom modyfikowanie lub usuwanie rekordu podczas jego blokowania.
Zestaw danych typed na platformie .NET udostępnia funkcje umożliwiające obsługę optymistycznej kontroli współbieżności. W szczególności instrukcje i DELETE
wydane dla bazy danych zawierają wszystkie kolumny tabeli, zapewniając w ten sposób, że aktualizacja lub usunięcie wystąpi tylko wtedy, UPDATE
gdy bieżące dane rekordu są zgodne z oryginalnymi danymi, które użytkownik miał podczas przeprowadzania aktualizacji lub usunięcia. Po skonfigurowaniu narzędzia DAL do obsługi optymistycznej współbieżności należy zaktualizować metody BLL. Ponadto strona ASP.NET wywołująca usługę BLL musi być skonfigurowana tak, aby obiekt ObjectDataSource pobierał oryginalne wartości z kontrolki sieci Web danych i przekazuje je do usługi BLL.
Jak pokazano w tym samouczku, implementowanie optymistycznej kontrolki współbieżności w aplikacji internetowej ASP.NET obejmuje aktualizowanie dal i BLL oraz dodawanie obsługi na stronie ASP.NET. Niezależnie od tego, czy ta dodana praca jest mądrą inwestycją w czas i nakład pracy zależy od aplikacji. Jeśli często użytkownicy współbieżni aktualizują dane lub dane, które aktualizują, różnią się od siebie, kontrola współbieżności nie jest kluczowym problemem. Jeśli jednak rutynowo masz wielu użytkowników w witrynie pracujących z tymi samymi danymi, kontrola współbieżności może pomóc zapobiec aktualizacjom lub usunięciom jednego użytkownika z nieświadomego zastępowania innych.
Szczęśliwe programowanie!
Informacje o autorze
Scott Mitchell, autor siedmiu książek ASP/ASP.NET i założyciel 4GuysFromRolla.com, współpracuje z technologiami internetowymi firmy Microsoft od 1998 roku. Scott pracuje jako niezależny konsultant, trener i pisarz. Jego najnowsza książka to Sams Teach Yourself ASP.NET 2.0 w ciągu 24 godzin. Można do niego dotrzeć pod adresem mitchell@4GuysFromRolla.com. Lub za pośrednictwem swojego bloga, który można znaleźć na stronie http://ScottOnWriting.NET.