Delen via


Een bedrijfslogicalaag maken (C#)

door Scott Mitchell

PDF downloaden

In deze zelfstudie leert u hoe u uw bedrijfsregels kunt centraliseren in een BLL (Business Logic Layer) die fungeert als intermediair voor gegevensuitwisseling tussen de presentatielaag en de DAL.

Introductie

De Data Access Layer (DAL) die in de eerste zelfstudie is gemaakt, scheidt de logica voor gegevenstoegang op een schone manier van de presentatielogica. Hoewel de DAL de gegevenstoegangsgegevens van de presentatielaag op schone wijze scheidt, worden er geen bedrijfsregels afgedwongen die van toepassing kunnen zijn. Voor onze toepassing willen we bijvoorbeeld het wijzigen van de CategoryID velden van de SupplierID tabel weigeren wanneer het Products veld is ingesteld op 1, of misschien willen we senioriteitsregels afdwingen, waardoor situaties worden verboden waarin een werknemer wordt beheerd door iemand die erna is ingehuurdDiscontinued. Een ander veelvoorkomend scenario is autorisatie mogelijk dat alleen gebruikers met een bepaalde rol producten kunnen verwijderen of de UnitPrice waarde kunnen wijzigen.

In deze zelfstudie zien we hoe u deze bedrijfsregels kunt centraliseren in een BLL (Business Logic Layer) die fungeert als intermediair voor gegevensuitwisseling tussen de presentatielaag en de DAL. In een echte toepassing moet de BLL worden geïmplementeerd als een afzonderlijk klassebibliotheekproject; Voor deze zelfstudies implementeren we de BLL echter als een reeks klassen in onze App_Code map om de projectstructuur te vereenvoudigen. Afbeelding 1 illustreert de architectuurrelaties tussen de presentatielaag, BLL en DAL.

De BLL scheidt de presentatielaag van de Gegevenstoegangslaag en legt bedrijfsregels op

Afbeelding 1: De BLL scheidt de presentatielaag van de Gegevenstoegangslaag en legt bedrijfsregels op

Stap 1: De BLL-klassen maken

Onze BLL bestaat uit vier klassen, één voor elke TableAdapter in de DAL; elk van deze BLL-klassen heeft methoden voor het ophalen, invoegen, bijwerken en verwijderen van de respectieve TableAdapter in de DAL, waarbij de juiste bedrijfsregels worden toegepast.

Als u de DAL- en BLL-gerelateerde klassen beter wilt scheiden, maken we twee submappen in de App_Code map DAL en BLL. Klik met de rechtermuisknop op de App_Code map in Solution Explorer en kies Nieuwe map. Nadat u deze twee mappen hebt gemaakt, verplaatst u de getypte gegevensset die u in de eerste zelfstudie hebt gemaakt naar de DAL submap.

Maak vervolgens de vier BLL-klassebestanden in de BLL submap. Hiervoor klikt u met de rechtermuisknop op de BLL submap, kiest u Een nieuw item toevoegen en kiest u de klassesjabloon. Noem de vier klassen ProductsBLL, CategoriesBLLen SuppliersBLLEmployeesBLL.

Vier nieuwe klassen toevoegen aan de map App_Code

Afbeelding 2: Vier nieuwe klassen toevoegen aan de App_Code map

Vervolgens gaan we methoden toevoegen aan elk van de klassen om eenvoudig de methoden te verpakken die zijn gedefinieerd voor tableAdapters uit de eerste zelfstudie. Op dit moment zullen deze methoden gewoon rechtstreeks in de DAL worden aangeroepen; We keren later terug om eventuele benodigde bedrijfslogica toe te voegen.

Opmerking

Als u Visual Studio Standard Edition of hoger gebruikt (u gebruikt dus geen Visual Web Developer), kunt u eventueel uw klassen visueel ontwerpen met class Designer. Raadpleeg het blog Class Designer voor meer informatie over deze nieuwe functie in Visual Studio.

Voor de ProductsBLL klasse moeten we in totaal zeven methoden toevoegen:

  • GetProducts() retourneert alle producten
  • GetProductByProductID(productID) retourneert het product met de opgegeven product-id
  • GetProductsByCategoryID(categoryID) retourneert alle producten uit de opgegeven categorie
  • GetProductsBySupplier(supplierID) retourneert alle producten van de opgegeven leverancier
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) voegt een nieuw product in de database in met behulp van de waarden die zijn doorgegeven; retourneert de ProductID waarde van de nieuw ingevoegde record
  • UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID) werkt een bestaand product in de database bij met behulp van de doorgegeven waarden; retourneert true als precies één rij is bijgewerkt, false anders
  • DeleteProduct(productID) verwijdert het opgegeven product uit de database

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

