Megosztás a következőn keresztül:


Optimista egyidejűség megvalósítása (VB)

által Scott Mitchell

PDF letöltése

Egy olyan webalkalmazás esetében, amely lehetővé teszi több felhasználó számára az adatok szerkesztését, fennáll annak a kockázata, hogy két felhasználó egyszerre szerkeszti ugyanazokat az adatokat. Ebben az oktatóanyagban optimista egyidejűség-vezérlést alkalmazunk a kockázat kezelésére.

Bevezetés

Az olyan webalkalmazások esetében, amelyek csak a felhasználók számára engedélyezik az adatok megtekintését, vagy azok esetében, amelyekben csak egyetlen felhasználó módosíthat adatokat, nem fenyegeti, hogy két egyidejű felhasználó véletlenül felülírja egymás módosításait. Az olyan webalkalmazások esetében, amelyek lehetővé teszik több felhasználó számára az adatok frissítését vagy törlését, előfordulhat azonban, hogy az egyik felhasználó módosításai ütköznek egy másik felhasználóéval. Ha nincs érvényben egyidejűségi szabályzat, amikor két felhasználó egyszerre szerkeszt egy rekordot, a módosításokat utoljára véglegesítő felhasználó felülbírálja az első által végrehajtott módosításokat.

Tegyük fel például, hogy két felhasználó, Jisun és Sam is meglátogatott egy oldalt az alkalmazásunkban, amely lehetővé tette a látogatók számára a termékek frissítését és törlését egy GridView-vezérlőn keresztül. Mindkettő kattintson a Szerkesztés gombra a GridView-ban nagyjából ugyanabban az időben. Jisun megváltoztatja a termék nevét "Chai Tea" és kattintson a Frissítés gombra. A nettó eredmény egy olyan UPDATE utasítás, amelyet az adatbázisnak küldenek, és beállítja a termék összes frissíthető mezőjét (annak ellenére, hogy Jisun csak egy mezőt frissített, ProductName). Jelenleg az adatbázis a "Chai Tea" értékekkel rendelkezik, az Italok kategóriával, az Egzotikus folyadékok szállítóval és így tovább az adott termékhez. Sam képernyőjén azonban a GridView továbbra is "Chai" néven jeleníti meg a termék nevét a szerkeszthető GridView sorban. Néhány másodperccel Jisun módosításainak véglegesítése után Sam frissíti a kategóriát Fűszerek-re, és a Frissítés gombra kattint. Ennek eredménye egy UPDATE utasítás az adatbázisnak, amely a termék nevét "Chai"-ra, a CategoryID-t pedig a megfelelő ital kategória azon azonosítójára stb. állítja. Jisun terméknév módosításait felülírták. Az 1. ábra grafikusan ábrázolja ezt az eseménysorozatot.

Ha két felhasználó egyidejűleg frissít egy rekordot, akkor lehetséges, hogy az egyik felhasználó módosítja a másikat

1. ábra: Ha két felhasználó egyszerre frissít egy rekordot, akkor lehetséges, hogy az egyik felhasználó felülírja a másikat (kattintson ide a teljes méretű kép megtekintéséhez)

Hasonlóképpen, amikor két felhasználó meglátogat egy lapot, előfordulhat, hogy az egyik felhasználó egy másik felhasználó által törölt rekord frissítésekor frissít egy rekordot. Vagy ha egy felhasználó betölt egy lapot, és a Törlés gombra kattint, előfordulhat, hogy egy másik felhasználó módosította a rekord tartalmát.

Három egyidejűségi vezérlési stratégia érhető el:

  • Do Nothing -if egyidejű felhasználók módosítják ugyanazt a rekordot, hagyja, hogy az utolsó véglegesítés nyerjen (az alapértelmezett viselkedés)
  • Optimista egyidejűség – tegyük fel, hogy bár időnként egyidejűségi ütközések fordulhatnak elő, az ilyen ütközések túlnyomó többsége nem fog bekövetkezni; ezért ütközés esetén egyszerűen tájékoztassa a felhasználót, hogy a módosítások nem menthetők, mert egy másik felhasználó módosította ugyanazokat az adatokat
  • Pesszimista egyidejűség – feltételezzük, hogy az egyidejűségi ütközések gyakoriak, és hogy a felhasználók nem fogják tolerálni, hogy egy másik felhasználó egyidejű tevékenysége miatt a módosítások nem lettek mentve; ezért amikor az egyik felhasználó elkezd frissíteni egy rekordot, zárolja azt, ezáltal megakadályozza, hogy a többi felhasználó szerkessze vagy törölje a rekordot, amíg a felhasználó véglegesíti a módosításokat

Az eddigi oktatóanyagaink mindegyike az alapértelmezett egyidejűségi megoldási stratégiát használta – vagyis hagytuk, hogy az utolsó írás nyerjen. Ebben az oktatóanyagban azt vizsgáljuk meg, hogyan implementálható az optimista egyidejűség-vezérlés.

Megjegyzés:

Ebben az oktatóanyag-sorozatban nem tekintjük meg a pesszimista egyidejűségi példákat. A pesszimista zárolást ritkán használják, mert az ilyen zárolások, ha nincsenek megfelelően felszabadítva, megakadályozhatják a többi felhasználót az adatok frissítésében. Ha például egy felhasználó zárol egy rekordot szerkesztés céljából, majd a zárolás feloldása előtti napra elhagyja a rekordot, más felhasználó nem fogja tudni frissíteni a rekordot, amíg az eredeti felhasználó vissza nem tér, és be nem fejeződik a frissítés. Ezért olyan helyzetekben, amikor pesszimista egyidejűséget használnak, általában van egy időkorlát, amely elérésekor megszakítja a zárolást. A jegyértékesítési webhelyek, amelyek rövid időre zárolnak egy adott helyet, miközben a felhasználó befejezi a rendelési folyamatot, a pesszimista egyidejűség-vezérlés példája.

1. lépés: Megnézzük, hogyan van implementálva az optimista egyidejűség

Az optimista egyidejűség-vezérlés úgy működik, hogy a frissített vagy törölt rekord értékei megegyeznek a frissítési vagy törlési folyamat indításakor megadott értékekkel. Ha például a Szerkeszthető gombra kattint egy szerkeszthető GridView-ban, a rendszer beolvassa a rekord értékeit az adatbázisból, és megjelenik a Szövegdobozokban és más webes vezérlőkben. Ezeket az eredeti értékeket a GridView menti. Később, miután a felhasználó végrehajtotta a módosításokat, és a Frissítés gombra kattintott, az eredeti értékeket és az új értékeket a rendszer elküldi az üzleti logikai rétegnek, majd az adatelérési rétegnek. Az adatelérési rétegnek ki kell adnia egy SQL-utasítást, amely csak akkor frissíti a rekordot, ha a felhasználó által megkezdett eredeti értékek megegyeznek az adatbázisban lévő értékekkel. A 2. ábra az események sorozatát ábrázolja.

