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 implementujeme optimistické řízení souběžnosti, abychom zvládli toto riziko.
Ú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í, které umožňují více uživatelům upravovat nebo mazat data, však existuje možnost, že úpravy jednoho uživatele se budou střetávat s úpravami jiného současné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 od Jisun, Sam aktualizuje kategorii na omáčky 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ápojů a tak dále. Změny názvu produktu Jisun byly přepsány. Obrázek 1 graficky znázorňuje tuto řadu událostí.
Obrázek 1: Když dva uživatelé současně aktualizují záznam, může dojít ke změně jednoho uživatele, aby přepsal ostatní (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.
Existují tři strategie řízení souběžnosti k dispozici.
- Nedělat nic: -if konkurentní uživatelé, kteří upravují stejný záznam; nechte poslední potvrzení zvítězit (výchozí chování)
- Optimistická souběžnost – předpokládejme, že i když čas od času mohou nastat konflikty souběžnosti, po drtivou většinu času se nevyskytují; proto, pokud ke konfliktu dojde, 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 kurzy doposud používaly výchozí strategii řešení souběžnosti – konkrétně jsme nechali poslední zápis vítězit. V tomto kurzu se podíváme, jak implementovat optimistickou kontrolu 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á souběžnost, obvykle dochází k vypršení časového limitu, který v případě dosažení zruší zámek. Prodejní weby lístků, které zamknou konkrétní místo sezení na krátkou dobu, zatímco uživatel dokončuje proces objednávky, jsou příkladem pesimistické kontroly souběžnosti.
Krok 1: Přehled implementace optimistické souběžnosti
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 stručný 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 TableAdapter v Typed DataSet upravuje příkazy TableAdapteru a zahrnuje UPDATE a DELETE tak, aby se porovnaly všechny původní hodnoty 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 ve WHERE klauzuli byla složitější, protože UnitPrice může obsahovat NULL a kontrola, 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, nakonfigurování TableAdapteru pro použití optimistické souběžnosti také upravuje podpis jeho přímých metod databáze. Vzpomeňte si z našeho prvního tutoriálu Vytvoření vrstvy přístupu 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 pevně typovaných instancí DataRow nebo DataTable). Při použití optimistické souběžnosti zahrnují databázové přímé Update() a Delete() metody také vstupní parametry pro původní hodnoty. Kromě toho musí být změněn také kód v BLL pro použití vzoru dávkové aktualizace (přetížení metod Update(), které přijímají DataRows a DataTables místo skalárních hodnot).
Namísto rozšíření našich existujících TableAdapters DAL pro použití optimistické souběžnosti (což by vyžadovalo změny v BLL), vytvoříme novou typizovanou datovou sadu s názvem NorthwindOptimisticConcurrency, do níž přidáme TableAdapter Products, který využívá optimistickou souběžnost. 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 vzorec vrácení DataTable z GetData na GetProducts, aby odrážely zásady pojmenování, 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ě typovaný Products DataTable a TableAdapter. Věnujte chvíli přejmenování tabulky DataTable z Products na ProductsOptimisticConcurrency. To můžete provést tak, že kliknete pravým tlačítkem na záhlaví tabulky DataTable a v místní nabídce zvolíte Přejmenovat.
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 TableAdapter (který používá optimistickou souběžnost) a TableAdapter Produktů (který ji nepoužívá), klikněte na TableAdapter a přejděte do okna Vlastnosti. Ve vlastnostech DeleteCommand a UpdateCommand dílčích CommandText můžete vidět skutečnou syntax SQL, která je odeslána do databáze, když jsou vyvolány metody DAL související s aktualizací nebo odstraně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))
Jak vidíte, klauzule WHERE v příkazu DELETE TableAdapteru, který využívá optimistickou souběžnost, zahrnuje porovnání mezi hodnotami existujících sloupců Product tabulky a původními hodnotami v době, 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.
Klikněte pravým tlačítkem myši na záhlaví TableAdapteru (oblast přímo nad názvy metod Fill a GetProducts) a v místní nabídce zvolte 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í dotazu SQL s předem zavedeným výchozím dotazem od TableAdapteru. 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 a přetížení UpdateProduct oba používají vzor dávkové aktualizace předáním instance ProductRow metodě Update TableAdaptera. Metoda DeleteProduct, naopak, používá přímý databázový vzor, který 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á hodnoty těchto dodatečných vstupních parametrů v klauzuli WHERE příkazu odeslaného do databáze, a smaže pouze zadaný záznam, pokud aktuální hodnoty v databázi odpovídají původním.
I když se podpis metody TableAdapteru Update používaného při vzoru dávkových aktualizací nezměnil, kód potřebný k zaznamenání původních a nových hodnot se změnil. Raději než se pokusit použít DAL podporující optimistickou souběžnost s naší stávající ProductsBLL třídou, vytvořme novou třídu vrstvy obchodní logiky pro práci 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 :
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindOptimisticConcurrencyTableAdapters;
[System.ComponentModel.DataObject]
public class ProductsOptimisticConcurrencyBLL
{
private ProductsOptimisticConcurrencyTableAdapter _productsAdapter = null;
protected ProductsOptimisticConcurrencyTableAdapter Adapter
{
get
{
if (_productsAdapter == null)
_productsAdapter = new ProductsOptimisticConcurrencyTableAdapter();
return _productsAdapter;
}
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, true)]
public NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable GetProducts()
{
return Adapter.GetProducts();
}
}
Všimněte si příkazu using NorthwindOptimisticConcurrencyTableAdapters nad začátkem deklarace třídy. Obor NorthwindOptimisticConcurrencyTableAdapters názvů obsahuje ProductsOptimisticConcurrencyTableAdapter třídu, která poskytuje metody DAL. Před deklarací třídy najdete také atribut System.ComponentModel.DataObject, který poskytuje pokyny Visual Studio, aby tuto třídu zahrnulo do rozevíracího seznamu průvodce ObjectDataSource.
Vlastnost ProductsOptimisticConcurrencyBLL poskytuje rychlý přístup k instanci Adapter třídy a řídí se vzorcem používaným v našich původních třídách BLL (ProductsOptimisticConcurrencyTableAdapter, ProductsBLL, atd.). 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í databázového vzoru přímo proti DAL, který používá optimistickou souběžnost, musí být metodám předány 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. Zaveďme, aby metoda DeleteProduct ve třídě ProductsOptimisticConcurrencyBLL používala přímou metodu databáze. 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 znázorněno v následujícím kódu:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteProduct
(int original_productID, string original_productName,
int? original_supplierID, int? original_categoryID,
string original_quantityPerUnit, decimal? original_unitPrice,
short? original_unitsInStock, short? original_unitsOnOrder,
short? original_reorderLevel, bool original_discontinued)
{
int rowsAffected = Adapter.Delete(original_productID,
original_productName,
original_supplierID,
original_categoryID,
original_quantityPerUnit,
original_unitPrice,
original_unitsInStock,
original_unitsOnOrder,
original_reorderLevel,
original_discontinued);
// Return true if precisely one row was deleted, otherwise false
return rowsAffected == 1;
}
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á DataRow, pole objektů DataRow, 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í, neoptimistickou 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álosti:
- 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í optimistické souběžnosti povolené DALem potřebujeme změnit UpdateProduct přetížení metody tak, 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á informuje 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 ta přetížení, kde jako vstupní parametry jsou předávány pouze některé hodnoty sloupců. 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 je metoda Update vyvolána s předáním DataRow jako argumentu.
Následující kód ukazuje UpdateProduct přetížení, které přijímá všechny datové polohy produktu jako vstupní parametry. I když se zde nezobrazuje, ProductsOptimisticConcurrencyBLL třída zahrnutá do stahování pro tento kurz obsahuje UpdateProduct také přetížení, které přijímá pouze název a cenu produktu jako vstupní parametry.
protected void AssignAllProductValues
(NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,
string productName, int? supplierID, int? categoryID, string quantityPerUnit,
decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
short? reorderLevel, bool discontinued)
{
product.ProductName = productName;
if (supplierID == null)
product.SetSupplierIDNull();
else
product.SupplierID = supplierID.Value;
if (categoryID == null)
product.SetCategoryIDNull();
else
product.CategoryID = categoryID.Value;
if (quantityPerUnit == null)
product.SetQuantityPerUnitNull();
else
product.QuantityPerUnit = quantityPerUnit;
if (unitPrice == null)
product.SetUnitPriceNull();
else
product.UnitPrice = unitPrice.Value;
if (unitsInStock == null)
product.SetUnitsInStockNull();
else
product.UnitsInStock = unitsInStock.Value;
if (unitsOnOrder == null)
product.SetUnitsOnOrderNull();
else
product.UnitsOnOrder = unitsOnOrder.Value;
if (reorderLevel == null)
product.SetReorderLevelNull();
else
product.ReorderLevel = reorderLevel.Value;
product.Discontinued = discontinued;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateProduct(
// new parameter values
string productName, int? supplierID, int? categoryID, string quantityPerUnit,
decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
short? reorderLevel, bool discontinued, int productID,
// original parameter values
string original_productName, int? original_supplierID, int? original_categoryID,
string original_quantityPerUnit, decimal? original_unitPrice,
short? original_unitsInStock, short? original_unitsOnOrder,
short? original_reorderLevel, bool original_discontinued,
int original_productID)
{
// STEP 1: Read in the current database product information
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable products =
Adapter.GetProductByProductID(original_productID);
if (products.Count == 0)
// no matching record found, return false
return false;
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product = products[0];
// STEP 2: Assign the original values to the product instance
AssignAllProductValues(product, original_productName, original_supplierID,
original_categoryID, original_quantityPerUnit, original_unitPrice,
original_unitsInStock, original_unitsOnOrder, original_reorderLevel,
original_discontinued);
// STEP 3: Accept the changes
product.AcceptChanges();
// STEP 4: Assign the new values to the product instance
AssignAllProductValues(product, productName, supplierID, categoryID,
quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel,
discontinued);
// STEP 5: Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
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 vytvoření nového ObjectDataSource s názvem ProductsOptimisticConcurrencyDataSource. Vzhledem k tomu, že chceme, aby tento ObjectDataSource používal DAL, který podporuje optimistickou souběžnost, nakonfigurujte jej pomocí ProductsOptimisticConcurrencyBLL objektu.
Obrázek 13: Použití objektu ProductsOptimisticConcurrencyBLL ObjectDataSource (kliknutím zobrazíte obrázek s plnou velikostí)
V průvodci vyberte metody z rozevíracích seznamů v 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 chvilku nastavení ConflictDetection vlastnosti na CompareAllValues hodnotu.
Konfigurace vlastností a polí objektu GridView
Když jsou vlastnosti ObjectDataSource správně nakonfigurované, pojďme se zaměřit na nastavení GridView. Za prvé, 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 chytré 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é prostředí je naprosto nepřijatelné. Pole CategoryID a SupplierID BoundFields se vykreslí jako textová pole, a vyžaduje se, aby uživatel zadal odpovídající kategorii a dodavatele pomocí čísel ID. 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 v kurzech Přidání ověřovacích ovládacích prvků do rozhraní pro úpravy a vkládání a Přizpůsobení rozhraní pro úpravu dat, uživatelské rozhraní lze přizpůsobit nahrazením BoundFields za TemplateFields. Upravil(a) jsem tento GridView a jeho rozhraní pro úpravy následujícími způsoby:
- Byla odstraněna pole
ProductID,SupplierNameaCategoryNameBoundFields. - Převedli jsme
ProductNameBoundField na TemplateField a přidali jsme ovládací prvek RequiredFieldValidation. - Převedli jsme pole
CategoryIDaSupplierIDBoundFields na TemplateFields a upravili rozhraní pro úpravy tak, aby místo textových polí používalo rozbalovací seznamy. V těchto TemplateFields'ItemTemplatesjsou 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 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 ConflictDetection u ObjectDataSource nastavena na CompareAllValues (stejně jako u nás), při vyvolání metod Update() nebo Delete() pomocí GridView (nebo DetailsView nebo FormView), se ObjectDataSource pokusí zkopírovat původní hodnoty GridView do odpovídajících instancí Parameter. Pro grafické znázornění tohoto procesu se vraťte na obrázek 2.
Konkrétně jsou původním hodnotám GridView přiřazeny hodnoty z obousměrného datového připojení 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í)
Dojde k vyvolání FormatException při pokusu ObjectDataSource o načtení původní hodnoty 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 dochází, když se ObjectDataSource pokusí převést tento řetězec na decimal. Abychom tento problém obešli, máme několik možností:
- Odstraňte formátování měny z
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
UnitPrice, formátované jako měnu vItemTemplate, ale použijte k tomu 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
ItemTemplatepomocí<%# Bind("UnitPrice") %>. V obslužné rutině události GridViewRowDataBoundprogramově přistupte k ovládacímu prvku Label Web, kde je zobrazena hodnotaUnitPrice, a nastavte jeho vlastnostTextna formátovanou verzi. - Nechte
UnitPricezformá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 v obslužné rutině událostiRowUpdatingprovést něco podobného v kurzu Zpracování BLL a DAL-Level výjimek 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 příkazy pro CategoryID a SupplierID TemplateFieldy aktuálně obsahují obousměrné Bind příkazy 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í příkazy pro vazbu Bind příkazy Eval a poté přidejte skryté ovládací prvky popisku, jejichž vlastnosti Text jsou svázány s datovými poli CategoryID a SupplierID 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 pro optimistickou souběžnost
Abychom ověřili, že se zjišťují porušení souběžnosti (namísto toho, aby se data přepisovala naslepo), musíme otevřít dvě okna prohlížeče na této stránce. 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 výjimce DBConcurrencyException.
Obrázek 17: Při zjištění porušení souběžnosti je vyvolána DBConcurrencyException (klikněte pro zobrazení obrázku v plné velikosti).
Tato DBConcurrencyException možnost se vyvolá pouze v případech, kdy se využívá model dávkové aktualizace 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.
Při kliknutí na tlačítko Delete se stránka vrátí zpět, GridView vyvolá metodu Delete() z ObjectDataSource a ObjectDataSource pak zavolá metodu ProductsOptimisticConcurrencyBLL ve třídě DeleteProduct, přičemž předá 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 se převážou na GridView.
Z pohledu koncového uživatele kliknutí na tlačítko Odstranit pro Chai Tea ve druhém okně prohlížeče způsobilo, že obrazovka blikne a po návratu je produkt stále tam, i když je teď uvedený jako "Chai" (název produktu změněný prvním oknem 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. Jasně nechceme uživateli zobrazit detailní informace o DBConcurrencyException výjimce 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 došlo k DBConcurrencyException výjimce v obslužné rutině události gridView po úrovni a podle potřeby zobrazit popisek upozornění. 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í při porušení souběžnosti
Když dojde k porušení souběžnosti, předvedené chování závisí na tom, zda byla použita dávková aktualizace vrstvy přístupu k datům (DAL) nebo přímý přístup k databázi. Náš kurz využívá oba vzory: vzor dávkové aktualizace se používá k aktualizaci a přímý vzor databáze se využívá k mazá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 ovládacího prvku Visible a EnableViewState na false. To způsobí, že budou skryty při každé návštěvě stránky, kromě těch konkrétních návštěv, kde 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ě toho, že jsem nastavil jejich Visible, EnabledViewState a Text vlastnosti, jsem také nastavil vlastnost CssClass na Warning, což způsobí, že Štítek se zobrazí ve velkém, červeném, kurzívou a 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 popisků by návrhář v sadě Visual Studio měl 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, a v tom okamžiku může být vlastnost příslušného popisku Visible nastavena na true, aby zobrazila 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 taková porušení kvůli dávkovému aktualizačnímu vzoru způsobí, že DBConcurrencyException se vyvolá výjimka, musíme na stránku 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 zpracování 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í webového ovládacího prvku pro data. 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 se předává odkaz na všechny výjimky, které byly vyvolány během procesu aktualizace, jak je znázorněno v kódu obslužné rutiny události níže:
protected void ProductsGrid_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
if (e.Exception != null && e.Exception.InnerException != null)
{
if (e.Exception.InnerException is System.Data.DBConcurrencyException)
{
// Display the warning message and note that the
// exception has been handled...
UpdateConflictMessage.Visible = true;
e.ExceptionHandled = true;
}
}
}
V případě DBConcurrencyException výjimky tato obslužná rutina zobrazí UpdateConflictMessage ovládací prvek Popisek a uvádí, že výjimka byla vyřešena. S tímto kódem dojde při aktualizaci záznamu k porušení souběžnosti, což způsobí ztrátu změn uživatele, protože jeho úpravy se současně přepíší úpravami 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 jsou ztraceny kvůli 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í v tomto kurzu, má tyto dva řádky kódu v RowUpdated obslužné rutině události okomentované. Jednoduše odkomentujte tyto řádky kódu, aby GridView zůstal v režimu úprav po porušení souběžnosti.
Reakce na porušení souběžnosti při odstraňování
S přímým vzorem databáze neexistuje žádná výjimka vyvolaná v případě porušení souběžnosti. 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í ObjectDataSource na úrovni událostí prostřednictvím vlastnosti ReturnValue objektu ObjectDataSourceStatusEventArgs, který je předán do obslužné rutiny 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 void ProductsOptimisticConcurrencyDataSource_Deleted(
object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.ReturnValue != null && e.ReturnValue is bool)
{
bool deleteReturnValue = (bool)e.ReturnValue;
if (deleteReturnValue == false)
{
// No row was deleted, display the warning message
DeleteConflictMessage.Visible = true;
}
}
}
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. Když nastane takové porušení, DeleteConflictMessage je zobrazena popisek, který vysvětluje, co se právě stalo (viz obrázek 20).
Obrázek 20: Odstranění uživatele je zrušeno v tvář 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, „vyhraje“ a 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 tak, aby podporoval optimistickou souběžnost, 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 však na vašem webu rutinně pracuje více uživatelů se stejnými daty, může řízení souběžnosti pomoci zabránit tomu, aby aktualizace nebo odstranění jednoho uživatele bezděčně nepřepsaly změny jiného uživatele.
Šť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 .