De methoden die simpelweg gegevens GetProductsretourneren, GetProductByProductIDGetProductsByCategoryIDen GetProductBySuppliersID zijn vrij eenvoudig omdat ze simpelweg in de DAL worden aangeroepen. In sommige scenario's kunnen er bedrijfsregels zijn die op dit niveau moeten worden geïmplementeerd (zoals autorisatieregels op basis van de momenteel aangemelde gebruiker of de rol waartoe de gebruiker behoort), laten we deze methoden gewoon as-is. Voor deze methoden dient de BLL dan alleen als een proxy waarmee de presentatielaag toegang heeft tot de onderliggende gegevens uit de Data Access-laag.

De AddProduct en UpdateProduct methoden nemen beide op als parameters de waarden voor de verschillende productvelden en voegen een nieuw product toe of werken een bestaand product bij. Omdat veel van de kolommen van de Product tabel waarden kunnen accepteren NULL (CategoryIDSupplierIDenUnitPrice, om er een paar te noemen), gebruiken die invoerparameters voor AddProduct en UpdateProduct die worden toegewezen aan dergelijke kolommen null-typen. Null-typen zijn nieuw voor .NET 2.0 en bieden een techniek om aan te geven of een waardetype in plaats daarvan moet zijn null. In C# kunt u een waardetype markeren als een null-type door het toe te voegen ? na het type (zoals int? x;). Raadpleeg de sectie Nullable Types in de C#-programmeerhandleiding voor meer informatie.

Alle drie de methoden retourneren een Booleaanse waarde die aangeeft of een rij is ingevoegd, bijgewerkt of verwijderd, omdat de bewerking mogelijk niet resulteert in een betrokken rij. Als de paginaontwikkelaar bijvoorbeeld DeleteProduct aanroept met een ProductID voor een niet-bestaand product, zal de DELETE-instructie die aan de database is uitgegeven geen effect hebben en zal de DeleteProduct-methode false retourneren.

Houd er rekening mee dat wanneer u een nieuw product toevoegt of een bestaand product bijwerkt, de veldwaarden van het nieuwe of gewijzigde product worden opgenomen als een lijst met scalaire waarden in plaats van een ProductsRow exemplaar te accepteren. Deze methode is gekozen omdat de ProductsRow klasse is afgeleid van de ADO.NET-klasse DataRow , die geen standaardconstructor zonder parameter heeft. Om een nieuw ProductsRow exemplaar te maken, moeten we eerst een ProductsDataTable exemplaar maken en vervolgens de NewProductRow() methode aanroepen (wat we doen).AddProduct Deze tekortkoming komt naar voren wanneer we producten invoegen en bijwerken met behulp van de ObjectDataSource. Kortom, de ObjectDataSource probeert een exemplaar van de invoerparameters te maken. Als de BLL-methode een ProductsRow exemplaar verwacht, probeert de ObjectDataSource er een te maken, maar mislukt deze vanwege het ontbreken van een standaardconstructor zonder parameter. Raadpleeg de volgende twee ASP.NET Forums-berichten voor meer informatie over dit probleem: ObjectDataSources bijwerken met Strongly-Typed DataSets en Probleem met ObjectDataSource en Strongly-Typed DataSet.

Vervolgens wordt in beide AddProduct en UpdateProduct, de code een ProductsRow instantie gemaakt en gevuld met de waarden die zojuist zijn doorgegeven. Bij het toewijzen van waarden aan DataColumns van een DataRow kunnen verschillende validatiecontroles op veldniveau plaatsvinden. Het handmatig terugzetten van de doorgegeven waarden in een DataRow helpt de geldigheid van de gegevens te waarborgen die naar de BLL-methode worden doorgegeven. Helaas gebruiken de sterk getypte DataRow-klassen die door Visual Studio worden gegenereerd geen nullbare typen. Om aan te geven dat een bepaalde DataColumn in een DataRow moet overeenkomen met een NULL databasewaarde, moeten we de SetColumnNameNull() methode gebruiken.