Ahhoz, hogy a frissítés vagy a törlés sikeres legyen, az eredeti értékeknek meg kell egyezniük az aktuális adatbázisértékekkel

2. ábra: Ahhoz, hogy a frissítés vagy a törlés sikeres legyen, az eredeti értékeknek meg kell egyezniük az aktuális adatbázisértékekkel (ide kattintva megtekintheti a teljes méretű képet)

Az optimista egyidejűség megvalósításának különböző megközelítései vannak (néhány lehetőség rövid áttekintéséért lásd Peter A. Brombergoptimista egyidejűségi frissítési logikáját ). A ADO.NET gépelt adatkészlet egy olyan implementációt biztosít, amely egy jelölőnégyzet bejelölésével konfigurálható. A TableAdapter optimista ütközéskezelésének engedélyezése a Typed DataSetben kiegészíti a TableAdapter UPDATE és DELETE utasításait, hogy az összes eredeti érték összehasonlítását tartalmazza a WHERE záradékban. Az alábbi UPDATE utasítás például csak akkor frissíti egy termék nevét és árát, ha az adatbázis aktuális értékei megegyeznek a GridView rekordjának frissítésekor eredetileg lekért értékekkel. A @ProductName paraméterek tartalmazzák @UnitPrice a felhasználó által megadott új értékeket, míg @original_ProductName@original_UnitPrice a Szerkesztés gombra kattintáskor eredetileg betöltött értékeket a GridView-ba:

UPDATE Products SET
    ProductName = @ProductName,
    UnitPrice = @UnitPrice
WHERE
    ProductID = @original_ProductID AND
    ProductName = @original_ProductName AND
    UnitPrice = @original_UnitPrice

Megjegyzés:

Ez az UPDATE utasítás egyszerűbb lett az olvashatóság érdekében. A gyakorlatban az ellenőrzés a UnitPrice záradékon belül összetettebb lenne, mert WHERE tartalmazhat UnitPrice elemeket, és ellenőrizni kell, hogy NULL mindig hamis értéket ad-e vissza (helyette NULL = NULL kell használni).

Egy másik mögöttes UPDATE utasítás használata mellett a TableAdapter optimista egyidejűség használatára való konfigurálása a közvetlen adatbázis-metódusok aláírását is módosítja. Emlékezzen vissza az első, Adatelérési réteg létrehozása című oktatóanyagunkból, hogy a közvetlen adatbázis-metódusok azok voltak, amelyek bemeneti paraméterekként fogadják el a skaláris értékek listáját (nem pedig egy erősen gépelt DataRow- vagy DataTable-példányt). Optimista egyidejűség használatakor a közvetlen Update() adatbázis és Delete() a metódusok az eredeti értékek bemeneti paramétereit is tartalmazzák. Ezenkívül a BLL-ben a kötegfrissítési minta használatához szükséges kódot, amelyben a metódusok túlterhelése DataRows és DataTables objektumokat, nem pedig skaláris értékeket fogad el, szintén módosítani kell.

Ahelyett, hogy kiterjesztenénk a meglévő DAL TableAdapters-eket az optimista egyidejűség használatára (ami szükségessé tenné a BLL módosítását az elhelyezéshez), inkább hozzunk létre egy új typed DataSet nevű NorthwindOptimisticConcurrencytáblát, amelyhez hozzáadunk egy Products optimista egyidejűséget használó TableAdaptert. Ezt követően létrehozunk egy ProductsOptimisticConcurrencyBLL Üzleti logikai réteg osztályt, amely megfelelő módosításokkal támogatja az optimista egyidejűségi DAL-t. Az alapmunka lefektetése után készen állunk a ASP.NET lap létrehozására.

2. lépés: Optimista egyidejűséget támogató adatelérési réteg létrehozása

Új típusozott adatkészlet létrehozásához kattintson a jobb gombbal a DAL mappában lévő App_Code mappára, és adjon hozzá egy új, elnevezett NorthwindOptimisticConcurrencyadatkészletet. Ahogy az első oktatóanyagban láttuk, ezzel hozzáad egy új TableAdaptert a Gépelt adatkészlethez, és automatikusan elindítja a TableAdapter konfigurációs varázslót. Az első képernyőn a rendszer arra kéri, hogy adja meg a csatlakozni kívánt adatbázist – csatlakozzon ugyanahhoz a Northwind-adatbázishoz a NORTHWNDConnectionString következő beállítással Web.config: .

Csatlakozás a Northwind Database-hez

3. ábra: Csatlakozás ugyanahhoz a Northwind-adatbázishoz (kattintson ide a teljes méretű kép megtekintéséhez)

Ezután a rendszer arra kéri, hogyan kérdezhetjük le az adatokat: egy alkalmi SQL-utasításon, egy új tárolt eljáráson vagy egy meglévő tárolt eljáráson keresztül. Mivel az eredeti DAL-ban alkalmi SQL-lekérdezéseket használtunk, ezt a lehetőséget itt is használhatja.

Adja meg az alkalmi SQL-utasítással lekérendő adatokat

4. ábra: Adja meg a lekérni kívánt adatokat alkalmi SQL-utasítással (kattintson ide a teljes méretű kép megtekintéséhez)

A következő képernyőn adja meg a termékinformációk lekéréséhez használni kívánt SQL-lekérdezést. Használjuk pontosan ugyanazt az SQL-lekérdezést, amelyet a TableAdapterhez használtunk az Products eredeti DAL-ból, amely a Product termék szállítói és kategórianeveivel együtt az összes oszlopot visszaadja:

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

Használja ugyanazt az SQL-lekérdezést a Products TableAdapterből az eredeti DAL-ban

5. ábra: Használja ugyanazt az SQL-lekérdezést a Products TableAdapterből az eredeti DAL-ban (kattintson ide a teljes méretű kép megtekintéséhez)

A következő képernyőre lépés előtt kattintson a Speciális beállítások gombra. Ha azt szeretné, hogy ez a TableAdapter optimista egyidejűségi vezérlőt használjon, egyszerűen jelölje be az "Optimista egyidejűség használata" jelölőnégyzetet.

Az optimista egyidejűség-vezérlés engedélyezése az

6. ábra: Az optimista egyidejűség-vezérlés engedélyezése az "Optimista egyidejűség használata" jelölőnégyzet bejelölésével (kattintson ide a teljes méretű kép megtekintéséhez)

Végül jelezze, hogy a TableAdapternek olyan adatelérési mintákat kell használnia, amelyek kitöltik a DataTable-t, és egy DataTable-t adnak vissza; azt is jelzi, hogy létre kell hozni a közvetlen adatbázis-metódusokat. Módosítsa a DataTable-minta Visszatérési mintájának metódusnevét GetData-ról GetProducts-ra, hogy tükrözze az eredeti DAL-ban használt elnevezési konvenciókat.

A TableAdapter az összes adatelérési mintát használja

7. ábra: A TableAdapter az összes adatelérési mintát használja (kattintson ide a teljes méretű kép megtekintéséhez)

