Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
által Scott Mitchell
Ebben az oktatóanyagban bemutatjuk, hogyan központosíthatja üzleti szabályait egy üzleti logikai rétegre (BLL), amely közvetítőként szolgál a bemutató réteg és a DAL közötti adatcseréhez.
Bevezetés
Az első oktatóanyagban létrehozott adatelérési réteg (DAL) tisztán elkülöníti az adatelérési logikát a bemutató logikától. Bár a DAL egyértelműen elkülöníti az adathozzáférési adatokat a bemutató rétegtől, nem kényszeríti ki az esetlegesen alkalmazandó üzleti szabályokat. Előfordulhat például, hogy az alkalmazás esetében le szeretnénk tiltani a CategoryID
tábla vagy SupplierID
mezők Products
módosítását, ha a Discontinued
mező értéke 1, vagy szeretnénk kikényszeríteni a rangidőre vonatkozó szabályokat, tiltva azokat a helyzeteket, amikor egy alkalmazottat olyan személy kezel, akit utána béreltek fel. Egy másik gyakori forgatókönyv az engedélyezés, talán csak az adott szerepkörben lévő felhasználók törölhetik a termékeket, vagy módosíthatják az UnitPrice
értéket.
Ez az oktatóanyag bemutatja, hogyan központosíthatja ezeket az üzleti szabályokat egy üzleti logikai rétegre (BLL), amely közvetítőként szolgál a bemutató réteg és a DAL közötti adatcseréhez. Egy valós alkalmazásban a BLL-t külön Osztálytár-projektként kell megvalósítani; Ezekhez az oktatóanyagokhoz azonban a BLL-t osztálysorozatként fogjuk implementálni a mappánkban, hogy egyszerűsítsük App_Code
a projektstruktúrát. Az 1. ábra a bemutató réteg, a BLL és a DAL architektúrakapcsolatait szemlélteti.
1. ábra: A BLL elkülöníti a bemutató réteget az adatelérési rétegtől, és üzleti szabályokat ír elő
1. lépés: A BLL-osztályok létrehozása
A BLL négy osztályból áll, egyet a DAL minden TableAdapteréhez; A BLL-osztályok mindegyike rendelkezik metódusokkal a megfelelő üzleti szabályok alkalmazásával a megfelelő TableAdapter táblaadapterből való lekérésére, beszúrására, frissítésére és törlésére.
A DAL- és BLL-hez kapcsolódó osztályok pontosabb elkülönítéséhez hozzunk létre két almappát a App_Code
mappában: DAL
és BLL
. Egyszerűen kattintson a jobb gombbal a App_Code
megoldáskezelő mappájára, és válassza az Új mappa lehetőséget. A két mappa létrehozása után helyezze át az első oktatóanyagban létrehozott Typed DataSetet az DAL
almappába.
Ezután hozza létre a négy BLL-osztályfájlt az BLL
almappában. Ehhez kattintson a jobb gombbal az almappára, válassza az BLL
Új elem hozzáadása lehetőséget, és válassza az Osztálysablont. Nevezze el a négy osztályt ProductsBLL
, CategoriesBLL
SuppliersBLL
és EmployeesBLL
.
2. ábra: Négy új osztály hozzáadása a App_Code
mappához
Ezután adjunk hozzá metódusokat az egyes osztályokhoz, hogy egyszerűen becsomagoljuk a TableAdaptershez definiált metódusokat az első oktatóanyagból. Egyelőre ezek a módszerek közvetlenül a DAL-t fogják hívni, később pedig visszatérünk, hogy hozzáadjunk minden szükséges üzleti logikát.
Megjegyzés:
Ha Visual Studio Standard Edition vagy újabb kiadást használ (vagyis nem a Visual Web Developert használja), igény szerint vizuálisan is megtervezheti az osztályokat az Osztálytervezővel. A Visual Studio új funkciójával kapcsolatos további információkért tekintse meg az Osztálytervező blogot .
Az ProductsBLL
osztályhoz összesen hét metódust kell hozzáadnunk:
-
GetProducts()
az összes terméket visszaadja -
GetProductByProductID(productID)
a megadott termékazonosítóval rendelkező terméket adja vissza -
GetProductsByCategoryID(categoryID)
a megadott kategóriából származó összes terméket visszaadja -
GetProductsBySupplier(supplierID)
a megadott szállítótól származó összes terméket visszaadja -
AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued)
új terméket szúr be az adatbázisba a megadott értékekkel; azProductID
újonnan beszúrt rekord értékét adja vissza -
UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID)
frissít egy meglévő terméket az adatbázisban az átadott értékekkel; visszaadjatrue
, ha pontosan egy sor frissült,false
ellenkező esetben -
DeleteProduct(productID)
törli a megadott terméket az adatbázisból
ProductsBLL.cs
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 NorthwindTableAdapters;
[System.ComponentModel.DataObject]
public class ProductsBLL
{
private ProductsTableAdapter _productsAdapter = null;
protected ProductsTableAdapter Adapter
{
get {
if (_productsAdapter == null)
_productsAdapter = new ProductsTableAdapter();
return _productsAdapter;
}
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, true)]
public Northwind.ProductsDataTable GetProducts()
{
return Adapter.GetProducts();
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductByProductID(int productID)
{
return Adapter.GetProductByProductID(productID);
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID)
{
return Adapter.GetProductsByCategoryID(categoryID);
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsBySupplierID(int supplierID)
{
return Adapter.GetProductsBySupplierID(supplierID);
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Insert, true)]
public bool AddProduct(string productName, int? supplierID, int? categoryID,
string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
short? unitsOnOrder, short? reorderLevel, bool discontinued)
{
// Create a new ProductRow instance
Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
Northwind.ProductsRow product = products.NewProductsRow();
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;
// Add the new product
products.AddProductsRow(product);
int rowsAffected = Adapter.Update(products);
// Return true if precisely one row was inserted,
// otherwise false
return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateProduct(string productName, int? supplierID, int? categoryID,
string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
short? unitsOnOrder, short? reorderLevel, bool discontinued, int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
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;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated,
// otherwise false
return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteProduct(int productID)
{
int rowsAffected = Adapter.Delete(productID);
// Return true if precisely one row was deleted,
// otherwise false
return rowsAffected == 1;
}
}
Azok a metódusok, amelyek egyszerűen visszaadják az adatokatGetProducts
, GetProductByProductID
GetProductsByCategoryID
és GetProductBySuppliersID
meglehetősen egyszerűek, mivel egyszerűen lehívják a DAL-t. Bizonyos esetekben előfordulhatnak olyan üzleti szabályok, amelyeket ezen a szinten kell implementálnunk (például az aktuálisan bejelentkezett felhasználó vagy a felhasználó szerepköre alapján történő engedélyezési szabályokat), ezeket a metódusokat egyszerűen as-ishagyjuk. Ezeknél a módszereknél a BLL csupán proxyként szolgál, amelyen keresztül a bemutató réteg hozzáfér a mögöttes adatokhoz az adatelérési rétegből.
Az AddProduct
és UpdateProduct
metódusok mindketten paraméterként használják a különböző termékmezők értékeit, és sorrendben egy új terméket adnak hozzá, illetve frissítenek egy meglévőt. Mivel a Product
tábla számos oszlopa elfogadhat NULL
értékeket (CategoryID
, SupplierID
, és UnitPrice
, hogy csak példát említsünk), azok a bemeneti paraméterek a AddProduct
és UpdateProduct
esetében, amelyek az ilyen oszlopokra vannak leképezve, null értékű típusokat használnak. A null értékű típusok újdonságnak számítanak a .NET 2.0-ban, és olyan technikát biztosítanak, amely azt jelzi, hogy egy értéktípus null értéket vehet-e fel. A C#-ban egy értéktípust null értékű típusként jelölhet meg ?
a típus után (például int? x;
). További információért tekintse meg a C# programozási útmutatóNull értékű típusok szakaszát.
Mindhárom metódus logikai értéket ad vissza, amely jelzi, hogy egy sor beszúrása, frissítése vagy törlése történt-e, mivel a művelet nem eredményez érintett sort. Ha például az oldal fejlesztője nem létező termékhez ad át DeleteProduct
-t, az adatbázisnak kiadott ProductID
utasításnak nem lesz semmilyen hatása, ezért a DELETE
metódus visszatér DeleteProduct
-val.
Vegye figyelembe, hogy új termék hozzáadásakor vagy meglévő frissítésekor az új vagy módosított termék mezőértékeit skaláris listaként vesszük fel, nem pedig egy ProductsRow
példány elfogadásakor. Ez a megközelítés azért lett kiválasztva, mert az osztály a ProductsRow
ADO.NET DataRow
osztályból származik, amely nem rendelkezik alapértelmezett paraméter nélküli konstruktorsal. Egy új ProductsRow
példány létrehozásához először létre kell hoznunk egy példányt ProductsDataTable
, majd meg kell hívnunk annak metódusát NewProductRow()
(ebben a példában AddProduct
). Ez a hiányosság felbukkan, amikor a termékek beszúrásához és frissítéséhez az ObjectDataSource-t használjuk. Röviden: az ObjectDataSource megpróbálja létrehozni a bemeneti paraméterek egy példányát. Ha a BLL metódus egy ProductsRow
példányt vár, az ObjectDataSource megpróbál létrehozni egyet, de mivel hiányzik az alapértelmezett, paraméter nélküli konstruktor, ez nem sikerül. A problémával kapcsolatos további információkért tekintse meg a következő két ASP.NET fórum bejegyzést: Az ObjectDataSources frissítése Strongly-Typed adatkészletekkel, valamint az ObjectDataSource és Strongly-Typed adatkészlettel kapcsolatos probléma.
Ezután mind a AddProduct
-ban, mind a UpdateProduct
-ben a kód létrehoz egy ProductsRow
példányt, és feltölti az imént átadott értékekkel. Amikor a DataRow DataColumnjaihoz rendelünk értékeket, különböző mezőszintű érvényesítési ellenőrzések történhetnek. Ezért az átadott értékeknek a DataRow-ba való manuális visszavétele biztosítja a BLL-metódusnak átadott adatok érvényességét. A Visual Studio által létrehozott, erősen gépelt DataRow-osztályok sajnos nem használnak null értékű típusokat. Ehelyett annak jelzéséhez, hogy a DataRow-ban egy adott DataColumnnak meg kell felelnie egy NULL
adatbázis-értéknek, a SetColumnNameNull()
módszert kell használnunk.
Először betöltjük a terméket a frissítéshez a következővel UpdateProduct
GetProductByProductID(productID)
. Bár ez szükségtelen adatbázislekérdezésnek tűnhet, ez az extra lekérdezés hasznosnak bizonyulni fog az optimista egyidejűséget feltáró jövőbeli oktatóanyagokban. Az optimista egyidejűség egy olyan technika, amellyel biztosítható, hogy az ugyanazon adatokon egyidejűleg dolgozó két felhasználó ne írja felül véletlenül egymás módosításait. A teljes rekord megragadásával egyszerűbben hozhat létre olyan frissítési metódusokat a BLL-ben, amelyek csak a DataRow oszlopainak egy részét módosítják. Amikor felfedezzük az SuppliersBLL
osztályt, egy ilyen példát fogunk látni.
Végül vegye figyelembe, hogy az ProductsBLL
osztályra a DataObject attribútum van alkalmazva (a [System.ComponentModel.DataObject]
szintaxis közvetlenül az osztályutasítás előtt a fájl tetején), és a metódusok DataObjectMethodAttribute attribútumokkal rendelkeznek. Az DataObject
attribútum az osztályt objektumként jelöli, amely alkalmas az ObjectDataSource-vezérlőhöz való kötésre, míg a DataObjectMethodAttribute
metódus célját jelzi. Ahogy a jövőbeli oktatóanyagokban is látni fogjuk, ASP.NET 2.0 ObjectDataSource-jával egyszerűen deklaratív módon érheti el az adatokat egy osztályból. Ha szeretné szűrni az ObjectDataSource varázslójában kötendő lehetséges osztályok listáját, alapértelmezés szerint csak azok az osztályok vannak megjelölve, amelyek DataObjects
a varázsló legördülő listájában láthatók. Az ProductsBLL
osztály ugyanúgy működik ezek nélkül az attribútumok nélkül, de a hozzáadásuk megkönnyíti a munkát az ObjectDataSource varázslójában.
Az egyéb osztályok hozzáadása
Miután a ProductsBLL
osztály elkészült, még hozzá kell adnunk a kategóriákkal, beszállítókkal és alkalmazottakkal való munkához szükséges osztályokat. Szánjon egy kis időt a következő osztályok és metódusok létrehozására a fenti példában szereplő fogalmak alapján:
CategoriesBLL.cs
GetCategories()
GetCategoryByCategoryID(categoryID)
SuppliersBLL.cs
GetSuppliers()
GetSupplierBySupplierID(supplierID)
GetSuppliersByCountry(country)
UpdateSupplierAddress(supplierID, address, city, country)
EmployeesBLL.cs
GetEmployees()
GetEmployeeByEmployeeID(employeeID)
GetEmployeesByManager(managerID)
Az egyetlen módszer, amelyet érdemes megjegyezni, az osztály SuppliersBLL
metódusa UpdateSupplierAddress
. Ez a módszer csak a szállító címadatainak frissítéséhez biztosít felületet. Ez a metódus belsőleg beolvassa a SupplierDataRow
objektumot a megadott supplierID
használatával, beállítja annak címmel kapcsolatos tulajdonságait, majd lehívja a GetSupplierBySupplierID
SupplierDataTable
metódusát. A UpdateSupplierAddress
módszer a következő:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateSupplierAddress
(int supplierID, string address, string city, string country)
{
Northwind.SuppliersDataTable suppliers =
Adapter.GetSupplierBySupplierID(supplierID);
if (suppliers.Count == 0)
// no matching record found, return false
return false;
else
{
Northwind.SuppliersRow supplier = suppliers[0];
if (address == null) supplier.SetAddressNull();
else supplier.Address = address;
if (city == null) supplier.SetCityNull();
else supplier.City = city;
if (country == null) supplier.SetCountryNull();
else supplier.Country = country;
// Update the supplier Address-related information
int rowsAffected = Adapter.Update(supplier);
// Return true if precisely one row was updated,
// otherwise false
return rowsAffected == 1;
}
}
A BLL-osztályok teljes implementációjához tekintse meg a cikk letöltését.
2. lépés: A beírt adathalmazok elérése a BLL-osztályokon keresztül
Az első oktatóanyagban példákat láthattunk arra, hogy a gépelt adathalmaz programozott módon működik, de a BLL-osztályok hozzáadásával a bemutatószintnek inkább a BLL-vel kell működnie.
AllProducts.aspx
Az első oktatóanyagban szereplő példában a ProductsTableAdapter
termékek listáját egy GridView-hoz kötötték, ahogyan az a következő kódban is látható:
ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();
Az új BLL-osztályok használatához mindössze annyit kell módosítani, hogy az első kódsor egyszerűen lecseréli az ProductsTableAdapter
objektumot egy ProductBLL
objektumra:
ProductsBLL productLogic = new ProductsBLL();
GridView1.DataSource = productLogic.GetProducts();
GridView1.DataBind();
A BLL-osztályok deklaratív módon is elérhetők (ahogy a Typed DataSet is) az ObjectDataSource használatával. Az ObjectDataSource-ról részletesebben az alábbi oktatóanyagokban lesz szó.
3. ábra: A termékek listája megjelenik egy GridView-ban (kattintson ide a teljes méretű kép megtekintéséhez)
3. lépés: Field-Level-ellenőrzés hozzáadása a DataRow-osztályokhoz
A mezőszintű ellenőrzések azok az üzleti objektumok tulajdonságértékeivel kapcsolatos vizsgálatok, amelyek beszúráskor vagy frissítésekor történnek. A termékek mezőszintű érvényesítési szabályai közé tartoznak a következők:
- A
ProductName
mező legfeljebb 40 karakter hosszúságú lehet - A
QuantityPerUnit
mező legfeljebb 20 karakter hosszúságú lehet - A
ProductID
,ProductName
ésDiscontinued
a mezők megadása kötelező, de az összes többi mező nem kötelező - A
UnitPrice
,UnitsInStock
,UnitsOnOrder
ésReorderLevel
mezőknek nullánál nagyobbnak vagy egyenlőnek kell lenniük
Ezeket a szabályokat az adatbázis szintjén lehet és kell kifejezni. A ProductName
és QuantityPerUnit
mezők karakterkorlátját a Products
táblázat oszlopainak adattípusai rögzítik (nvarchar(40)
és nvarchar(20)
esetében). Annak kifejezése, hogy a mezők kötelezőek vagy választhatók, attól függ, hogy az adatbázistábla oszlopa engedélyezi-e a NULL
s-t. Négy ellenőrzési feltétel létezik, amelyek biztosítják, hogy csak a nullánál nagyobb vagy egyenlő értékek kerülhessenek a UnitPrice
, UnitsInStock
, UnitsOnOrder
, vagy ReorderLevel
oszlopokba.
Ezen szabályok az adatbázison történő kényszerítése mellett az adathalmaz szintjén is kikényszeríthetők. Valójában minden egyes DataTable esetében a DataColumnok halmazának már rögzítve van a mező hossza, valamint az, hogy egy érték szükséges vagy opcionális. Ha automatikusan meg szeretné tekinteni a meglévő mezőszintű ellenőrzést, nyissa meg az Adatkészlettervezőt, jelöljön ki egy mezőt az egyik Adattáblából, majd lépjen a Tulajdonságok ablakra. A 4. ábrán látható, hogy a QuantityPerUnit
DataColumn ProductsDataTable
legfeljebb 20 karakter hosszúságú, és engedélyezi NULL
az értékeket. Ha megpróbáljuk a ProductsDataRow
QuantityPerUnit
tulajdonságot 20 karakternél hosszabb sztringértékre beállítani, akkor ArgumentException
keletkezik.
4. ábra: A DataColumn alapszintű Field-Level érvényesítést biztosít (kattintson ide a teljes méretű kép megtekintéséhez)
Sajnos a Tulajdonságok ablakban nem adhatók meg a határellenőrzések, például az UnitPrice
értéknek nullánál nagyobbnak vagy egyenlőnek kell lennie. Az ilyen típusú mezőszintű ellenőrzés biztosításához létre kell hoznunk egy eseménykezelőt a DataTable ColumnChanging eseményéhez. Ahogy az előző oktatóanyagban is említettük, a Gépelt adatkészlet által létrehozott DataSet, DataTables és DataRow objektumok részleges osztályok használatával bővíthetők. Ezzel a technikával létrehozhatunk egy eseménykezelőt ColumnChanging
az ProductsDataTable
osztály számára. Először hozzon létre egy osztályt a App_Code
nevű ProductsDataTable.ColumnChanging.cs
mappában.
5. ábra: Új osztály hozzáadása a App_Code
mappához (kattintson ide a teljes méretű kép megtekintéséhez)
Ezután hozzon létre egy eseménykezelőt az ColumnChanging
eseményhez, amely biztosítja, hogy a UnitPrice
, UnitsInStock
, UnitsOnOrder
, és ReorderLevel
oszlop értékei (ha nem NULL
) nullánál nagyobbak vagy egyenlők legyenek. Ha bármelyik ilyen oszlop kívül esik a tartományon, dobja a ArgumentException
-t.
ProductsDataTable.ColumnChanging.cs
public partial class Northwind
{
public partial class ProductsDataTable
{
public override void BeginInit()
{
this.ColumnChanging += ValidateColumn;
}
void ValidateColumn(object sender,
DataColumnChangeEventArgs e)
{
if(e.Column.Equals(this.UnitPriceColumn))
{
if(!Convert.IsDBNull(e.ProposedValue) &&
(decimal)e.ProposedValue < 0)
{
throw new ArgumentException(
"UnitPrice cannot be less than zero", "UnitPrice");
}
}
else if (e.Column.Equals(this.UnitsInStockColumn) ||
e.Column.Equals(this.UnitsOnOrderColumn) ||
e.Column.Equals(this.ReorderLevelColumn))
{
if (!Convert.IsDBNull(e.ProposedValue) &&
(short)e.ProposedValue < 0)
{
throw new ArgumentException(string.Format(
"{0} cannot be less than zero", e.Column.ColumnName),
e.Column.ColumnName);
}
}
}
}
}
4. lépés: Egyéni üzleti szabályok hozzáadása a BLL osztályaihoz
A mezőszintű érvényesítés mellett lehetnek olyan magas szintű egyéni üzleti szabályok, amelyek különböző entitásokat vagy fogalmakat foglalnak magukban, amelyek nem fejezhetők ki egyetlen oszlop szintjén, például:
- Ha egy termék megszűnik, az
UnitPrice
nem frissíthető - A munkavállaló tartózkodási országának meg kell egyeznie a vezető tartózkodási országával
- A termék nem szüntethető meg, ha ez az egyetlen, a szállító által biztosított termék
A BLL-osztályoknak olyan ellenőrzéseket kell tartalmazniuk, amelyek biztosítják az alkalmazás üzleti szabályainak betartását. Ezek az ellenőrzések közvetlenül hozzáadhatók az általuk alkalmazott módszerekhez.
Tegyük fel, hogy az üzleti szabályok azt diktálják, hogy egy termék ne legyen megszüntetve, ha egy adott szállító egyetlen terméke volt. Vagyis ha az X termék volt az egyetlen termék, amelyet az Y szállítótól vásároltunk, akkor nem tudtuk megszüntetni az X jelölését; ha azonban az Y szállító három terméket adott nekünk, A, B és C terméket, akkor ezek bármelyikét megszüntettük. Furcsa üzleti szabály, de az üzleti szabályok és a józan ész nem mindig igazodnak egymáshoz!
Ahhoz, hogy ezt az üzleti szabályt az UpdateProducts
első módszer szerint érvényesítsük, ellenőrizzük, hogy Discontinued
be van-e true
állítva, és ha igen, hívjuk GetProductsBySupplierID
meg, hogy hány terméket vásároltunk a termék szállítójától. Ha csak egy terméket vásárolnak ettől a szállítótól, ejtünk egy ApplicationException
.
public bool UpdateProduct(string productName, int? supplierID, int? categoryID,
string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
short? unitsOnOrder, short? reorderLevel, bool discontinued, int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
// Business rule check - cannot discontinue
// a product that is supplied by only
// one supplier
if (discontinued)
{
// Get the products we buy from this supplier
Northwind.ProductsDataTable productsBySupplier =
Adapter.GetProductsBySupplierID(product.SupplierID);
if (productsBySupplier.Count == 1)
// this is the only product we buy from this supplier
throw new ApplicationException(
"You cannot mark a product as discontinued if it is the only
product purchased from a supplier");
}
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;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated,
// otherwise false
return rowsAffected == 1;
}
Válaszadás a prezentációs réteg érvényesítési hibáira
Amikor meghívjuk a BLL-t a bemutatószintről, eldönthetjük, hogy megkíséreljük-e kezelni az esetlegesen felmerülő kivételeket, vagy hagyjuk, hogy azok átadódjanak az ASP.NET-nek (ami aktiválja a HttpApplication
eseményt Error
). A BLL programozása közben a kivétel kezeléséhez try...catch blokkot használhatunk, amint azt az alábbi példa mutatja.
ProductsBLL productLogic = new ProductsBLL();
// Update information for ProductID 1
try
{
// This will fail since we are attempting to use a
// UnitPrice value less than 0.
productLogic.UpdateProduct(
"Scott s Tea", 1, 1, null, -14m, 10, null, null, false, 1);
}
catch (ArgumentException ae)
{
Response.Write("There was a problem: " + ae.Message);
}
Ahogy a jövőbeli oktatóanyagokban is látni fogjuk, a BLL-ből az adatok beszúrására, frissítésére vagy törlésére szolgáló adat-webvezérlők használata során felmerülő kivételek kezelése közvetlenül egy eseménykezelőben kezelhető, nem pedig blokkokban try...catch
lévő kódfuttatással.
Összefoglalás
Egy jól kialakított alkalmazás különböző rétegekbe van beosztva, amelyek mindegyike egy adott szerepkört foglal magában. A cikksorozat első oktatóanyagában egy adatelérési réteget hoztunk létre gépelt adathalmazok használatával; Ebben az oktatóanyagban egy üzleti logikai réteget hoztunk létre osztálysorozatként az alkalmazás mappájában App_Code
, amely lehívja a DAL-unkat. A BLL implementálja az alkalmazás mezőszintű és üzleti szintű logikáját. Amellett, hogy külön BLL-t hoztunk létre, ahogyan ebben az oktatóanyagban is tettük, egy másik lehetőség a TableAdapters metódusainak kiterjesztése részleges osztályok használatával. Ennek a technikának a használata azonban nem teszi lehetővé a meglévő módszerek felülbírálását, és nem választja el a DAL-t és a BLL-t olyan tiszta módon, mint a jelen cikkben ismertetett megközelítés.
A DAL és a BLL elkészültével készen állunk arra, hogy elkezdjük a prezentációs réteggel való munkát. A következő oktatóanyagban rövid kitérőt teszünk az adathozzáférési témakörökből, és konzisztens lapelrendezést határozunk meg az oktatóanyagok során.
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.
Külön köszönet
Ezt az oktatóanyag-sorozatot sok hasznos véleményező áttekintette. Az oktatóanyag vezető véleményezői Liz Shulok, Dennis Patterson, Carlos Santos és Hilton Giesenow voltak. Szeretné áttekinteni a közelgő MSDN-cikkeimet? Ha igen, írj egy sort a mitchell@4GuysFromRolla.com-ra.