In UpdateProduct laden we eerst het product dat moet worden bijgewerkt met behulp van GetProductByProductID(productID). Hoewel dit misschien een onnodige reis naar de database lijkt, zal deze extra reis de moeite waard blijken in toekomstige zelfstudies die optimistische gelijktijdigheid verkennen. Optimistische gelijktijdigheid is een techniek om ervoor te zorgen dat twee gebruikers die tegelijkertijd aan dezelfde gegevens werken, niet per ongeluk de wijzigingen van elkaar overschrijven. Door de hele record te pakken, is het ook eenvoudiger om updatemethoden te maken in de BLL die alleen een subset van de kolommen van DataRow wijzigen. Wanneer we de SuppliersBLL klas verkennen, zien we zo'n voorbeeld.

Houd er ten slotte rekening mee dat voor de ProductsBLL klasse het DataObject-kenmerk is toegepast (de [System.ComponentModel.DataObject] syntaxis vlak vóór de klasse-instructie boven aan het bestand) en dat de methoden DataObjectMethodAttribute-kenmerken hebben. Het DataObject kenmerk markeert de klasse als een object dat geschikt is voor binding met een ObjectDataSource-besturingselement, terwijl het DataObjectMethodAttribute doel van de methode aangeeft. Zoals we in toekomstige zelfstudies zien, maakt ASP.NET ObjectDataSource van 2.0 het eenvoudig om gegevens van een klasse declaratief te openen. Om te helpen de lijst met mogelijke klassen om aan te binden in de wizard ObjectDataSource te filteren, worden standaard alleen de klassen weergegeven die als DataObjects gemarkeerd zijn in de vervolgkeuzelijst van de wizard. De ProductsBLL klasse werkt net zo goed als zonder deze kenmerken, maar als u ze toevoegt, is het eenvoudiger om met de wizard ObjectDataSource te werken.

De andere klassen toevoegen

Nu de ProductsBLL klas is voltooid, moeten we nog steeds de klassen toevoegen voor het werken met categorieën, leveranciers en werknemers. Neem even de tijd om de volgende klassen en methoden te maken met behulp van de concepten uit het bovenstaande voorbeeld:

  • CategoriesBLL.cs

    • GetCategories()
    • GetCategoryByCategoryID(categoryID)
  • SuppliersBLL.cs

    • GetSuppliers()
    • GetSupplierBySupplierID(supplierID)
    • GetSuppliersByCountry(country)
    • UpdateSupplierAddress(supplierID, address, city, country)
  • EmployeesBLL.cs

    • GetEmployees()
    • GetEmployeeByEmployeeID(employeeID)
    • GetEmployeesByManager(managerID)

Een methode die het vermelden waard is, is de SuppliersBLL klasse's UpdateSupplierAddress methode. Deze methode biedt een interface voor het bijwerken van alleen de adresgegevens van de leverancier. Intern werkt deze methode het SupplierDataRow object voor het opgegeven supplierID in (met behulp van GetSupplierBySupplierID), stelt het de adresgerelateerde eigenschappen in en roept vervolgens de SupplierDataTable-methode van Update aan. De UpdateSupplierAddress methode volgt:

[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;
    }
}

Raadpleeg de download van dit artikel voor mijn volledige implementatie van de BLL-klassen.

Stap 2: Toegang tot de getypte gegevenssets via de BLL-klassen

In de eerste zelfstudie hebben we voorbeelden gezien van het programmatisch werken met de Getypte DataSet, maar met de toevoeging van onze BLL-klassen moet de presentatielaag in plaats daarvan tegen de BLL werken. In het AllProducts.aspx voorbeeld uit de eerste zelfstudie is de ProductsTableAdapter lijst met producten gekoppeld aan een GridView, zoals wordt weergegeven in de volgende code:

ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();

Als u de nieuwe BLL-klassen wilt gebruiken, hoeft u alleen maar de eerste regel code te vervangen door ProductsTableAdapter een ProductBLL object:

ProductsBLL productLogic = new ProductsBLL();
GridView1.DataSource = productLogic.GetProducts();
GridView1.DataBind();

De BLL-klassen kunnen ook declaratief worden geopend (net als de getypte gegevensset) met behulp van de ObjectDataSource. In de volgende zelfstudies bespreken we de ObjectDataSource in meer detail.