A varázsló befejezése után a DataSet Designer tartalmazni fog egy szigorúan tipizált Products DataTable-t és egy TableAdaptert. Szánjon egy kis időt a DataTable átnevezésére Products-ról ProductsOptimisticConcurrency-re, amit a DataTable címsorára jobb gombbal kattintva, majd a helyi menüből az Átnevezés lehetőséget választva tehet meg.

Egy DataTable és TableAdapter lett hozzáadva a gépelt adatkészlethez

8. ábra: Egy DataTable és TableAdapter lett hozzáadva a gépelt adatkészlethez (ide kattintva megtekintheti a teljes méretű képet)

Ha meg szeretné tekinteni a UPDATE és DELETE lekérdezések közötti különbségeket a ProductsOptimisticConcurrency TableAdapter (amely optimista konkurenciát használ) és a Products TableAdapter (amely nem), kattintson a TableAdapterre, és lépjen a Tulajdonságok ablakba. A DeleteCommand és UpdateCommand tulajdonságok CommandText altulajdonságaiban láthatja az adatbázisnak küldött tényleges SQL-szintaxist, amikor a DAL frissítéssel vagy törléssel kapcsolatos metódusait meghívják. ProductsOptimisticConcurrency A TableAdapter esetében a használt utasítás a DELETE következő:

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))

Míg a Product TableAdapter-re vonatkozó állítás eredeti DAL-unkban sokkal egyszerűbb:

DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))

Amint látható, az WHERE optimista egyidejűséget használó TableAdapter utasításában DELETE szereplő záradék tartalmazza a Product táblázat meglévő oszlopértékeinek összehasonlítását, valamint a GridView (vagy DetailsView vagy FormView) legutóbbi kitöltésének időpontjában érvényes eredeti értékeket. Mivel a ProductID, ProductName és Discontinued kivételével minden más mező NULL értékeket tartalmazhat, további paraméterek és ellenőrzések kerültek bevezetésre annak érdekében, hogy a NULL záradékban szereplő WHERE értékek helyesen legyenek összehasonlítva.

Ebben az oktatóanyagban nem fogunk további DataTable-adatokat hozzáadni az optimista egyidejűség-kompatibilis adatkészlethez, mivel ASP.NET lapunk csak a termékinformációk frissítését és törlését biztosítja. Azonban továbbra is hozzá kell adnunk a GetProductByProductID(productID) metódust a ProductsOptimisticConcurrency TableAdapterhez.

Ehhez kattintson a jobb gombbal a TableAdapter címsorára (a Fill és GetProducts metódusok neve fölötti területre), és válassza a helyi menüből a Lekérdezés hozzáadása parancsot. Ezzel elindítja a TableAdapter Lekérdezéskonfiguráció varázslót. A TableAdapter kezdeti konfigurálásához hasonlóan a metódust egy alkalmi SQL-utasítással is létrehozhatja GetProductByProductID(productID) (lásd a 4. ábrát). Mivel a GetProductByProductID(productID) metódus egy adott termék adatait adja vissza, azt jelzi, hogy ez a lekérdezés olyan SELECT lekérdezéstípus, amely sorokat ad vissza.

A lekérdezés típusának megjelölése

9. ábra: Jelölje meg a lekérdezés típusát "SELECT , amely sorokat ad vissza" (Kattintson ide a teljes méretű kép megtekintéséhez)

A következő képernyőn a rendszer az SQL-lekérdezés használatát kéri, előre betöltve a TableAdapter alapértelmezett lekérdezését. Bővítse a meglévő lekérdezést a záradék WHERE ProductID = @ProductIDbelefoglalásához a 10. ábrán látható módon.

WHERE záradék hozzáadása az előre betöltött lekérdezéshez adott termékrekord visszaadásához

10. ábra: A WHERE záradék hozzáadása az előre betöltött lekérdezéshez egy konkrét termékrekord visszaadására (Teljes méretű kép megtekintéséhez kattintson)

Végül módosítsa a létrehozott metódusneveket a következőre FillByProductID : és GetProductByProductID.

A metódusok átnevezése FillByProductID és GetProductByProductID értékre

11. ábra: Nevezze át a metódusokat a következőre FillByProductID és GetProductByProductID (kattintson ide a teljes méretű kép megtekintéséhez)

A varázsló befejezésével a TableAdapter mostantól két módszert tartalmaz az adatok beolvasására: GetProducts()az összes terméket visszaadó, valamint GetProductByProductID(productID)a megadott terméket visszaíró metódusokat.

3. lépés: Üzleti logikai réteg létrehozása az optimista Concurrency-Enabled DAL számára

Meglévő ProductsBLL osztályunk példákat mutat be a batch update és a db közvetlen mintáinak használatára. A AddProduct metódus és a UpdateProduct túlterhelések egyaránt a kötegfrissítési mintát használják, ProductRow példányt adva át a TableAdapter Update metódusának. A DeleteProduct metódus viszont a közvetlen ADATBÁZIS-mintát használja, és meghívja a TableAdapter metódust Delete(productID) .

Az új ProductsOptimisticConcurrency TableAdapter esetében a közvetlen ADATBÁZIS-metódusok megkövetelik az eredeti értékek átadását is. A Delete metódus például most tíz bemeneti paramétert vár: az eredeti ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, és Discontinued. Ezeket a további bemeneti paraméterek értékeit használja az WHERE adatbázisnak küldött utasítás záradékábanDELETE, és csak akkor törli a megadott rekordot, ha az adatbázis aktuális értékei az eredetire vannak megfeleltetve.

Bár a Batch-frissítési mintában használt TableAdapter Update metódus szignatúrája nem változott, az eredeti és az új értékek rögzítéséhez szükséges kód megváltozott. Ezért ahelyett, hogy megpróbáljuk használni az optimista egyidejűség-kompatibilis DAL-t a meglévő ProductsBLL osztályunkkal, hozzunk létre egy új Üzleti logikai réteg osztályt az új DAL használatához.

Adjon hozzá egy osztályt ProductsOptimisticConcurrencyBLL a BLL mappában lévő App_Code mappához.

A ProductsOptimisticConcurrencyBLL osztály hozzáadása a BLL mappához

12. ábra: Az osztály hozzáadása a ProductsOptimisticConcurrencyBLL BLL mappához

Ezután adja hozzá a következő kódot az ProductsOptimisticConcurrencyBLL osztályhoz:

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

Vegye figyelembe a "using NorthwindOptimisticConcurrencyTableAdapters" utasítást az osztálydeklaráció kezdete előtt. A NorthwindOptimisticConcurrencyTableAdapters névtér tartalmazza az osztályt ProductsOptimisticConcurrencyTableAdapter , amely a DAL metódusát biztosítja. Az osztálydeklaráció előtt is megtalálja az attribútumot, amely arra utasítja a System.ComponentModel.DataObject Visual Studiót, hogy vegye fel ezt az osztályt az ObjectDataSource varázsló legördülő listájába.

