Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
U webové aplikace, která umožňuje více uživatelům upravovat data, existuje riziko, že dva uživatelé můžou upravovat stejná data současně. V tomto kurzu si osvojíme implementaci řízení optimistické souběžnosti pro zvládnutí tohoto rizika.
Úvod
U webových aplikací, které umožňují uživatelům zobrazovat data, nebo pro uživatele, kteří obsahují jenom jednoho uživatele, který může upravovat data, neexistuje žádná hrozba dvou souběžných uživatelů, kteří omylem přepsají změny druhé. U webových aplikacích, které umožňují více uživatelům aktualizovat nebo mazat data, však existuje možnost, že se úpravy jednoho uživatele mohou střetnout s úpravami simultánního uživatele. Bez jakýchkoli zásad souběžnosti, když dva uživatelé současně upravují jeden záznam, uživatel, který potvrdí poslední změny, přepíše změny provedené prvním uživatelem.
Představte si například, že dva uživatelé, Jisun a Sam, navštívili stránku v naší aplikaci, která návštěvníkům umožnila aktualizovat a odstranit produkty prostřednictvím ovládacího prvku GridView. Oba klikněte na tlačítko Upravit v Objektu GridView přibližně ve stejnou dobu. Jisun změní název produktu na Chai Tea a klikne na tlačítko Aktualizovat. Čistý výsledek je UPDATE příkaz, který se odešle do databáze, která nastaví všechna aktualizovatelná pole produktu (i když Jisun aktualizoval pouze jedno pole, ProductName). V tomto okamžiku má databáze hodnoty "Chai Čaj", kategorie Nápoje, dodavatel Exotic Liquids a tak dále pro tento konkrétní produkt. GridView na obrazovce Samu ale stále zobrazuje název produktu v upravitelném řádku GridView jako "Chai". Několik sekund po potvrzení změn Jisun Sam aktualizuje kategorii na Dochucovadla a klikne na Aktualizovat. Výsledkem je UPDATE příkaz odeslaný do databáze, který nastaví název produktu na "Chai", CategoryID na odpovídající ID kategorie Nápoje, a tak dále. Změny, které Jisun provedl/a na názvu produktu, byly přepsány. Obrázek 1 graficky znázorňuje tuto řadu událostí.
Když dva uživatelé současně aktualizují záznam, existuje možnost, že změny jednoho uživatele přepíší změny druhého.
Obrázek 1: Když dva uživatelé současně aktualizují záznam, může dojít ke změnám jednoho uživatele při přepsání druhého uživatele (kliknutím zobrazíte obrázek v plné velikosti).
Podobně platí, že když dva uživatelé navštíví stránku, může být jeden uživatel uprostřed aktualizace záznamu, když ho odstraní jiný uživatel. Nebo mezi tím, kdy uživatel načte stránku a když klikne na tlačítko Odstranit, mohl jiný uživatel upravit obsah tohoto záznamu.
K dispozici jsou tři strategie řízení souběžnosti :
- Nedělat nic -if souběžní uživatelé mění stejný záznam, přičemž vítězí poslední potvrzení (výchozí chování)
- Optimistická souběžnost – předpokládáme, že i když mohou čas od času nastat konflikty souběžnosti, většinou času ke konfliktům nedojde. Proto když dojde ke konfliktu, jednoduše informujte uživatele, že jeho změny nelze uložit, protože jiný uživatel změnil stejná data.
- Pesimistické souběžnost – předpokládejme, že konflikty souběžnosti jsou běžné a že uživatelé nebudou tolerovat, že jejich změny se neuložily kvůli souběžné aktivitě jiného uživatele; pokud tedy jeden uživatel začne aktualizovat záznam, zamkněte ho, čímž zabrání ostatním uživatelům v úpravách nebo odstranění daného záznamu, dokud uživatel nezapíše změny.
Všechny naše výukové lekce zatím používaly výchozí strategii řešení souběžnosti – konkrétně jsme nechali vítězství posledního zápisu. V tomto kurzu se podíváme, jak implementovat optimistické řízení souběžnosti.
Poznámka:
V této sérii kurzů se nebudeme zabývat pesimistickými příklady souběžnosti. Pesimistická souběžnost se používá zřídka, protože tyto zámky, pokud nejsou správně zrušeny, mohou ostatním uživatelům zabránit v aktualizaci dat. Pokud například uživatel uzamkne záznam pro úpravy a potom odejde na den před odemknutím, nebude ho moct aktualizovat žádný jiný uživatel, dokud původní uživatel nevrátí a nedokončí jeho aktualizaci. Proto v situacích, kdy se používá pesimistická konkurence, je obvykle zvolen časový limit, který, pokud je dosažen, zruší zámek. Prodejní weby lístků, které zamknou konkrétní místo sezení po krátkou dobu, zatímco uživatel dokončí proces objednávky, je příkladem pesimistické kontroly souběžnosti.
Krok 1: Podívejte se, jak se implementuje optimistická souběžnost
Optimistické řízení souběžnosti funguje tak, že zajišťuje, aby záznam, který se aktualizuje nebo odstranil, měl stejné hodnoty jako při spuštění aktualizace nebo odstranění procesu. Když například kliknete na tlačítko Upravit v upravitelném objektu GridView, hodnoty záznamu se načtou z databáze a zobrazí se v textových polích a dalších webových ovládacích prvcích. Tyto původní hodnoty jsou uloženy objektem GridView. Později, jakmile uživatel provede změny a klikne na tlačítko Aktualizovat, původní hodnoty plus nové hodnoty se odešlou do vrstvy obchodní logiky a pak dolů do vrstvy přístupu k datům. Vrstva přístupu k datům musí vydat příkaz SQL, který aktualizuje záznam pouze v případě, že původní hodnoty, které uživatel začal upravovat, jsou stejné jako hodnoty stále v databázi. Obrázek 2 znázorňuje tuto posloupnost událostí.
Obrázek 2: Aby aktualizace nebo odstranění byla úspěšná, musí být původní hodnoty rovny aktuálním hodnotám databáze (kliknutím zobrazíte obrázek v plné velikosti).
Existují různé přístupy k implementaci optimistické souběžnosti (viz Peter A. Bromberg's Optimistic Concurrency Updating Logic pro krátký přehled řady možností). Typová datová sada ADO.NET poskytuje jednu implementaci, která se dá nakonfigurovat pouze pomocí zaškrtávacího políčka. Povolení optimistické souběžnosti pro objekt TableAdapter v Typed DataSet rozšiřuje UPDATE a DELETE příkazy TableAdapteru tak, aby zahrnovaly porovnání všech původních hodnot v klauzuli WHERE. Následující UPDATE příkaz například aktualizuje název a cenu produktu pouze v případě, že se aktuální hodnoty databáze rovnají hodnotám, které byly původně načteny při aktualizaci záznamu v GridView. Parametry @ProductName a @UnitPrice obsahují nové hodnoty zadané uživatelem, zatímco @original_ProductName a @original_UnitPrice obsahují hodnoty, které byly původně načteny do GridView po kliknutí na tlačítko Upravit.
UPDATE Products SET
ProductName = @ProductName,
UnitPrice = @UnitPrice
WHERE
ProductID = @original_ProductID AND
ProductName = @original_ProductName AND
UnitPrice = @original_UnitPrice
Poznámka:
Tento UPDATE příkaz byl zjednodušen pro čitelnost. V praxi by kontrola UnitPrice v klauzuli WHERE byla více komplexní, protože UnitPrice může obsahovat NULL hodnoty a ověření, zda NULL = NULL vždy vrací False (místo toho je nutné použít IS NULL).
Kromě použití jiného základního UPDATE příkazu konfigurace TableAdapter tak, aby používal optimistickou souběžnost, upraví také podpis jeho přímých metod databáze. Vzpomeňte si z našeho prvního kurzu Vytváření vrstvy pro přístup k datům, že přímé metody databáze jsou ty, které přijímají seznam skalárních hodnot jako vstupní parametry (místo instancí DataRow nebo DataTable s pevně definovanými typy). Při použití optimistické konkurentnosti zahrnují přímé metody databáze Update() a Delete() také parametry vstupu pro původní hodnoty. Kromě toho je nutné změnit kód v BLL pro použití vzoru dávkových aktualizací (přetížení metod, které přijímají DataRows a DataTables namísto skalárních hodnot).
Místo toho, abychom rozšířili naše existující TableAdaptery DAL tak, aby používaly optimistické řízení souběžnosti (což by vyžadovalo změnu BLL tak, aby nám vyhovovala), vytvoříme novou typově definovanou datovou sadu s názvem NorthwindOptimisticConcurrency, do které přidáme Products TableAdapter, který používá optimistické řízení souběžnosti. Pak vytvoříme ProductsOptimisticConcurrencyBLL třídu vrstvy obchodní logiky, která má odpovídající úpravy pro podporu optimistické souběžnosti DAL. Jakmile bude tato základní práce položena, budeme připraveni vytvořit ASP.NET stránku.
Krok 2: Vytvoření vrstvy přístupu k datům, která podporuje optimistickou souběžnost
Pokud chcete vytvořit novou typovou datovou sadu, klikněte pravým tlačítkem myši na DAL složku ve App_Code složce a přidejte novou datovou sadu s názvem NorthwindOptimisticConcurrency. Jak jsme viděli v prvním kurzu, přidá se do typové datové sady nový TableAdapter, který automaticky spustí Průvodce konfigurací TableAdapter. Na první obrazovce se zobrazí výzva k zadání databáze, ke které se má připojit – připojte se ke stejné databázi Northwind pomocí NORTHWNDConnectionString nastavení z Web.config.
Obrázek 3: Připojení ke stejné databázi Northwind (kliknutím zobrazíte obrázek s plnou velikostí)
Dále se zobrazí výzva k dotazování na data: prostřednictvím ad hoc příkazu SQL, nové uložené procedury nebo existující uložené procedury. Vzhledem k tomu, že jsme v původní DAL použili ad hoc dotazy SQL, použijte tuto možnost i zde.
Obrázek 4: Určení dat, která se mají načíst pomocí příkazu AD-Hoc SQL (kliknutím zobrazíte obrázek s plnou velikostí)
Na následující obrazovce zadejte dotaz SQL, který se má použít k načtení informací o produktu. Pojďme použít stejný dotaz SQL použitý pro Products TableAdapter z původního DAL, který vrátí všechny Product sloupce spolu s názvy dodavatelů a kategorií 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
Obrázek 5: Použití stejného dotazu SQL z objektu Products TableAdapter v původní dal (kliknutím zobrazíte obrázek s plnou velikostí)
Před přechodem na další obrazovku klikněte na tlačítko Upřesnit možnosti. Pokud chcete, aby tento objekt TableAdapter používal optimistické řízení souběžnosti, jednoduše zaškrtněte políčko Použít optimistickou souběžnost.
Obrázek 6: Povolení řízení optimistické souběžnosti zaškrtnutím políčka Použít optimistickou souběžnost (kliknutím zobrazíte obrázek plné velikosti)
Nakonec uveďte, že objekt TableAdapter by měl používat vzory přístupu k datům, které vyplní tabulku DataTable a vrátí tabulku DataTable; také značí, že by se měly vytvořit přímé metody databáze. Změňte název metody pro vzor Return a DataTable z GetData na GetProducts, aby odrážely názvoslovné konvence, které jsme použili v původním DAL.
Obrázek 7: Použití modelu TableAdapter pomocí všech vzorů přístupu k datům (kliknutím zobrazíte obrázek v plné velikosti)
Po dokončení průvodce bude Návrhář datové sady obsahovat silně typovanou Products DataTable a TableAdapter. Věnujte chvíli k přejmenování tabulky DataTable z Products na ProductsOptimisticConcurrency, což můžete provést kliknutím pravým tlačítkem myši na záhlaví tabulky DataTable a výběrem možnosti Přejmenovat v místní nabídce.
Obrázek 8: DataTable a TableAdapter byly přidány do typové datové sady (kliknutím zobrazíte obrázek s plnou velikostí)
Pokud chcete zobrazit rozdíly mezi dotazy UPDATE a DELETE v rámci ProductsOptimisticConcurrency TableAdapteru (který používá optimistickou souběžnost) a TableAdapteru pro produkty (který ji nepoužívá), klikněte na TableAdapter a přejděte do okna Vlastnosti. V podvlastnostech DeleteCommand a UpdateCommand vlastností CommandText můžete vidět aktuální syntaxi SQL, která se odešle do databáze při použití metod DAL souvisejících s aktualizací nebo mazáním.
ProductsOptimisticConcurrency Pro TableAdapter použitý DELETE příkaz je:
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))
Vzhledem k tomu, že prohlášení DELETE o produktu TableAdapter v našem původním DAL je mnohem jednodušší:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))
Můžete si všimnout, že klauzule WHERE v příkazu DELETE pro TableAdapter, která používá optimistickou souběžnost, zahrnuje porovnání mezi každou hodnotou existujícího sloupce v tabulce a původními hodnotami z doby, kdy byl objekt GridView (nebo DetailsView nebo FormView) naposledy naplněn. Vzhledem k tomu, že všechna jiná pole než ProductID, ProductNamea Discontinued mohou mít NULL hodnoty, jsou zahrnuty další parametry a kontroly pro správné porovnání NULL hodnot v WHERE klauzuli.
Do datové sady s podporou optimistické souběžnosti pro účely tohoto kurzu nebudeme přidávat žádné další datové tabulky, protože naše ASP.NET stránka bude poskytovat pouze informace o aktualizaci a odstraňování produktů. Přesto ale potřebujeme přidat metodu GetProductByProductID(productID)ProductsOptimisticConcurrency do TableAdapteru.
Provedete to tak, že kliknete pravým tlačítkem myši na záhlaví objektu TableAdapter (oblast přímo nad názvy metod Fill a GetProducts) a v místní nabídce zvolíte Přidat dotaz. Tím se spustí Průvodce konfigurací dotazu TableAdapter. Stejně jako u počáteční konfigurace TableAdapter se můžete rozhodnout vytvořit metodu GetProductByProductID(productID) pomocí příkazu AD-hoc SQL (viz obrázek 4). Vzhledem k tomu, že GetProductByProductID(productID) metoda vrací informace o určitém produktu, označuje, že tento dotaz je SELECT typ dotazu, který vrací řádky.
Obrázek 9: Označení typu dotazu jako "SELECT , který vrací řádky" (Kliknutím zobrazíte obrázek plné velikosti)
Na další obrazovce jsme vyzváni k zadání nebo výběru SQL dotazu, přičemž je automaticky načten výchozí dotaz TableAdapter. Rozšiřte existující dotaz tak, aby zahrnoval klauzuli WHERE ProductID = @ProductID, jak je znázorněno na obrázku 10.
Obrázek 10: Přidání WHERE klauzule do předem načteného dotazu pro vrácení konkrétního záznamu produktu (kliknutím zobrazíte obrázek s plnou velikostí)
Nakonec změňte vygenerované názvy metod na FillByProductID a GetProductByProductID.
Obrázek 11: Přejmenování metod na FillByProductID a GetProductByProductID (kliknutím zobrazíte obrázek s plnou velikostí)
Po dokončení tohoto průvodce nyní TableAdapter obsahuje dvě metody pro načítání dat: GetProducts(), která vrací všechny produkty; a GetProductByProductID(productID), která vrací zadaný produkt.
Krok 3: Vytvoření vrstvy obchodní logiky pro optimistickou Concurrency-Enabled DAL
Naše stávající ProductsBLL třída obsahuje příklady použití dávkových aktualizací i přímých vzorů databáze. Metoda AddProduct i přetížení UpdateProduct obě používají vzor dávkové aktualizace a předávají instanci ProductRow metodě Update objektu TableAdapter. Metoda DeleteProduct na druhou stranu používá přímý vzor DB a volá metodu Delete(productID) TableAdapter.
S novým ProductsOptimisticConcurrency Objektem TableAdapter nyní přímé metody databáze vyžadují předání původních hodnot. Například Delete metoda nyní očekává deset vstupních parametrů: původní ProductID, ProductName, , SupplierID, CategoryID, QuantityPerUnit, UnitPriceUnitsInStock, UnitsOnOrder, , ReorderLevel, a Discontinued. Používá tyto další hodnoty vstupních WHERE parametrů v DELETE klauzuli příkazu odeslaného do databáze, přičemž odstraní pouze zadaný záznam, pokud aktuální hodnoty databáze odpovídají původním hodnotám.
I když se podpis metody pro TableAdapter Update používanou ve vzoru dávkové aktualizace nezměnil, kód potřebný k zaznamenání původních a nových hodnot se změnil. Proto místo toho, abychom se pokusili použít DAL s podporou optimistické souběžnosti s naší stávající ProductsBLL třídou, vytvoříme novou třídu pro vrstvu obchodní logiky, která bude pracovat s naším novým DAL.
Přidejte třídu pojmenovanou ProductsOptimisticConcurrencyBLL do BLL složky v rámci App_Code složky.
Obrázek 12: Přidání ProductsOptimisticConcurrencyBLL třídy do složky BLL
Dále do třídy přidejte následující kód ProductsOptimisticConcurrencyBLL :
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
Všimněte si příkazu using NorthwindOptimisticConcurrencyTableAdapters nad začátkem deklarace třídy. Jmenný prostor NorthwindOptimisticConcurrencyTableAdapters obsahuje třídu ProductsOptimisticConcurrencyTableAdapter, která poskytuje metody pro DAL. Také před deklarací třídy najdete atribut System.ComponentModel.DataObject, který dává sadě Visual Studio pokyn, aby tuto třídu zahrnula do rozevíracího seznamu průvodce ObjectDataSource.
Vlastnost ProductsOptimisticConcurrencyBLL poskytuje Adapter rychlý přístup k instanci třídy ProductsOptimisticConcurrencyTableAdapter a řídí se vzorem používaným v našich původních třídách BLL (ProductsBLL, CategoriesBLL a tak dále). Nakonec metoda GetProducts() jednoduše zavolá metodu DAL GetProducts() a vrátí ProductsOptimisticConcurrencyDataTable objekt naplněný ProductsOptimisticConcurrencyRow instancí pro každý záznam produktu v databázi.
Odstranění produktu pomocí modelu přímé databáze s optimistickou souběžností
Při použití přímého vzoru databáze v souvislosti s DAL, který využívá optimistickou souběžnost, je nutné metodám předat nové a původní hodnoty. Pro odstranění neexistují žádné nové hodnoty, takže je potřeba předat pouze původní hodnoty. V naší BLL pak musíme přijmout všechny původní parametry jako vstupní parametry. Pojďme v metodě DeleteProduct třídy ProductsOptimisticConcurrencyBLL použít přímý databázový přístup. To znamená, že tato metoda musí jako vstupní parametry převzít všech deset datových polí produktu a předat je DAL, jak je ukázáno v následujícím kódu:
<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
Pokud se původní hodnoty – hodnoty, které byly naposledy načteny do objektu GridView (nebo DetailsView nebo FormView) – liší od hodnot v databázi, když uživatel klikne na tlačítko Delete, WHERE klauzule se neshoduje s žádným záznamem databáze a nebudou ovlivněny žádné záznamy. Metoda TableAdapter Delete se proto vrátí 0 a metoda BLL DeleteProduct vrátí false.
Aktualizace produktu pomocí vzoru dávkové aktualizace s optimistickou souběžností
Jak jsme uvedli dříve, metoda TableAdapter Update pro model dávkové aktualizace má stejný podpis metody bez ohledu na to, zda se používá optimistická souběžnost. Konkrétně metoda Update očekává objekt typu DataRow, pole objektů DataRow, objekt typu DataTable nebo Typed DataSet. Pro zadání původních hodnot neexistují žádné další vstupní parametry. To je možné, protože DataTable sleduje původní a změněné hodnoty pro dataRow(s). Když DAL vydá svůj UPDATE příkaz, @original_ColumnName parametry se naplní původními hodnotami DataRow, zatímco @ColumnName parametry se naplní upravenými hodnotami DataRow.
ProductsBLL Ve třídě (která používá původní, neoptimalizační souběžnost DAL) při použití vzoru dávkové aktualizace k aktualizaci informací o produktu náš kód provede následující posloupnost událostí:
- Načtení informací o aktuálním databázovém
ProductRowproduktu do instance pomocí metody TableAdapterGetProductByProductID(productID) - Přiřazení nových hodnot instanci
ProductRowz kroku 1 - Volání metody TableAdapter
Updatea předáníProductRowinstance
Tato posloupnost kroků ale nebude správně podporovat optimistickou souběžnost, protože ProductRow vyplněná hodnota v kroku 1 je naplněna přímo z databáze, což znamená, že původní hodnoty používané DataRow jsou ty, které aktuálně existují v databázi, a ne ty, které byly vázané na GridView na začátku procesu úprav. Místo toho při použití data access layer s povolenou optimistickou souběžností potřebujeme změnit přetížení metody UpdateProduct, aby používalo následující kroky:
- Načtení informací o aktuálním databázovém
ProductsOptimisticConcurrencyRowproduktu do instance pomocí metody TableAdapterGetProductByProductID(productID) - Přiřazení původních hodnot instanci
ProductsOptimisticConcurrencyRowz kroku 1 - Zavolejte metodu
ProductsOptimisticConcurrencyRowinstanceAcceptChanges(), která dává pokyn DataRow, že jeho aktuální hodnoty jsou "původní" - Přiřaďte nové hodnoty instanci
ProductsOptimisticConcurrencyRow. - Volání metody TableAdapter
Updatea předáníProductsOptimisticConcurrencyRowinstance
Krok 1 přečte všechny aktuální hodnoty databáze pro zadaný záznam produktu. Tento krok je nadbytečný v UpdateProduct přetížení, které aktualizuje všechny sloupce produktu (protože tyto hodnoty jsou přepsány v kroku 2), ale je nezbytné pro tato přetížení, kde se jako vstupní parametry předávají pouze podmnožina hodnot sloupce. Jakmile jsou původní hodnoty přiřazeny k ProductsOptimisticConcurrencyRow instanci, AcceptChanges() volá se metoda, která označuje aktuální hodnoty DataRow jako původní hodnoty, které se mají použít v @original_ColumnName parametrech v UPDATE příkazu. Dále jsou nové hodnoty parametrů přiřazeny ProductsOptimisticConcurrencyRow a nakonec Update je vyvolána metoda, která předává DataRow.
Následující kód ukazuje UpdateProduct přetížení, které přijímá všechna pole dat produktu jako vstupní parametry. I když to zde není zobrazeno, ProductsOptimisticConcurrencyBLL třída zahrnutá do stahování pro tento kurz obsahuje také přetížení, které přijímá název produktu a cenu jako vstupní parametry.
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: Předání původních a nových hodnot ze stránky ASP.NET metodám BLL
Po dokončení DAL a BLL je vše, co zbývá, vytvořit ASP.NET stránku, která může využívat logiku optimistické souběžnosti integrovanou v systému. Konkrétně musí datový webový ovládací prvek (GridView, DetailsView nebo FormView) pamatovat své původní hodnoty a ObjectDataSource musí předat obě sady hodnot do vrstvy obchodní logiky. Kromě toho musí být stránka ASP.NET nakonfigurovaná tak, aby řádně zpracovávala porušení souběžnosti.
Začněte otevřením OptimisticConcurrency.aspx stránky ve EditInsertDelete složce a přidáním GridView do Návrháře, nastavením jeho ID vlastnosti na ProductsGrid. Z chytré značky GridView vyberte možnost vytvořit nový ObjectDataSource s názvem ProductsOptimisticConcurrencyDataSource. Vzhledem k tomu, že chceme, aby tento ObjectDataSource používal DAL, který podporuje optimistickou souběžnost, nakonfigurujte ho tak, aby používal ProductsOptimisticConcurrencyBLL objekt.
Obrázek 13: Použití objektu ProductsOptimisticConcurrencyBLL ObjectDataSource (kliknutím zobrazíte obrázek s plnou velikostí)
V průvodci zvolte metody z rozevíracích seznamů GetProducts, UpdateProduct a DeleteProduct. Pro metodu UpdateProduct použijte přetížení, které přijímá všechna datová pole produktu.
Konfigurace vlastností ovládacího prvku ObjectDataSource
Po dokončení průvodce by deklarativní kód ObjectDataSource měl vypadat takto:
<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 vidíte, DeleteParameters kolekce obsahuje Parameter instanci pro každý z deseti vstupních parametrů v ProductsOptimisticConcurrencyBLL metodě třídy DeleteProduct .
UpdateParameters Stejně tak kolekce obsahuje Parameter instanci pro každý ze vstupních parametrů v UpdateProduct.
V předchozích kurzech, které zahrnovaly úpravy dat, bychom v tomto okamžiku odebrali vlastnost ObjectDataSource OldValuesParameterFormatString , protože tato vlastnost označuje, že metoda BLL očekává předání starých (nebo původních) hodnot a také nové hodnoty. Kromě toho tato hodnota vlastnosti označuje názvy vstupních parametrů pro původní hodnoty. Vzhledem k tomu, že předáváme původní hodnoty do BLL, neodebídějte tuto vlastnost.
Poznámka:
Hodnota vlastnosti musí mapovat na názvy parametrů vstupu OldValuesParameterFormatString v BLL, které očekávají původní hodnoty. Vzhledem k tomu, že jsme tyto parametry pojmenovali jako original_productName, original_supplierID a tak dále, můžete ponechat hodnotu vlastnosti OldValuesParameterFormatString jako original_{0}. Pokud však vstupní parametry metod BLL měly názvy jako old_productName, old_supplierIDa tak dále, budete muset aktualizovat OldValuesParameterFormatString vlastnost na old_{0}.
Existuje jedno konečné nastavení vlastnosti, které je potřeba provést, aby ObjectDataSource správně předal původní hodnoty metodám BLL. ObjectDataSource má ConflictDetection vlastnost , která může být přiřazena k jedné ze dvou hodnot:
-
OverwriteChanges- výchozí hodnota; neodesílá původní hodnoty do původních vstupních parametrů metod BLL. -
CompareAllValues- odesílá původní hodnoty metodám BLL; volba této možnosti při použití optimistické souběžnosti
Věnujte chvíli nastavení vlastnosti ConflictDetection na CompareAllValues.
Konfigurace vlastností a polí objektu GridView
Když jsou vlastnosti ObjectDataSource správně nakonfigurované, pojďme se zaměřit na nastavení GridView. Nejprve, protože chceme, aby GridView podporoval úpravy a odstraňování, klikněte na zaškrtávací políčka Povolit Úpravy a Povolit Odstranění z inteligentní značky GridView. Tím se přidá CommandField, jehož ShowEditButton a ShowDeleteButton oba jsou nastaveny na true.
Při vazbě ProductsOptimisticConcurrencyDataSource na ObjectDataSource obsahuje Objekt GridView pole pro jednotlivá datová pole produktu. I když takový GridView lze upravit, uživatelská zkušenost je všelijaká, jen ne přijatelná. Pole CategoryID a SupplierID BoundField bude vykreslené jako textová pole, což vyžaduje, aby uživatel zadal odpovídající kategorii a dodavatele jako identifikační čísla. Pro číselná pole nebude žádné formátování a žádné ověřovací ovládací prvky, které zajistí, že byl zadán název produktu a že jednotková cena, jednotky na skladě, jednotky v objednávce a hodnoty na úrovni pořadí jsou správné číselné hodnoty a jsou větší nebo rovno nule.
Jak jsme si probrali ve výukových materiálech Přidání ověřovacích ovládacích prvků do rozhraní pro úpravy a vkládání apřizpůsobení rozhraní pro úpravu dat, uživatelské rozhraní lze přizpůsobit jejich nahrazením BoundFields za TemplateFields. Upravil(a) jsem tento GridView a jeho rozhraní pro úpravy následujícími způsoby:
- Odebrali jsme pole
ProductID,SupplierNameaCategoryNamevázaných polí. - Převedli
ProductNameBoundField na TemplateField a přidali ovládací prvek RequiredFieldValidation. - Převedli jsme pole
CategoryIDaSupplierIDBoundFields na TemplateFields a upravili rozhraní pro úpravy, aby místo TextBoxů používalo DropDownLists. V těchto TemplateFieldsItemTemplatesjsou zobrazena datová poleCategoryNameaSupplierName. - Převedli jsme
UnitPrice,UnitsInStock,UnitsOnOrderaReorderLevelBoundFields na TemplateFields a přidali jsme ovládací prvky CompareValidator.
Vzhledem k tomu, že jsme už prozkoumali, jak tyto úlohy provést v předchozích kurzech, vypíšem zde pouze konečnou deklarativní syntaxi a nechám implementaci jako praxi.
<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>
Jsme velmi blízko k tomu, abychom měli plně funkční příklad. Existuje však několik jemností, které se nečekaně objeví a způsobí nám problémy. Kromě toho stále potřebujeme určité rozhraní, které uživatele upozorní, když dojde k porušení souběžnosti.
Poznámka:
Aby ovládací prvek web dat správně předal původní hodnoty ObjectDataSource (které se pak předávají do BLL), je důležité, aby vlastnost GridView EnableViewState byla nastavena na true (výchozí). Pokud zakážete stav zobrazení, původní hodnoty se při zpětném vrácení ztratí.
Předání správných původních hodnot do ObjectDataSource
Existuje několik problémů se způsobem konfigurace GridView. Pokud je vlastnost ObjectDataSource ConflictDetection nastavena na CompareAllValues (jak je tomu u nás), když jsou metody Update() nebo Delete() u GridView vyvolány (nebo u DetailsView či FormView), ObjectDataSource se snaží kopírovat původní hodnoty GridView do příslušných Parameter instancí. Pro grafické znázornění tohoto procesu se vraťte na obrázek 2.
Konkrétně jsou původní hodnoty GridView přiřazeny hodnotám z obousměrných datových vazeb, a to pokaždé, když jsou data svázána s GridView. Proto je nezbytné, aby se všechny požadované původní hodnoty zaznamenávaly prostřednictvím obousměrné vazby dat a že jsou k dispozici ve sklápěcí podobě.
Pokud chcete zjistit, proč je to důležité, navštivte naši stránku v prohlížeči. Podle očekávání obsahuje GridView každý produkt s tlačítkem Upravit a Odstranit ve sloupci úplně vlevo.
Obrázek 14: Produkty jsou uvedeny v objektu GridView (kliknutím zobrazíte obrázek s plnou velikostí)
Pokud kliknete na tlačítko Odstranit u libovolného produktu, vyvolá se FormatException.
Obrázek 15: Pokus o odstranění všech výsledků produktu na obrázku FormatException (kliknutím zobrazíte obrázek s plnou velikostí)
K vyvolání FormatException dojde, když se ObjectDataSource pokusí načíst původní hodnotu UnitPrice.
ItemTemplate Vzhledem k tomu, že má UnitPrice formát jako měnu (<%# Bind("UnitPrice", "{0:C}") %>), obsahuje symbol měny, například 19,95 USD. K FormatException dojde, když se ObjectDataSource pokusí převést tento řetězec na decimal. Abychom tento problém obešli, máme několik možností:
- Odeberte formátování měny z objektu
ItemTemplate. To znamená, že místo použití<%# Bind("UnitPrice", "{0:C}") %>jednoduše použijte<%# Bind("UnitPrice") %>. Nevýhodou je, že cena už není naformátovaná. - Zobrazte
UnitPriceve formátu měny vItemTemplatea k tomu použijte klíčové slovoEval. Vzpomeňte si, žeEvalprovádí jednosměrné vazby dat. Stále potřebujeme zadatUnitPricehodnotu pro původní hodnoty, takže budeme stále potřebovat obousměrný příkaz pro vazbu dat vItemTemplateovládacím prvku Label Web, jehožVisiblevlastnost je nastavena nafalse. V ItemTemplate bychom mohli použít následující kód:
<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>
- Odeberte formátování měny z objektu
ItemTemplate, pomocí<%# Bind("UnitPrice") %>. V obslužné rutině události GridViewRowDataBoundprogramově přistupte k webovému ovládacímu prvku Label, který zobrazuje hodnotuUnitPrice, a nastavte jeho vlastnostTextna formátovanou verzi. - Nechte
UnitPricenaformátovanou jako měnu. V obslužné rutině události GridViewRowDeletingnahraďte existující původníUnitPricehodnotu ($19,95) skutečnou desetinnou hodnotou pomocíDecimal.Parse. Viděli jsme, jak vRowUpdatingobslužné rutině události provést něco podobného v kurzu Zpracování výjimek BLL a DAL-Level na stránce ASP.NET.
Pro můj příklad jsem se rozhodl jít s druhým přístupem, přidání skrytého ovládacího prvku Label Web, jehož Text vlastnost je obousměrná data svázaná s neformátovanou UnitPrice hodnotou.
Po vyřešení tohoto problému zkuste znovu kliknout na tlačítko Odstranit u libovolného produktu. Tentokrát se zobrazí InvalidOperationException , když se ObjectDataSource pokusí vyvolat metodu UpdateProduct BLL.
Obrázek 16: ObjectDataSource nemůže najít metodu se vstupními parametry, které chce odeslat (kliknutím zobrazíte obrázek v plné velikosti).
Když se podíváte na zprávu výjimky, je jasné, že ObjectDataSource chce vyvolat metodu BLL DeleteProduct , která obsahuje original_CategoryName a original_SupplierName vstupní parametry. Důvodem je to, že ItemTemplate objekty pro CategoryID a SupplierID TemplateField aktuálně obsahují obousměrné příkazy Bind s datovými poli CategoryName a SupplierName. Místo toho musíme zahrnout Bind příkazy spolu s datovými poli CategoryID a SupplierID. Chcete-li toho dosáhnout, nahraďte existující výrazy Bind výrazy Eval, a potom přidejte skryté ovládací prvky Popisek, jejichž vlastnosti jsou svázány s datovými poli Text a CategoryID pomocí obousměrné vazby dat, jak je znázorněno níže:
<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>
Díky těmto změnám teď můžeme úspěšně odstranit a upravit informace o produktu. V kroku 5 se podíváme na to, jak ověřit, že se zjistí porušení souběžnosti. Prozatím ale zkuste aktualizovat a odstranit několik záznamů, abyste zajistili, že aktualizace a odstranění jednoho uživatele bude fungovat podle očekávání.
Krok 5: Testování podpory optimistické souběžnosti
Abychom ověřili, že se zjišťují chyby způsobené souběžností (místo toho, aby se data přepsala naslepo), musíme na této stránce otevřít dvě okna prohlížeče. V obou instancích prohlížeče klikněte na tlačítko Upravit pro Chai. Pak v jednom z prohlížečů změňte název na "Chai Čaj" a klikněte na Aktualizovat. Aktualizace by měla být úspěšná a vrátit GridView do stavu předběžné úpravy s názvem nového produktu "Chai Tea".
V jiné instanci okna prohlížeče však název produktu TextBox stále zobrazuje "Chai". V tomto druhém okně prohlížeče aktualizujte UnitPrice na 25.00. Bez podpory optimistické souběžnosti by kliknutí na aktualizaci ve druhé instanci prohlížeče změnilo název produktu zpět na Chai, čímž by se přepsaly změny provedené první instancí prohlížeče. Při použití optimistické souběžnosti však kliknutí na tlačítko Aktualizovat ve druhé instanci prohlížeče vede k DBConcurrencyException.
Obrázek 17: Při zjištění porušení souběžnosti dojde k DBConcurrencyException vyvolání (kliknutím zobrazíte obrázek s plnou velikostí).
Výjimka DBConcurrencyException je vyvolána pouze tehdy, když je využíván vzor dávkové aktualizace v DAL. Přímý vzor databáze nevyvolá výjimku, pouze značí, že nebyly ovlivněny žádné řádky. Chcete-li to ilustrovat, vraťte obě instance prohlížeče GridView do stavu před úpravou. Dále v první instanci prohlížeče klikněte na tlačítko Upravit a změňte název produktu z "Chai Čaj" zpět na "Chai" a klikněte na Aktualizovat. V druhém okně prohlížeče klikněte na tlačítko Odstranit pro Chai.
Po kliknutí na Delete stránka odešle zpět, GridView vyvolá metodu Delete() z ObjectDataSource a ObjectDataSource vyvolá metodu ProductsOptimisticConcurrencyBLL z třídy DeleteProduct, čímž předává původní hodnoty. Původní ProductName hodnota druhé instance prohlížeče je Chai Tea, která neodpovídá aktuální ProductName hodnotě v databázi.
DELETE Příkaz vydaný pro databázi proto ovlivňuje nulové řádky, protože v databázi není žádný záznam, který WHERE klauzule splňuje. Metoda DeleteProduct vrátí false a data z ObjectDataSource jsou znovu přiřazena k GridView.
Z pohledu koncového uživatele kliknutí na tlačítko Odstranit pro Chai Čaj v druhém okně prohlížeče způsobilo, že obrazovka blikla a když se vrátila, produkt je stále tam, i když je teď uveden jako "Chai" (název produktu byl změněn první instancí prohlížeče). Pokud uživatel znovu klikne na tlačítko Odstranit, bude odstranění úspěšné, protože původní ProductName hodnota GridView (Chai) se teď shoduje s hodnotou v databázi.
V obou těchto případech je uživatelské prostředí daleko od ideálního. Zjevně nechceme uživateli zobrazit drobné detaily výjimky DBConcurrencyException při použití vzoru dávkové aktualizace. A chování při použití přímého vzoru databáze je poněkud matoucí, protože příkaz uživatele selhal, ale nebyl žádný přesný údaj o tom, proč.
Abychom tyto dva problémy odstranili, můžeme vytvořit ovládací prvky Label Web na stránce s vysvětlením, proč aktualizace nebo odstranění selhala. U vzoru dávkové aktualizace můžeme určit, zda v obslužné rutině události GridView po zpracování úrovně došlo k DBConcurrencyException výjimce a podle potřeby zobrazit varovný štítek. Pro přímou metodu DB můžeme prozkoumat návratovou hodnotu metody BLL (což je true v případě ovlivnění jednoho řádku, false jinak) a podle potřeby zobrazit informační zprávu.
Krok 6: Přidání informačních zpráv a jejich zobrazení v případě porušení souběžnosti
Když dojde k porušení souběžnosti, chování vystavené závisí na tom, jestli se použila dávková aktualizace DAL nebo přímý vzor databáze. Náš tutoriál používá oba přístupy, přičemž přístup dávkové aktualizace se používá k aktualizaci a přímý přístup k databázi se používá k odstranění. Pojďme na naši stránku přidat dva ovládací prvky Label Web, které vysvětlují, že při pokusu o odstranění nebo aktualizaci dat došlo k porušení souběžnosti. Nastavte vlastnosti Visible a EnableViewState ovládacího prvku Popisek na false. To způsobí, že budou skryty při každé návštěvě stránky, kromě konkrétních návštěv, kdy je jejich vlastnost Visible programově nastavena na true.
<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." />
Kromě nastavení jejich Visible, EnabledViewState a Text vlastností jsem také nastavil CssClass vlastnost na Warning, což způsobí, že Popisek se zobrazí velkým, červeným, kurzívou tučným písmem. Tato třída šablon stylů CSS Warning byla definována a přidána do Styles.css zpět v kurzu Zkoumání událostí spojených s vkládáním, aktualizací a odstraňováním .
Po přidání těchto štítků by měl Návrhář ve Visual Studio vypadat podobně jako na obrázku 18.
Obrázek 18: Na stránku byly přidány dva ovládací prvky popisku (kliknutím zobrazíte obrázek v plné velikosti)
S těmito ovládacími prvky Label Web jsme připraveni prozkoumat, jak zjistit, kdy došlo k porušení souběžnosti, v jakém okamžiku může být vlastnost příslušného Visible popisku nastavena na true, zobrazující informační zprávu.
Zpracování porušení souběžnosti při aktualizaci
Nejprve se podíváme na to, jak řešit porušení souběžnosti při použití vzoru dávkové aktualizace. Vzhledem k tomu, že porušení se vzorem dávkové aktualizace způsobí DBConcurrencyException výjimku, musíme do naší stránky ASP.NET přidat kód, abychom zjistili, zda během procesu aktualizace došlo k DBConcurrencyException výjimce. Pokud ano, měli bychom uživateli zobrazit zprávu s vysvětlením, že jejich změny nebyly uloženy, protože jiný uživatel upravil stejná data mezi tím, kdy začal upravovat záznam a po kliknutí na tlačítko Aktualizovat.
Jak jsme viděli v kurzu Řešení BLL a DAL-Level výjimek na stránce ASP.NET, tyto výjimky je možné detekovat a potlačit v obslužných rutinách událostí datového webového ovládacího prvku. Proto potřebujeme vytvořit obslužnou rutinu události pro událost GridView RowUpdated , která kontroluje, zda DBConcurrencyException byla vyvolána výjimka. Tato obslužná rutina události je předávána s odkazem na všechny výjimky, které byly vyvolány během aktualizačního procesu, jak je znázorněno v kódu níže:
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
V případě DBConcurrencyException výjimky tato obslužná rutina události zobrazí UpdateConflictMessage ovládací prvek Popisek a udává, že výjimka byla zpracována. Při použití tohoto kódu, když dojde při aktualizaci záznamu k porušení souběžnosti, změny uživatele se ztratí, protože by současně přepsaly úpravy jiného uživatele. Konkrétně se objekt GridView vrátí do stavu předběžné úpravy a je svázán s aktuálními daty databáze. Tím se řádek GridView aktualizuje o změny ostatních uživatelů, které nebyly dříve viditelné. Ovládací prvek Popisek navíc uživateli vysvětlí, UpdateConflictMessage co se právě stalo. Tato posloupnost událostí je podrobně popsána na obrázku 19.
Obrázek 19: Aktualizace uživatele se ztratí v tvář porušení souběžnosti (kliknutím zobrazíte obrázek v plné velikosti).
Poznámka:
Případně bychom místo vrácení Objektu GridView do stavu před úpravou mohli objekt GridView ponechat ve stavu úprav nastavením KeepInEditMode vlastnosti předaného GridViewUpdatedEventArgs objektu na hodnotu true. Pokud ale použijete tento přístup, nezapomeňte data znovu připojit k Objektu GridView (vyvoláním metody DataBind() ) tak, aby hodnoty druhého uživatele byly načteny do rozhraní pro úpravy. Kód, který je k dispozici ke stažení s tímto kurzem, má tyto dva řádky kódu v RowUpdated obslužné rutině události zakomentované. Stačí odkomentovat tyto řádky, aby GridView zůstal v režimu úprav po porušení souběžnosti.
Reakce na porušení souběžnosti při odstraňování
Při přímém přístupu k databázi se v případě porušení souběžnosti nevyvolá žádná výjimka. Místo toho příkaz databáze jednoduše nemá vliv na žádné záznamy, protože klauzule WHERE neodpovídá žádnému záznamu. Všechny metody úpravy dat vytvořené v BLL byly navrženy tak, aby vrátily logickou hodnotu označující, zda byly ovlivněny přesně jedním záznamem. Proto abychom zjistili, jestli došlo k porušení souběžnosti při odstraňování záznamu, můžeme prozkoumat návratovou hodnotu metody BLL DeleteProduct .
Návratovou hodnotu metody BLL lze prozkoumat v obslužných rutinách událostí ObjektDataSource po úrovni prostřednictvím ReturnValue vlastnosti objektu ObjectDataSourceStatusEventArgs předaného obslužné rutině události. Vzhledem k tomu, že nás zajímá určení návratové hodnoty z DeleteProduct metody, potřebujeme vytvořit obslužnou rutinu události pro událost ObjectDataSource Deleted . Vlastnost ReturnValue je typu object a může být null , pokud byla vyvolána výjimka a metoda byla přerušena předtím, než by mohla vrátit hodnotu. Proto bychom měli nejprve zajistit, aby ReturnValue vlastnost nebyla null a byla to logická hodnota. Za předpokladu, že tato kontrola projde, zobrazíme ovládací prvek Popisek DeleteConflictMessage, pokud je ReturnValuefalse. Toho lze dosáhnout pomocí následujícího kódu:
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
V případě porušení souběžnosti se žádost o odstranění uživatele zruší. Objekt GridView se aktualizuje a zobrazuje změny, ke kterým došlo u daného záznamu mezi časem, kdy uživatel načetl stránku, a po kliknutí na tlačítko Odstranit. Pokud k takovému porušení dojde, zobrazí se DeleteConflictMessage popisek, který vysvětluje, co se právě stalo (viz obrázek 20).
Obrázek 20: Odstranění uživatele je zrušeno při porušení souběžnosti (kliknutím zobrazíte obrázek v plné velikosti).
Shrnutí
Příležitosti pro porušení souběžnosti existují v každé aplikaci, která umožňuje více souběžných uživatelů aktualizovat nebo odstranit data. Pokud taková porušení nejsou zohledněna, když dva uživatelé současně aktualizují stejná data, ten, kdo provede poslední zápis, 'vyhrává', přepíše změny druhého uživatele. Vývojáři můžou také implementovat optimistické nebo pesimistické řízení souběžnosti. Řízení optimistické souběžnosti předpokládá, že porušení souběžnosti jsou občasná a jednoduše zakáže aktualizaci nebo odstranění příkazu, který by představoval porušení souběžnosti. Pesimistické řízení souběžnosti předpokládá, že porušení souběžnosti jsou častá a jednoduše odmítání aktualizace nebo příkazu delete jednoho uživatele není přijatelné. Při pesimistickém řízení souběžnosti zahrnuje aktualizace záznamu uzamčení, což brání ostatním uživatelům v úpravě nebo odstranění záznamu, když je zamknutý.
Typová datová sada v .NET poskytuje funkce pro podporu optimistického řízení souběžnosti. Zejména příkazy UPDATE a DELETE příkazy vydané pro databázi obsahují všechny sloupce tabulky, čímž zajistíte, aby aktualizace nebo odstranění probíhala pouze v případě, že aktuální data záznamu odpovídají původním datům, která uživatel měl při provádění aktualizace nebo odstranění. Jakmile je DAL nakonfigurován na podporu optimistické souběžnosti, je potřeba aktualizovat metody BLL. Kromě toho musí být stránka ASP.NET, která volá do BLL, nakonfigurována tak, aby ObjectDataSource načítal původní hodnoty z jeho datového webového ovládacího prvku a předává je do BLL.
Jak jsme viděli v tomto kurzu, implementace optimistického řízení souběžnosti ve webové aplikaci ASP.NET zahrnuje aktualizaci DAL a BLL a přidání podpory na stránce ASP.NET. Bez ohledu na to, jestli tato přidaná práce představuje moudrou investici času a úsilí, závisí na vaší aplikaci. Pokud máte občas souběžné uživatele, kteří aktualizují data, nebo se data, která aktualizují, liší od sebe, pak řízení souběžnosti není klíčovým problémem. Pokud ale na vašem webu pravidelně pracuje více uživatelů se stejnými daty, řízení souběžnosti může zabránit nechtěnému přepsání změn nebo odstranění jednoho uživatele jiným.
Šťastné programování!
O autorovi
Scott Mitchell, autor sedmi knih ASP/ASP.NET a zakladatel 4GuysFromRolla.com, pracuje s webovými technologiemi Microsoftu od roku 1998. Scott pracuje jako nezávislý konzultant, trenér a spisovatel. Jeho nejnovější kniha je Sams Naučte se ASP.NET 2.0 za 24 hodin. Může být dosažitelný na mitchell@4GuysFromRolla.comadrese .