De lijst met producten wordt weergegeven in een GridView

Afbeelding 3: De lijst met producten wordt weergegeven in een rasterweergave (klik om de afbeelding op volledige grootte weer te geven)

Stap 3: Field-Level-validatie toevoegen aan de DataRow-klassen

Validatie op veldniveau is controles die betrekking hebben op de eigenschapswaarden van de bedrijfsobjecten bij het invoegen of bijwerken. Enkele validatieregels op veldniveau voor producten zijn:

  • Het ProductName veld moet 40 tekens of minder lang zijn
  • Het QuantityPerUnit veld moet 20 tekens of minder lang zijn
  • De ProductIDvelden , ProductNameen Discontinued velden zijn vereist, maar alle andere velden zijn optioneel
  • De UnitPricevelden , UnitsInStocken UnitsOnOrderReorderLevel velden moeten groter zijn dan of gelijk zijn aan nul

Deze regels kunnen en moeten worden uitgedrukt op databaseniveau. De tekenlimiet voor de ProductName en QuantityPerUnit velden worden vastgelegd door de gegevenstypen van die kolommen in de Products tabel (nvarchar(40) en nvarchar(20)respectievelijk). Of velden verplicht of optioneel zijn, komt tot uiting wanneer de databasetabelkolom NULL toestaat. Er zijn vier controlebeperkingen die ervoor zorgen dat alleen waarden groter dan of gelijk aan nul kunnen worden opgeslagen in de UnitPrice, UnitsInStock, UnitsOnOrder of ReorderLevel kolommen.

Naast het afdwingen van deze regels in de database, moeten ze ook worden afgedwongen op datasetniveau. De veldlengte en of een waarde vereist of optioneel is, worden al vastgelegd voor de set DataColumns van elke gegevenstabel. Als u de bestaande validatie op veldniveau automatisch wilt zien, gaat u naar DataSet Designer, selecteert u een veld in een van de gegevenstabellen en gaat u vervolgens naar het venster Eigenschappen. Zoals in afbeelding 4 wordt weergegeven, heeft de QuantityPerUnit DataColumn in de ProductsDataTable een maximale lengte van 20 tekens en staat het NULL waarden toe. Als we proberen de ProductsDataRow-eigenschap van QuantityPerUnit in te stellen op een tekenreeks die langer is dan 20 tekens, wordt er een ArgumentException opgegooid.

De DataColumn biedt basisvalidatie voor Field-Level

Afbeelding 4: De DataColumn biedt basisvalidatie Field-Level (klik om de afbeelding op volledige grootte weer te geven)

Helaas kunnen we geen grenscontroles opgeven, zoals dat de UnitPrice waarde groter dan of gelijk aan nul moet zijn, via het Eigenschappen venster. Als u dit type validatie op veldniveau wilt opgeven, moet u een gebeurtenis-handler maken voor de gebeurtenis ColumnChanging van de DataTable. Zoals vermeld in de voorgaande zelfstudie, kunnen de DataSet-, DataTables- en DataRow-objecten die door de Getypte DataSet zijn gemaakt, worden uitgebreid met behulp van gedeeltelijke klassen. Met deze techniek kunnen we een ColumnChanging gebeurtenis-handler voor de ProductsDataTable klasse maken. Begin met het maken van een klasse in de map met de App_Code naam ProductsDataTable.ColumnChanging.cs.

Een nieuwe klasse toevoegen aan de map App_Code

Afbeelding 5: Een nieuwe klasse toevoegen aan de App_Code map (klik om de afbeelding op volledige grootte weer te geven)

Maak vervolgens een gebeurtenis-handler voor de ColumnChanging gebeurtenis die ervoor zorgt dat de UnitPrice, UnitsInStock, UnitsOnOrder, en ReorderLevel kolomwaarden (indien niet NULL) groter dan of gelijk aan nul zijn. Als een dergelijke kolom buiten het bereik valt, gooit u een ArgumentException.

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

Stap 4: Aangepaste bedrijfsregels toevoegen aan de klassen van BLL

Naast validatie op veldniveau zijn er mogelijk aangepaste bedrijfsregels op hoog niveau waarbij verschillende entiteiten of concepten niet worden weergegeven op één kolomniveau, zoals:

  • Als een product wordt stopgezet, kan het UnitPrice niet worden bijgewerkt
  • Het land van verblijf van een werknemer moet hetzelfde zijn als het land van verblijf van de manager
  • Een product kan niet worden stopgezet als het het enige product is dat door de leverancier wordt geleverd