A ProductsOptimisticConcurrencyBLLtulajdonság Adapter gyors hozzáférést biztosít az ProductsOptimisticConcurrencyTableAdapter osztály egy példányához, és követi az eredeti BLL-osztályokban (ProductsBLLCategoriesBLLstb.) használt mintát. Végül a GetProducts() metódus egyszerűen lehívja a DAL metódusát GetProducts() , és visszaad egy ProductsOptimisticConcurrencyDataTable példánysal ProductsOptimisticConcurrencyRow kitöltött objektumot az adatbázis minden termékrekordjára vonatkozóan.

Termék törlése DB Direct mintázattal optimista párhuzamosság mellett

Amikor közvetlen adatbázis-mintát alkalmaz egy optimista egyidejűséget használó DAL-ra, a metódusoknak át kell adni az új és az eredeti értékeket. A törléshez nincsenek új értékek, ezért csak az eredeti értékeket kell átadni. A BLL-ben ezt követően az összes eredeti paramétert bemeneti paraméterként kell elfogadnunk. Legyen a DeleteProduct metódus a ProductsOptimisticConcurrencyBLL osztályban úgy módosítva, hogy közvetlenül az adatbázis metódusát használja. Ez azt jelenti, hogy ennek a módszernek mind a tíz termékadatmezőt bemeneti paraméterként kell megadnia, és továbbítania kell őket a DAL-nak, ahogyan az a következő kódban is látható:

<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

Ha az eredeti értékek – amelyek legutóbb be lettek töltve a GridView-ba (vagy DetailsView vagy FormView) – eltérnek az adatbázisban lévő értékektől, amikor a felhasználó a Törlés gombra kattint, a WHERE záradék nem egyezik meg egyetlen adatbázisrekordkal sem, és a rendszer semmilyen rekordot nem érint. Ezért a TableAdapter metódusa Delete vissza fog térni 0 , és a BLL metódusa DeleteProduct vissza fog térni false.

Termék frissítése csoportos frissítési mintával optimista párhuzamossággal

Amint azt korábban említettük, a TableAdapter Update metódusa a Batch-frissítési mintánál ugyanolyan metódus-szignatúrával rendelkezik, függetlenül attól, hogy alkalmaznak-e optimista konzisztenciát. A metódus tehát egy Update DataRow-t, egy DataRows-tömböt, egy DataTable-t vagy egy Tipizált DataSetet vár. Az eredeti értékek megadásához nincsenek további bemeneti paraméterek. Ez azért lehetséges, mert a DataTable nyomon követi a DataRow(k) eredeti és módosított értékeit. Amikor a DAL kiadja az utasítását UPDATE , a @original_ColumnName paraméterek a DataRow eredeti értékeivel lesznek feltöltve, míg a @ColumnName paraméterek a DataRow módosított értékeivel lesznek feltöltve.

A ProductsBLL osztályban (amely az eredeti, nem optimista egyidejűségi DAL-t használja), amikor a kötegelt frissítési mintát használjuk a termékinformációk frissítésére, a kód a következő események sorozatát hajtja végre:

  1. Az adatbázis aktuális termékadatainak beolvasása egy ProductRow példányba a TableAdapter GetProductByProductID(productID) metódusával
  2. Az új értékeket rendelje hozzá az ProductRow 1. lépésből származó példányhoz.
  3. Hívja meg a TableAdapter Update metódusát úgy, hogy átadja a ProductRow példányt

Ez a lépések sorozata azonban nem támogatja megfelelően az optimista egyidejűséget, mivel az ProductRow 1. lépésben kitöltött adatok közvetlenül az adatbázisból vannak feltöltve, ami azt jelenti, hogy a DataRow által használt eredeti értékek az adatbázisban jelenleg is léteznek, és nem azok, amelyek a szerkesztési folyamat kezdetén a GridView-hoz voltak kötve. Ehelyett optimista egyidejűség-kompatibilis DAL használata esetén módosítani kell a UpdateProduct metódus túlterhelését a következő lépések végrehajtásához:

  1. Az adatbázis aktuális termékadatainak beolvasása egy ProductsOptimisticConcurrencyRow példányba a TableAdapter GetProductByProductID(productID) metódusával
  2. Az eredeti értékek hozzárendelése a példányhoz az ProductsOptimisticConcurrencyRow 1. lépésből
  3. Hívja meg a ProductsOptimisticConcurrencyRow példány metódusát AcceptChanges() , amely arra utasítja a DataRow-t, hogy az aktuális értékek az "eredetiek".
  4. Az új értékek hozzárendelése a ProductsOptimisticConcurrencyRow példányhoz
  5. Hívja meg a TableAdapter Update metódusát úgy, hogy átadja a ProductsOptimisticConcurrencyRow példányt

Az 1. lépés beolvassa a megadott termékrekord összes aktuális adatbázisértékét. Ez a lépés felesleges abban a UpdateProduct túlterhelésben, amely frissíti az összes termékoszlopot (mivel ezeket az értékeket felülírja a 2. lépés), de elengedhetetlen azokhoz a túlterhelésekhez, ahol az oszlopértékek csak egy részhalmaza lesz bemeneti paraméterként átadva. Miután az eredeti értékeket hozzárendelte a ProductsOptimisticConcurrencyRow példányhoz, meghívja a AcceptChanges() metódust, amely az aktuális DataRow-értékeket jelöli meg az utasítás paramétereiben @original_ColumnNameUPDATE használandó eredeti értékekként. Ezután a rendszer az új paraméterértékeket rendeli hozzá a ProductsOptimisticConcurrencyRow metódushoz, és végül meghívja a Update metódust, és átadja a DataRow-nak.

Az alábbi kód azt a túlterhelést UpdateProduct mutatja be, amely az összes termékadatmezőt bemeneti paraméterként fogadja el. Bár itt nem látható, az ProductsOptimisticConcurrencyBLL oktatóanyag letöltésében szereplő osztály olyan túlterhelést UpdateProduct is tartalmaz, amely csak a termék nevét és árát fogadja el bemeneti paraméterekként.

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

4. lépés: Az eredeti és az új értékek átadása a ASP.NET oldalról a BLL-metódusok számára

A DAL és a BLL elkészültével csak egy ASP.NET lap létrehozása marad, amely a rendszerbe beépített optimista egyidejűségi logikát használhatja. Pontosabban az adat-webvezérlőnek (a GridView, a DetailsView vagy a FormView) emlékeznie kell az eredeti értékeire, és az ObjectDataSource-nak mindkét értékkészletet át kell adnia az üzleti logikai rétegnek. Ezenkívül a ASP.NET lapot konfigurálni kell az egyidejűségi szabálysértések elegáns kezelésére.

Először nyissa meg a OptimisticConcurrency.aspx lapot a EditInsertDelete mappában, és adjon hozzá egy GridView-t a Tervezőhöz, és állítsa be a tulajdonságát ID a következőre ProductsGrid: . A GridView intelligens címkéje alapján hozzon létre egy új ObjectDataSource nevű ProductsOptimisticConcurrencyDataSourceobjektumot. Mivel azt szeretnénk, hogy ez az ObjectDataSource az optimista egyidejűséget támogató DAL-t használja, konfigurálja az ProductsOptimisticConcurrencyBLL objektum használatára.

