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


Üzleti logikai réteg létrehozása (C#)

által Scott Mitchell

PDF letöltése

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.

A BLL elkülöníti a bemutatóréteget az adatelérési rétegtől, és üzleti szabályokat ír elő

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, CategoriesBLLSuppliersBLLés EmployeesBLL.

Négy új osztály hozzáadása a App_Code mappához

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; az ProductID ú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; visszaadja true, 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, GetProductByProductIDGetProductsByCategoryIDé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 UpdateProductGetProductByProductID(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 GetSupplierBySupplierIDSupplierDataTable 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ó.

A termékek listája megjelenik egy GridView-ban

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és Discontinued a mezők megadása kötelező, de az összes többi mező nem kötelező
  • A UnitPrice, UnitsInStock, UnitsOnOrderés ReorderLevel 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 ProductsDataRowQuantityPerUnit tulajdonságot 20 karakternél hosszabb sztringértékre beállítani, akkor ArgumentException keletkezik.

A DataColumn alapszintű Field-Level-ellenőrzést biztosít

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.csmappában.

Új osztály hozzáadása a App_Code mappához

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.