De BLL-klassen moeten controles bevatten om ervoor te zorgen dat de bedrijfsregels van de toepassing worden nageleefd. Deze controles kunnen rechtstreeks worden toegevoegd aan de methoden waarop ze van toepassing zijn.

Stel dat onze bedrijfsregels bepalen dat een product niet als beëindigd kan worden gemarkeerd als het het enige product van een bepaalde leverancier is. Als product X het enige product was dat we bij leverancier Y hebben gekocht, konden we X niet markeren als stopgezet; Als leverancier Y ons echter drie producten, A, B en C heeft geleverd, kunnen we deze als stopgezet markeren. Een oneven bedrijfsregel, maar bedrijfsregels en gezond verstand zijn niet altijd uitgelijnd!

Als we deze bedrijfsregel willen afdwingen in de UpdateProducts-methode, controleren we eerst of Discontinued is ingesteld op true en zo ja, roepen we GetProductsBySupplierID aan om te bepalen hoeveel producten we hebben gekocht bij de leverancier van het product. Als er slechts één product wordt gekocht bij deze leverancier, gooien we een 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;
}

Reageren op validatiefouten in de presentatielaag

Wanneer u de BLL aanroept vanuit de presentatielaag, kunnen we beslissen of we eventuele opgegooide uitzonderingen proberen af te handelen of dat we ze naar ASP.NET laten doorbubbelen (waardoor het HttpApplication-gebeurtenis van Error wordt gegenereerd). Als u een uitzondering wilt afhandelen bij het programmatisch werken met de BLL, kunt u een try...catch blok gebruiken, zoals het volgende voorbeeld laat zien.

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

Zoals we in toekomstige zelfstudies zullen zien, kunnen uitzonderingen die uit de BLL worden doorgestuurd bij het gebruik van een webbesturingselement voor het invoegen, bijwerken of verwijderen van gegevens, rechtstreeks in een gebeurtenishandler worden verwerkt in plaats van dat code in try...catch blokken moet worden verpakt.

Samenvatting

Een goed ontworpen toepassing is ontworpen in verschillende lagen, die elk een bepaalde rol inkapselen. In de eerste zelfstudie van deze artikelenreeks hebben we een Data Access-laag gecreëerd met behulp van getypeerde gegevenssets. In deze zelfstudie hebben we een bedrijfslogische laag gebouwd als een reeks klassen in de map App_Code van onze toepassing, die naar onze DLA verwijzen. De BLL implementeert de logica op veldniveau en bedrijfsniveau voor onze toepassing. Naast het maken van een afzonderlijke BLL, zoals we in deze zelfstudie hebben gedaan, is een andere optie om de methoden van TableAdapters uit te breiden via het gebruik van gedeeltelijke klassen. Door deze techniek te gebruiken, kunnen we echter geen bestaande methoden overschrijven en kunnen we ook onze DAL en BLL niet zo helder scheiden als met de benadering die we in dit artikel hebben gebruikt.

Nu de DAL en BLL zijn voltooid, zijn we klaar om aan de slag te gaan met onze presentatielaag. In de volgende zelfstudie volgen we een korte omleiding van onderwerpen over Data Access en definiëren we een consistente pagina-indeling voor gebruik in de zelfstudies.

Veel plezier met programmeren!

Over de auteur

Scott Mitchell, auteur van zeven ASP/ASP.NET-boeken en oprichter van 4GuysFromRolla.com, werkt sinds 1998 met Microsoft-webtechnologieën. Scott werkt als onafhankelijk consultant, trainer en schrijver. Zijn laatste boek is Sams Teach Yourself ASP.NET 2.0 in 24 uur. Hij kan worden bereikt op mitchell@4GuysFromRolla.com.

Speciale dank aan

Deze tutorialreeks is beoordeeld door veel behulpzame beoordelers. Hoofdbeoordelaars voor deze zelfstudie waren Liz Shulok, Dennis Patterson, Carlos Santos en Hilton Giesenow. Bent u geïnteresseerd in het bekijken van mijn aanstaande MSDN-artikelen? Zo ja, laat iets van je horen via mitchell@4GuysFromRolla.com.