Az ObjectDataSource használja a ProductsOptimisticConcurrencyBLL objektumot

13. ábra: Az ObjectDataSource használata az ProductsOptimisticConcurrencyBLL objektummal (kattintson ide a teljes méretű kép megtekintéséhez)

Válassza ki a GetProducts, UpdateProduct, és DeleteProduct metódusokat a varázsló legördülő listáiból. Az UpdateProduct metódushoz használja a termék összes adatmezőjét elfogadó túlterhelt verziót.

Az ObjectDataSource vezérlő tulajdonságainak konfigurálása

A varázsló befejezése után az ObjectDataSource deklaratív korrektúrája a következőképpen fog kinézni:

<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>

Ahogy látható, a DeleteParameters gyűjtemény tartalmaz egy Parameter példányt mindegyik tíz bemeneti paraméterhez a ProductsOptimisticConcurrencyBLL osztály DeleteProduct metódusában. Hasonlóképpen, a UpdateParameters gyűjtemény minden bemeneti paraméteréhez tartalmaz egy Parameter példányt a UpdateProduct.

Az adatmódosítást tartalmazó korábbi oktatóanyagok esetében ezen a ponton eltávolítjuk az ObjectDataSource tulajdonságát OldValuesParameterFormatString , mivel ez a tulajdonság azt jelzi, hogy a BLL-metódus elvárja a régi (vagy eredeti) értékek átadását, valamint az új értékeket. Ez a tulajdonságérték továbbá az eredeti értékek bemeneti paraméterneveit is jelzi. Mivel az eredeti értékeket továbbítjuk a BLL-be, ne távolítsa el ezt a tulajdonságot.

Megjegyzés:

A tulajdonság értékének megfeleltetnie OldValuesParameterFormatString kell a BLL bemeneti paraméterneveinek, amelyek az eredeti értékeket várják. Mivel ezeket a paramétereket original_productNameelneveztük , original_supplierIDés így tovább, a OldValuesParameterFormatString tulajdonság értékét meghagyhatja .original_{0} Ha azonban a BLL-metódusok bemeneti paramétereinek nevei olyanok voltak, mint old_productName, old_supplierID és így tovább, frissítenie kell a OldValuesParameterFormatString tulajdonságot old_{0}-ra.

Van egy végleges tulajdonságbeállítás, amelyet el kell készíteni ahhoz, hogy az ObjectDataSource megfelelően adja át az eredeti értékeket a BLL-metódusoknak. Az ObjectDataSource rendelkezik egy ConflictDetection tulajdonságmal , amely két érték egyikéhez rendelhető hozzá:

  • OverwriteChanges - az alapértelmezett érték; nem küldi el az eredeti értékeket a BLL-metódusok eredeti bemeneti paramétereinek
  • CompareAllValues - elküldi az eredeti értékeket a BLL-metódusnak; válassza ezt a lehetőséget optimista egyidejűség használatakor

Szánjon egy kis időt arra, hogy beállítja a ConflictDetection tulajdonságot CompareAllValues-re.

A GridView tulajdonságainak és mezőinek konfigurálása

Az ObjectDataSource tulajdonságainak megfelelően konfigurálva, fordítsuk a figyelmünket a GridView beállítására. Először is, mivel azt szeretnénk, hogy a GridView támogassa a szerkesztést és a törlést, kattintson a GridView intelligens címkéjének Szerkesztés engedélyezése és Törlés engedélyezése jelölőnégyzetére. Ez hozzáad egy CommandFieldet, amelynek ShowEditButton és ShowDeleteButton értéke true.

Ha az ProductsOptimisticConcurrencyDataSource ObjectDataSource-hoz van kötve, a GridView a termék minden adatmezőjének egy-egy mezőjét tartalmazza. Bár egy ilyen GridView szerkeszthető, a felhasználói élmény semmiképpen sem elfogadható. A CategoryID és SupplierID BoundFields szövegdobozként fog megjelenni, így a felhasználónak meg kell adnia a megfelelő kategóriát és szállítót azonosítószámokként. A numerikus mezők nem lesznek formázásban, és nem lesznek érvényesítési vezérlők, amelyek biztosítják, hogy a termék neve meg legyen adva, és hogy az egységár, a készletben lévő egységek, a rendelési egységek és az átrendezési szintek egyaránt megfelelő numerikus értékek, és nagyobbak vagy egyenlők nullánál.

Amint azt az Érvényesítési vezérlők hozzáadása a szerkesztési és beszúrási felületekhez , valamint az Adatmódosítási felület testreszabása oktatóanyagok ismertetik, a felhasználói felület testre szabható a BoundFields sablonmezőkre való lecserélésével. A Következő módokon módosítottam ezt a GridView-t és a szerkesztőfelületét:

  • ProductID, SupplierName, és CategoryName BoundFields eltávolítva.
  • A BoundField sablonmezővé ProductName alakította át, és hozzáadott egy RequiredFieldValidation vezérlőt.
  • A CategoryID és SupplierID BoundFields-t sablonmezőkké alakította, és módosította a szerkesztőfelületet, hogy a Szövegdobozok helyett DropDownList-eket használjon. Ezekben a Sablonmezőkben megjelennek a ItemTemplates és CategoryName adatmezők.
  • UnitPrice, UnitsInStock, UnitsOnOrder és ReorderLevel mezőket BoundFieldsről TemplateFieldsre konvertálta, és CompareValidator vezérlőket adott hozzá.

Mivel már megvizsgáltuk, hogyan végezheti el ezeket a feladatokat a korábbi oktatóanyagokban, csak felsorolom a végleges deklaratív szintaxist itt, és gyakorlatként hagyjuk a megvalósítást.

<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>

Nagyon közel vagyunk egy teljesen működő példához. Azonban van néhány finomság, amely előbukkan, és problémákat okoz nekünk. Emellett szükség van egy olyan felületre is, amely riasztást küld a felhasználónak, ha egyidejűségi szabálysértés történt.

Megjegyzés:

Ahhoz, hogy egy adat-webvezérlő helyesen adja át az eredeti értékeket az ObjectDataSource-nak (amelyet aztán átad a BLL-nek), elengedhetetlen, hogy a GridView tulajdonsága EnableViewState az alapértelmezett értékre true legyen állítva. Ha letiltja a ViewState-t, az eredeti értékek elvesznek az adatküldéskor.

A helyes eredeti értékek átadása az ObjectDataSource-nak

A GridView konfigurálásának módjával kapcsolatban van néhány probléma. Ha az ObjectDataSource ConflictDetection tulajdonsága CompareAllValues értékre van beállítva (mint a miénk), amikor a GridView (vagy a DetailsView vagy a FormView) meghívja az ObjectDataSource Update() vagy Delete() metódusait, az ObjectDataSource megpróbálja a GridView eredeti értékeit a megfelelő Parameter példányokba másolni. A folyamat grafikus ábrázolásához tekintse meg a 2. ábrát.

Pontosabban a GridView eredeti értékei a kétirányú adatkötési utasítások értékeihez vannak rendelve minden alkalommal, amikor az adatok a GridView-hoz vannak kötve. Ezért elengedhetetlen, hogy a szükséges eredeti értékek mind kétirányú adatkötéssel legyenek rögzítve, és konvertibilis formátumban legyenek megadva.

Annak érdekében, hogy ez miért fontos, szánjon egy kis időt arra, hogy megtekintse lapunkat egy böngészőben. A GridView a vártnak megfelelően listázza az egyes termékeket a bal szélső oszlop szerkesztési és törlési gombjával.

A termékek a GridView-ban vannak felsorolva

14. ábra: A termékek a GridView-ban vannak felsorolva (kattintson ide a teljes méretű kép megtekintéséhez)

Ha bármelyik termék Törlés gombjára kattint, a rendszer egy FormatException elemet dob.

Bármely termék törlésének megkísérlése FormatException hibát eredményez

15. ábra: A termékeredmények törlésének megkísérlése (FormatExceptionkattintson ide a teljes méretű kép megtekintéséhez)

Az FormatException akkor következik be, amikor az ObjectDataSource megpróbálja beolvasni az eredeti UnitPrice értékét. Mivel a ItemTemplateUnitPrice formátum pénznem (<%# Bind("UnitPrice", "{0:C}") %>), egy pénznemszimbólumot tartalmaz, például 19,95 USD. Ez FormatException akkor fordul elő, amikor az ObjectDataSource megpróbálja átalakítani ezt a sztringet egy decimal. A probléma megkerüléséhez számos lehetőségünk van:

  • Távolítsa el a pénznemformázást a ItemTemplate. Tehát ahelyett, hogy <%# Bind("UnitPrice", "{0:C}") %>-t használna, egyszerűen használja a <%# Bind("UnitPrice") %>-t. Ennek hátránya, hogy az ár már nincs formázva.
  • Az UnitPrice értéket pénznemként formázva jelenítse meg a ItemTemplate-ban, de ehhez használja a Eval kulcsszót. Ne feledje, hogy Eval egyirányú adatkötést hajt végre. Továbbra is meg kell adnunk az UnitPrice eredeti értékek értékét, ezért továbbra is szükségünk lesz egy kétirányú adatkötési utasításra a ItemTemplatenézetben, de ez elhelyezhető egy Címke web vezérlőelemben, amelynek Visible a tulajdonsága a következőre falsevan állítva. Az ItemTemplate-ban a következő korrektúrát használhatjuk:
<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>
  • Távolítsa el a pénznemformázást a ItemTemplatehasználatával <%# Bind("UnitPrice") %>. A GridView eseménykezelőjében programozott módon elérheti a Címke web vezérlőelemet, amelyen belül az RowDataBound érték megjelenik, és állítsa be a UnitPrice tulajdonságát a formázott verzióra.
  • Hagyja meg pénznemként UnitPrice formázva. A GridView eseménykezelőjében RowDeleting cserélje le a meglévő eredeti UnitPrice értéket (19,95 USD) egy tényleges decimális értékre Decimal.Parse. Láthattuk, hogyan lehet valami hasonlót megvalósítani a RowUpdating eseménykezelőben a BLL és DAL-Level Kivételek kezelése egy ASP.NET lapon oktatóanyagban.

A példámban úgy döntöttem, hogy a második megközelítést választom, és hozzáadok egy rejtett címke webvezérlőtText, amelynek UnitPrice tulajdonsága a formázatlan értékhez kötött kétirányú adat.

A probléma megoldása után próbálkozzon újra a Törlés gombra kattintva bármely terméknél. Ezúttal egy InvalidOperationException -t kap, amikor az ObjectDataSource kísérletet tesz a BLL UpdateProduct metódusának meghívására.

Az ObjectDataSource nem talál olyan metódust, amely a küldeni kívánt bemeneti paraméterekkel rendelkezik

16. ábra: Az ObjectDataSource nem talál olyan metódust, amely a küldeni kívánt bemeneti paraméterekkel rendelkezik (kattintson ide a teljes méretű kép megtekintéséhez)

A kivétel üzenetét tekintve egyértelmű, hogy az ObjectDataSource egy BLL-metódust DeleteProduct szeretne meghívni, amely original_CategoryName és original_SupplierName bemeneti paramétereket foglal magába. Ennek az az oka, hogy a ItemTemplate és a CategoryID TemplateFields kétirányú kötési utasításokat tartalmaznak az SupplierID és CategoryName adatmezőkkel. Ehelyett bele kell foglalnunk a Bind állításokat a CategoryID és SupplierID adatmezőkkel. Ehhez cserélje le a meglévő kötési utasításokat utasításokra Eval , majd adjon hozzá rejtett címkevezérlőket, amelyek Text tulajdonságai kétirányú adatkötéssel vannak kötve az CategoryIDSupplierID adatmezőkhöz, az alábbiak szerint:

<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>

Ezekkel a módosításokkal sikeresen törölhetjük és szerkeszthetik a termékinformációkat! Az 5. lépésben azt vizsgáljuk meg, hogyan ellenőrizhető, hogy az egyidejűségi szabálysértések észlelhetők-e. Egyelőre azonban szánjon néhány percet, hogy megpróbáljon frissíteni és törölni néhány rekordot annak érdekében, hogy egyetlen felhasználó frissítése és törlése a várt módon működjön.

5. lépés: Az optimista egyidejűség támogatásának tesztelése

Annak ellenőrzéséhez, hogy az egyidejűségi szabálysértések észlelhetők-e (ahelyett, hogy az adatok vakon felülíródnak), két böngészőablakot kell megnyitnunk ezen a lapon. Mindkét böngészőpéldányban kattintson a Chaihoz tartozó Szerkesztés gombra. Ezután csak az egyik böngészőben módosítsa a nevet "Chai Tea" névre, és kattintson a Frissítés gombra. A frissítésnek sikeresnek kell lennie, és vissza kell adnia a GridView-t az előzetes szerkesztési állapotába, az új terméknév pedig a "Chai Tea" lesz.

A másik böngészőablak-példányban azonban a TextBox terméknév továbbra is a "Chai" értéket jeleníti meg. Ebben a második böngészőablakban frissítse a UnitPrice elemet 25.00-re. Az optimista egyidejűség támogatása nélkül a második böngészőpéldány frissítésére kattintva a terméknév visszavált a "Chai" értékre, ezzel felülírva az első böngészőpéldány által végrehajtott módosításokat. Az optimista egyidejűség alkalmazása esetén azonban a második böngészőpéldány Frissítés gombjára kattintva DBConcurrencyException keletkezik.

Egyidejűségi szabálysértés észlelése esetén a rendszer egy DBConcurrencyException hibát jelez

17. ábra: Ha egyidejűség-sértés észlelhető, egy DBConcurrencyException kivétel dobódik (kattintson ide a teljes méretű kép megtekintéséhez)

A DBConcurrencyException rendszer csak a DAL kötegfrissítési mintájának használatakor dobja ki. Az adatbázis közvetlen mintázat nem generál kivételt, csupán azt jelzi, hogy nincs érintett sor. Ennek szemléltetéséhez adja vissza mindkét böngészőpéldány GridView-ját a szerkesztés előtti állapotba. Ezután az első böngészőpéldányban kattintson a Szerkesztés gombra, és módosítsa a termék nevét a "Chai Tea" névről "Chai" értékre, majd kattintson a Frissítés gombra. A második böngészőablakban kattintson a Chai Törlés gombjára.

A Törlés gombra kattintva a lap visszatér, a GridView meghívja az ObjectDataSource metódusát Delete() , és az ObjectDataSource lehívja az ProductsOptimisticConcurrencyBLL osztály metódusát DeleteProduct az eredeti értékek mentén haladva. A második böngészőpéldány eredeti ProductName értéke "Chai Tea", amely nem egyezik meg az adatbázis aktuális ProductName értékével. Ezért az adatbázisnak kiadott DELETE utasítás nulla sort érint, mivel nincs olyan rekord az adatbázisban, amelyet kielégít a WHERE záradék. A DeleteProduct metódus visszatér false , és az ObjectDataSource adatai visszakerülnek a GridView-ba.

A végfelhasználó szemszögéből nézve, amikor a második böngészőablakban rákattint a Törlés gombra a Chai Tea esetében, a képernyő villogni kezd, és visszatérés után a termék még mindig ott van. Bár most már "Chai" néven szerepel, amit az első böngészőablakban történt névváltoztatás okozott. Ha a felhasználó ismét a Törlés gombra kattint, a Törlés sikeres lesz, mivel a GridView eredeti ProductName értéke ("Chai") most megegyezik az adatbázis értékével.

Mindkét esetben a felhasználói élmény messze nem ideális. A kötegfrissítési minta használatakor egyértelműen nem szeretnénk megjeleníteni a felhasználónak a DBConcurrencyException kivétel nitty-gritty részleteit. A közvetlen adatbázis-minta használata kicsit összezavaró, mivel a felhasználó parancsa sikertelenül végrehajtódott, de nem volt pontos indoklás arra vonatkozóan, hogy miért.

A két probléma megoldásához létrehozhatunk címke webvezérlőket a lapon, amelyek magyarázatot adnak arra, hogy miért nem sikerült a frissítés vagy a törlés. A kötegfrissítési minta esetében megállapíthatjuk, hogy történt-e DBConcurrencyException kivétel a GridView posztszintű eseménykezelőjében, és szükség szerint megjelenítjük a figyelmeztető címkét. A közvetlen ADATBÁZIS-metódus esetében megvizsgálhatjuk a BLL-metódus visszatérési értékét (vagyis true ha egy sort érintett, false egyébként), és szükség szerint megjeleníthetünk egy tájékoztató üzenetet.

6. lépés: Tájékoztató üzenetek hozzáadása és megjelenítése egyidejűségi szabálysértés esetén

Egyidejűségi szabálysértés esetén a megjelenített viselkedés attól függ, hogy a DAL kötegelt frissítését vagy közvetlen adatbázismintáját használták-e. Az oktatóanyag mindkét mintát alkalmazza: a frissítéshez a kötegelt frissítési mintát, a törléshez pedig a közvetlen adatbázis-mintát. Első lépésként adjunk hozzá két címke webvezérlőt a lapunkhoz, amelyek azt mutatják, hogy az adatok törlésekor vagy frissítésekor egyidejűség-megsértés történt. Állítsa be a Címke vezérlő Visible és EnableViewState tulajdonságait false értékre; ez azt eredményezi, hogy minden oldallátogatáskor rejtetté válnak, kivéve azokat az oldallátogatásokat, amikor a Visible tulajdonság programozottan true értékre van állítva.

<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." />

Amellett, hogy beállítottam a Visible, EnabledViewState, és Text tulajdonságokat, a CssClass tulajdonságot is Warning-ra állítottam, ami miatt a címkék nagy, piros, dőlt, félkövér betűtípussal jelennek meg. Ez a CSS-osztály Warning a beszúráshoz, frissítéshez és törléshez kapcsolódó események vizsgálata című oktatóanyagban lett definiálva és hozzáadva a Styles.css-hez.

A címkék hozzáadása után a Visual Studio tervezőjének a 18. ábrához hasonlóan kell kinéznie.

Két címkevezérlő lett hozzáadva a laphoz

18. ábra: Két címkevezérlő lett hozzáadva a laphoz (ide kattintva megtekintheti a teljes méretű képet)

A címke webes vezérlőinek használatával megvizsgáljuk, hogyan állapítható meg, hogy mikor történt egyidejűségi szabálysértés. Ekkor a megfelelő címke Visible tulajdonsága beállítható trueaz információs üzenet megjelenítéséhez.

Egyidejűségi szabálysértések kezelése frissítéskor

Először nézzük meg, hogyan kezelhetők az egyidejűségi szabálysértések a kötegelt frissítési minta használatakor. Mivel a kötegfrissítési mintával kapcsolatos ilyen szabálysértések DBConcurrencyException kivételt okoznak, kódot kell hozzáadnunk az ASP.NET oldalhoz annak megállapításához, hogy történt-e DBConcurrencyException kivétel a frissítési folyamat során. Ha igen, akkor egy üzenetet kell jelenítenünk a felhasználónak, amely elmagyarázza, hogy a módosítások nem lettek mentve, mert egy másik felhasználó módosította ugyanazokat az adatokat a rekord szerkesztésekor és a Frissítés gombra való kattintáskor.

Ahogy az ASP.NET lap BLL- és DAL-Level-kivételek kezelése című oktatóanyagában láttuk, ezek a kivételek észlelhetők és letilthatók az adatos webvezérlő post-szintű eseménykezelőiben. Ezért létre kell hoznunk egy eseménykezelőt a GridView eseményéhez RowUpdated , amely ellenőrzi, hogy történt-e DBConcurrencyException kivétel. Ez az eseménykezelő a frissítési folyamat során felmerült kivételre mutató hivatkozást ad át, ahogyan az alábbi eseménykezelő kódban látható:

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

Kivétel esetén ez az eseménykezelő rutin megjeleníti a DBConcurrencyException Címke vezérlőelemet, és jelzi, hogy a kivétel kezelése megtörtént. Ezzel a kóddal, amikor egy rekord frissítésekor egyidejűségi szabálysértés történik, a felhasználó módosításai elvesznek, mivel egy másik felhasználó módosításait felülírták volna egyszerre. A GridView visszakerül az előzetes szerkesztési állapotába, és az aktuális adatbázisadatokhoz van kötve. Ez frissíti a GridView sort a többi felhasználó módosításaival, amelyek korábban nem voltak láthatók. A Címke vezérlőelem emellett UpdateConflictMessage elmagyarázza a felhasználónak, hogy mi történt. Ezt az eseménysorozatot a 19. ábra részletezi.

A felhasználói frissítések elvesznek egy egyidejűségi szabálysértés miatt

19. ábra: A felhasználók frissítései elvesznek egy egyidejűségi szabálysértés miatt (kattintson ide a teljes méretű kép megtekintéséhez)

Megjegyzés:

Másik lehetőségként aHelyett, hogy a GridView-t az előszerkesztési állapotba adnánk vissza, a GridView-t a szerkesztési állapotában hagyhatjuk úgy, hogy az KeepInEditMode átadott GridViewUpdatedEventArgs objektum tulajdonságát igaz értékre állítjuk. Ha azonban ezt a megközelítést alkalmazza, bizonyosnak kell lennie abban, hogy újrakonfigurálja az adatokat a GridView-ba (a metódus meghívásával DataBind() ), hogy a másik felhasználó értékei betöltve legyenek a szerkesztőfelületre. Az oktatóanyaggal letölthető kódban ez a két sornyi kód szerepel az RowUpdated eseménykezelő megjegyzésében; egyszerűen törölje ezeket a kódsorokat, hogy a GridView szerkesztési módban maradjon az egyidejűség megsértése után.

Válasz az egyidejűségi szabálysértésekre törléskor

A közvetlen adatbázis-minta esetén nincs kivétel az egyidejűségi szabálysértés miatt. Ehelyett az adatbázis-utasítás egyszerűen nem érint rekordokat, mivel a WHERE záradék nem egyezik egyetlen rekordtal sem. A BLL-ben létrehozott összes adatmódosítási módszer úgy lett kialakítva, hogy logikai értéket adjanak vissza, amely jelzi, hogy pontosan egy rekordot érintenek-e. Ezért annak megállapításához, hogy egy rekord törlésekor egyidejűségi szabálysértés történt-e, megvizsgálhatjuk a BLL DeleteProduct metódusának visszatérési értékét.

A BLL-metódus visszatérési értéke az ObjectDataSource posztszintű eseménykezelőiben vizsgálható meg az ReturnValue eseménykezelőnek átadott objektum tulajdonságán ObjectDataSourceStatusEventArgs keresztül. Mivel szeretnénk meghatározni a metódus visszatérési DeleteProduct értékét, létre kell hoznunk egy eseménykezelőt az ObjectDataSource eseményéhez Deleted . A ReturnValue tulajdonság típusa objectnull lehet, ha kivétel történt, és a metódus megszakadt, mielőtt értéket adna vissza. Ezért először győződjünk meg arról, hogy a ReturnValue tulajdonság nem null, és hogy logikai érték. Feltételezve, hogy az ellenőrzés megfelel, a DeleteConflictMessage Címke vezérlőelem jelenik meg, ha a ReturnValuefalse. Ez a következő kóddal hajtható végre:

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

Az egyidejűség megsértése esetén a felhasználó törlési kérése megszakad. A Rendszer frissíti a GridView-t, és megjeleníti a rekord módosításait az oldal betöltése és a Törlés gombra való kattintás között. Ha egy ilyen szabálysértés bekövetkezik, megjelenik a DeleteConflictMessage címke, amely elmagyarázza, hogy mi történt (lásd a 20. ábrát).

A felhasználó törlése egyidejűségi szabálysértés miatt megszakad

20. ábra: A felhasználó törlése megszakítva az egyidejűségi szabálysértés miatt (ide kattintva megtekintheti a teljes méretű képet)

Összefoglalás

Az egyidejűségi szabálysértések lehetősége minden alkalmazásban létezik, amely lehetővé teszi több egyidejű felhasználó számára az adatok frissítését vagy törlését. Ha figyelmen kívül hagyják az ilyen ütközéseket, akkor amikor két felhasználó egyszerre frissíti ugyanazt az adatot, az utolsó módosító "nyer", felülírva a másik felhasználó változtatásait. Másik lehetőségként a fejlesztők optimista vagy pesszimista egyidejűség-vezérlést is megvalósíthatnak. Az optimista egyidejűség-vezérlés feltételezi, hogy az egyidejűségi szabálysértések ritkák, és egyszerűen letiltják az egyidejűségi szabálysértést okozó frissítési vagy törlési parancsokat. A pesszimista egyidejűség-vezérlés azt feltételezi, hogy az egyidejűség-szabálysértések gyakoriak, és nem elfogadható egyetlen felhasználó frissítési vagy törlési parancsának elvetése. A pesszimista egyidejűség-vezérléssel a rekord frissítése magában foglalja a zárolást, ezáltal megakadályozza, hogy más felhasználók módosítsák vagy töröljék a rekordot zárolt állapotban.

A .NET-ben a Typed DataSet funkció támogatja az optimista egyidejűség-vezérlést. Az adatbázishoz kiadott utasítások és UPDATE utasítások különösen DELETE tartalmazzák a tábla összes oszlopát, így biztosítva, hogy a frissítés vagy törlés csak akkor történjen meg, ha a rekord aktuális adatai megegyeznek az eredeti adatokkal, amelyek a felhasználó frissítésének vagy törlésének végrehajtásakor voltak. Miután a DAL az optimista egyidejűség támogatására lett konfigurálva, frissíteni kell a BLL-metódusokat. Emellett a BLL-be lehívható ASP.NET lapot úgy kell konfigurálni, hogy az ObjectDataSource lekérje az eredeti értékeket az adat webes vezérlőjéből, és átadja őket a BLL-nek.

Ahogy ebben az oktatóanyagban láttuk, az optimista egyidejűség-vezérlés implementálása egy ASP.NET webalkalmazásban magában foglalja a DAL és a BLL frissítését és a támogatás hozzáadását a ASP.NET oldalon. Az alkalmazástól függ, hogy ez a hozzáadott munka az idő és a munka bölcs befektetése-e. Ha ritkán egyidejű felhasználók frissítik az adatokat, vagy a frissített adatok eltérnek egymástól, akkor az egyidejűség-vezérlés nem kulcsfontosságú probléma. Ha azonban a webhelyen rendszeresen több felhasználó dolgozik ugyanazokkal az adatokkal, az egyidejűség-vezérléssel megakadályozhatja, hogy az egyik felhasználó frissítései vagy törlései véletlenül felülírják a másikat.

Boldog programozást!

Tudnivalók a szerzőről

Scott Mitchell, hét ASP/ASP.NET-könyv szerzője és a 4GuysFromRolla.com alapítója, 1998 óta dolgozik a Microsoft webtechnológiáival. Scott független tanácsadóként, edzőként és íróként dolgozik. Legújabb könyve Sams Tanuld meg ASP.NET 2.0 24 óra alatt. Ő itt elérhető mitchell@4GuysFromRolla.com.