Freigeben über


Erstellen einer Geschäftslogikebene (C#)

von Scott Mitchell

PDF herunterladen

In diesem Lernprogramm erfahren Sie, wie Sie Ihre Geschäftsregeln in eine Business Logic Layer (BLL) zentralisieren, die als Vermittler für den Datenaustausch zwischen der Präsentationsebene und dem DAL dient.

Einleitung

Die im ersten Lernprogramm erstellte Datenzugriffsebene (Data Access Layer, DAL) trennt die Datenzugriffslogik sauber von der Präsentationslogik. Während die DAL jedoch die Datenzugriffsdetails sauber von der Präsentationsebene trennt, erzwingt sie keine Geschäftsregeln, die gelten können. Für unsere Anwendung möchten wir z. B. die Änderungen der CategoryID Felder oder SupplierID Felder der Products Tabelle möglicherweise nicht zulassen, wenn das Discontinued Feld auf 1 festgelegt ist, oder wir möchten möglicherweise Dienstalterregelungen durchsetzen, um Situationen zu verhindern, in denen ein Mitarbeiter von jemandem betreut wird, der später eingestellt wurde. Ein weiteres gängiges Szenario ist die Autorisierung, vielleicht können nur Benutzer in einer bestimmten Rolle Produkte löschen oder den UnitPrice Wert ändern.

In diesem Lernprogramm erfahren Sie, wie Sie diese Geschäftsregeln in eine Business Logic Layer (BLL) zentralisieren, die als Vermittler für den Datenaustausch zwischen der Präsentationsschicht und dem DAL dient. In einer realen Anwendung sollte die BLL als separates Klassenbibliotheksprojekt implementiert werden; Für diese Lernprogramme implementieren wir die BLL jedoch als Eine Reihe von Klassen in unserem App_Code Ordner, um die Projektstruktur zu vereinfachen. Abbildung 1 veranschaulicht die Architekturbeziehungen zwischen der Präsentationsebene, BLL und DAL.

Die BLL trennt die Präsentationsschicht von der Datenzugriffsschicht und legt Geschäftsregeln fest.

Abbildung 1: Die BLL trennt die Präsentationsschicht von der Datenzugriffsschicht und legt Geschäftsregeln fest.

Schritt 1: Erstellen der BLL-Klassen

Unsere BLL besteht aus vier Klassen, einer für jeden TableAdapter im DAL; Jede dieser BLL-Klassen verfügt über Methoden zum Abrufen, Einfügen, Aktualisieren und Löschen aus dem jeweiligen TableAdapter im DAL, wobei die entsprechenden Geschäftsregeln angewendet werden.

Um die DAL- und BLL-bezogenen Klassen sauberer zu trennen, erstellen wir zwei Unterordner im App_Code Ordner und DALBLL. Klicken Sie einfach mit der rechten Maustaste auf den App_Code Ordner im Projektmappen-Explorer, und wählen Sie "Neuer Ordner" aus. Verschieben Sie nach dem Erstellen dieser beiden Ordner das typierte DataSet, das im ersten Lernprogramm erstellt wurde, in den DAL Unterordner.

Erstellen Sie als Nächstes die vier BLL-Klassendateien im BLL Unterordner. Klicken Sie dazu mit der rechten Maustaste auf den BLL Unterordner, wählen Sie "Neues Element hinzufügen" aus, und wählen Sie dann die Vorlage "Klasse" aus. Benennen Sie die vier Klassen ProductsBLL, CategoriesBLL, SuppliersBLLund EmployeesBLL.

Hinzufügen von vier neuen Klassen zum ordner App_Code

Abbildung 2: Hinzufügen von vier neuen Klassen zum App_Code Ordner

Als Nächstes fügen wir den einzelnen Klassen Methoden hinzu, um die Methoden, die für die TableAdapters im ersten Tutorial definiert sind, einfach zu umwickeln. Diese Methoden werden vorerst nur direkt in die DAL aufrufen; wir kehren später zurück, um die erforderliche Geschäftslogik hinzuzufügen.

Hinweis

Wenn Sie Visual Studio Standard Edition oder höher verwenden (d. h., Sie verwenden Visual Web Developer nicht), können Sie ihre Klassen optional visuell mit dem Klassen-Designer entwerfen. Weitere Informationen zu diesem neuen Feature in Visual Studio finden Sie im Class Designer-Blog .

Für die ProductsBLL Klasse müssen wir insgesamt sieben Methoden hinzufügen:

  • GetProducts() gibt alle Produkte zurück.
  • GetProductByProductID(productID) gibt das Produkt mit der angegebenen Produkt-ID zurück.
  • GetProductsByCategoryID(categoryID) gibt alle Produkte aus der angegebenen Kategorie zurück.
  • GetProductsBySupplier(supplierID) gibt alle Produkte des angegebenen Lieferanten zurück.
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) fügt ein neues Produkt mithilfe der übergebenen Werte in die Datenbank ein; gibt den ProductID Wert des neu eingefügten Datensatzes zurück.
  • UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID) aktualisiert ein vorhandenes Produkt in der Datenbank mithilfe der übergebenen Werte; gibt zurück true , wenn genau eine Zeile aktualisiert wurde, false andernfalls
  • DeleteProduct(productID) löscht das angegebene Produkt aus der Datenbank.

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

Die Methoden, die einfach Daten GetProducts, GetProductByProductID, GetProductsByCategoryID und GetProductBySuppliersID zurückgeben, sind ziemlich einfach, da sie lediglich Methoden des DAL aufrufen. In einigen Szenarien gibt es möglicherweise Geschäftsregeln, die auf dieser Ebene implementiert werden müssen (z. B. Autorisierungsregeln basierend auf dem aktuell angemeldeten Benutzer oder der Rolle, zu der der Benutzer gehört), wir lassen diese Methoden einfach as-is. Für diese Methoden dient die BLL dann lediglich als Proxy, über den die Präsentationsschicht auf die zugrunde liegenden Daten von der Datenzugriffsebene zugreift.

Die AddProduct Methoden UpdateProduct übernehmen beide als Parameter die Werte für die verschiedenen Produktfelder und fügen ein neues Produkt hinzu oder aktualisieren ein vorhandenes Produkt. Da viele Spalten der Product-Tabelle Werte wie NULL, CategoryID, SupplierID und UnitPrice akzeptieren können, verwenden die Eingabeparameter für AddProduct und UpdateProduct, die solchen Spalten zugeordnet sind, nullable Typen. Nullable-Typen sind neu in .NET 2.0 und stellen eine Technik bereit, die angibt, ob ein Werttyp stattdessen sein nullsoll. In C# können Sie einen Werttyp als Typ kennzeichnen, der NULL-Werte zulässt, indem Sie nach dem Typ (wie int? x;) hinzufügen?. Weitere Informationen finden Sie im Abschnitt NULLABLE-Typen im C#-Programmierhandbuch .

Alle drei Methoden geben einen booleschen Wert zurück, der angibt, ob eine Zeile eingefügt, aktualisiert oder gelöscht wurde, da der Vorgang möglicherweise nicht zu einer betroffenen Zeile führt. Wenn der Seitenentwickler z. B. DeleteProduct aufruft und dabei ein ProductID für ein nicht vorhandenes Produkt übergibt, hat die an die Datenbank ausgegebene DELETE-Anweisung keine Auswirkungen und daher gibt die DeleteProduct Methode false zurück.

Beachten Sie, dass wir beim Hinzufügen eines neuen Produkts oder beim Aktualisieren eines vorhandenen Produkts die Feldwerte des neuen oder geänderten Produkts als Liste von Skalaren anstelle einer ProductsRow Instanz erfassen. Dieser Ansatz wurde ausgewählt, da die ProductsRow Klasse von der ADO.NET DataRow Klasse abgeleitet ist, die keinen standardparameterlosen Konstruktor aufweist. Um eine neue ProductsRow Instanz zu erstellen, müssen wir zuerst eine ProductsDataTable Instanz erstellen und dann ihre NewProductRow() Methode aufrufen, was wir in AddProduct tun. Dieses Manko tritt in Erscheinung, wenn wir Produkte mit der ObjectDataSource einfügen und aktualisieren. Kurz gesagt versucht objectDataSource, eine Instanz der Eingabeparameter zu erstellen. Wenn die BLL-Methode eine ProductsRow Instanz erwartet, versucht Die ObjectDataSource, eine instanz zu erstellen, schlägt jedoch aufgrund des Fehlens eines standardparameterlosen Konstruktors fehl. Weitere Informationen zu diesem Problem finden Sie in den folgenden beiden ASP.NET Forenbeiträgen: Aktualisieren von ObjectDataSources mit Strongly-Typed DataSets und Problem mit ObjectDataSource und Strongly-Typed DataSet.

Als Nächstes wird sowohl in AddProduct als auch in UpdateProduct eine ProductsRow-Instanz vom Code erstellt und mit den soeben übergebenen Werten gefüllt. Beim Zuweisen von Werten zu DataColumns eines DataRow können verschiedene Validierungsprüfungen auf Feldebene auftreten. Daher trägt das manuelle Einfügen der übergebenen Werte in ein DataRow-Objekt dazu bei, die Gültigkeit der an die BLL-Methode übergebenen Daten sicherzustellen. Leider verwenden die stark typisierten DataRow-Klassen, die von Visual Studio generiert werden, keine Nullable-Typen. Um anzugeben, dass eine bestimmte Datenspalte in einer Datenzeile einem NULL Datenbankwert entsprechen sollte, müssen wir die SetColumnNameNull() Methode verwenden.

In UpdateProduct laden wir zuerst das Produkt, um es mit GetProductByProductID(productID) zu aktualisieren. Auch wenn dies wie eine unnötige Datenbankanfrage scheint, wird sich diese zusätzliche Anfrage in zukünftigen Tutorials als lohnend erweisen, die optimistische Parallelität erkunden. Optimistische Parallelität ist eine Technik, um sicherzustellen, dass zwei Benutzer, die gleichzeitig an denselben Daten arbeiten, nicht versehentlich die Änderungen eines anderen überschreiben. Das Abrufen des gesamten Datensatzes erleichtert auch das Erstellen von Aktualisierungsmethoden in der BLL, die nur eine Teilmenge der Spalten von DataRow ändern. Wenn wir die SuppliersBLL Klasse untersuchen, werden wir ein solches Beispiel sehen.

Beachten Sie schließlich, dass die ProductsBLL Klasse das DataObject-Attribut zugewiesen hat (die [System.ComponentModel.DataObject] Syntax direkt vor der Klassenanweisung am oberen Rand der Datei), und die Methoden weisen DataObjectMethodAttribute-Attribute auf. Das DataObject Attribut kennzeichnet die Klasse als objekt, das für die Bindung an ein ObjectDataSource-Steuerelement geeignet ist, während der DataObjectMethodAttribute Zweck der Methode angegeben wird. Wie wir in zukünftigen Lernprogrammen sehen werden, erleichtert ASP.NET 2.0 ObjectDataSource den deklarativen Zugriff auf Daten aus einer Klasse. Um die Liste der möglichen Klassen, die im Assistenten von ObjectDataSource gebunden werden sollen, zu filtern, werden standardmäßig nur die Klassen angezeigt, die als DataObjects markiert sind und in der Dropdownliste des Assistenten erscheinen. Die ProductsBLL Klasse funktioniert genauso gut ohne diese Attribute, aber das Hinzufügen erleichtert das Arbeiten mit dem Assistenten von ObjectDataSource.

Hinzufügen der anderen Klassen

Nach Abschluss der ProductsBLL Klasse müssen wir weiterhin die Klassen für die Arbeit mit Kategorien, Lieferanten und Mitarbeitern hinzufügen. Nehmen Sie sich einen Moment Zeit, um die folgenden Klassen und Methoden mithilfe der Konzepte aus dem obigen Beispiel zu erstellen:

  • CategoriesBLL.cs

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

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

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

Die eine Methode, die zu notieren ist, ist die Methode der SuppliersBLL Klasse UpdateSupplierAddress . Diese Methode stellt eine Schnittstelle zum Aktualisieren nur der Adressinformationen des Lieferanten bereit. Intern liest diese Methode das SupplierDataRow-Objekt für die angegebene supplierID (unter Verwendung von GetSupplierBySupplierID) ein, legt die adressbezogenen Eigenschaften fest und ruft dann die Methode SupplierDataTable von Update auf. Die UpdateSupplierAddress Methode folgt:

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

Bitte konsultieren Sie den Download dieses Artikels für meine vollständige Implementierung der BLL-Klassen.

Schritt 2: Zugreifen auf die typierten DataSets über die BLL-Klassen

Im ersten Tutorial haben wir Beispiele für die programmatische Arbeit mit dem Typed DataSet gesehen, aber mit der Einführung unserer BLL-Klassen sollte die Präsentationsschicht stattdessen mit der BLL interagieren. AllProducts.aspx Im Beispiel aus dem ersten Lernprogramm wurde die ProductsTableAdapter Liste der Produkte an ein GridView gebunden, wie im folgenden Code gezeigt:

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

Um die neuen BLL-Klassen zu verwenden, müssen Sie lediglich die erste Codezeile ändern, indem Sie das ProductsTableAdapter Objekt durch ein ProductBLL Objekt ersetzen.

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

Auf die BLL-Klassen kann, ebenso wie auf das Typed DataSet, deklarativ zugegriffen werden, indem die ObjectDataSource verwendet wird. In den folgenden Lernprogrammen werden wir die ObjectDataSource ausführlicher besprechen.

Die Liste der Produkte wird in einer GridView angezeigt.

Abbildung 3: Die Liste der Produkte wird in einer GridView angezeigt (Zum Anzeigen des Bilds mit voller Größe klicken)

Schritt 3: Hinzufügen der Field-Level-Validierung für die DataRow-Klassen

Bei der Überprüfung auf Feldebene handelt es sich um Überprüfungen, die sich auf die Eigenschaftswerte der Geschäftsobjekte beziehen, wenn sie eingefügt oder aktualisiert werden. Einige Gültigkeitsprüfungsregeln auf Feldebene für Produkte umfassen:

  • Das ProductName Feld muss maximal 40 Zeichen lang sein.
  • Das QuantityPerUnit Feld muss maximal 20 Zeichen lang sein.
  • Die ProductIDFelder ProductNameund Discontinued Felder sind erforderlich, aber alle anderen Felder sind optional.
  • Die Felder UnitPrice, UnitsInStock, UnitsOnOrder und ReorderLevel müssen größer oder gleich Null sein.

Diese Regeln können und sollten auf Datenbankebene ausgedrückt werden. Die Zeichenbeschränkung in den Feldern ProductName und QuantityPerUnit wird durch die Datentypen dieser Spalten in der Products-Tabelle erfasst (nvarchar(40) bzw. nvarchar(20)). Gibt an, ob Felder erforderlich und optional angegeben werden, wenn die Datenbanktabellenspalte s zulässt NULL . Es existieren vier Prüfeinschränkungen, die sicherstellen, dass nur Werte größer als oder gleich Null in die UnitPrice, UnitsInStock, UnitsOnOrder oder ReorderLevel Spalten gelangen können.

Zusätzlich zum Erzwingen dieser Regeln in der Datenbank sollten sie auch auf DataSet-Ebene erzwungen werden. Tatsächlich werden die Feldlänge und ob ein Wert erforderlich oder optional ist, bereits für jeden DataTable-Satz von DataColumns erfasst. Um die vorhandene Überprüfung auf Feldebene automatisch anzuzeigen, wechseln Sie zum DataSet-Designer, wählen Sie ein Feld aus einem der DataTables aus, und wechseln Sie dann zum Eigenschaftenfenster. Wie in Abbildung 4 gezeigt, hat der QuantityPerUnit DataColumn im ProductsDataTable eine maximale Länge von 20 Zeichen und erlaubt NULL Werte. Wenn wir versuchen, die Eigenschaft von ProductsDataRow auf einen Zeichenfolgenwert festzulegen, der länger als 20 Zeichen ist, wird ein QuantityPerUnit ausgelöst.

Die DataColumn bietet eine grundlegende Field-Level-Validierung

Abbildung 4: Das DataColumn stellt eine einfache Field-Level Überprüfung bereit (Klicken Sie, um das Bild in voller Größe anzuzeigen)

Leider können wir keine Begrenzungsprüfungen angeben, wie zum Beispiel, dass der UnitPrice-Wert größer oder gleich Null sein muss, über das Eigenschaftenfenster. Um diese Art von Überprüfung auf Feldebene bereitzustellen, müssen wir einen Ereignishandler für das ColumnChanging-Ereignis der DataTable erstellen. Wie im vorherigen Lernprogramm erwähnt, können die dataSet-, DataTables- und DataRow-Objekte, die vom Typed DataSet erstellt wurden, über die Verwendung partieller Klassen erweitert werden. Mit dieser Technik können wir einen ColumnChanging Ereignishandler für die ProductsDataTable Klasse erstellen. Erstellen Sie zunächst eine Klasse im Ordner mit dem App_Code Namen ProductsDataTable.ColumnChanging.cs.

Hinzufügen einer neuen Klasse zum ordner

Abbildung 5: Hinzufügen einer neuen Klasse zum App_Code Ordner (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)

Erstellen Sie als Nächstes einen Ereignishandler für das ColumnChanging-Ereignis, der sicherstellt, dass die Werte der UnitPrice, UnitsInStock, UnitsOnOrder und ReorderLevel-Spalten (wenn nicht NULL) größer oder gleich null sind. Wenn eine solche Spalte außerhalb des zulässigen Bereichs liegt, werfen Sie ein 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);
                }
            }
         }
    }
}

Schritt 4: Hinzufügen von benutzerdefinierten Geschäftsregeln zu den BLL-Klassen

Zusätzlich zur Überprüfung auf Feldebene können benutzerdefinierte Geschäftsregeln auf hoher Ebene vorhanden sein, die verschiedene Entitäten oder Konzepte umfassen, die nicht auf der Ebene einer einzelnen Spalte ausgedrückt werden können, z. B.:

  • Wenn ein Produkt eingestellt wird, kann sein UnitPrice nicht aktualisiert werden.
  • Das Wohnsitzland eines Arbeitnehmers muss mit dem Wohnsitzland seines Vorgesetzten übereinstimmen.
  • Ein Produkt kann nicht eingestellt werden, wenn es sich um das einzige Produkt handelt, das vom Lieferanten bereitgestellt wird.

Die BLL-Klassen sollten Prüfungen enthalten, um die Einhaltung der Geschäftsregeln der Anwendung sicherzustellen. Diese Prüfungen können direkt zu den Methoden hinzugefügt werden, auf die sie angewendet werden.

Stellen Sie sich vor, unsere Geschäftsregeln würden vorschreiben, dass ein Produkt nicht als ausgelaufen markiert werden darf, wenn es das einzige Produkt eines bestimmten Lieferanten ist. Wenn das Produkt X das einzige Produkt war, das wir von Lieferanten Y erworben haben, konnten wir X nicht als nicht mehr eingestellt kennzeichnen; Wenn der Lieferant Y uns jedoch drei Produkte, A, B und C, geliefert hat, könnten wir alle diese als nicht mehr eingestellt kennzeichnen. Eine merkwürdige Geschäftsregel, aber Geschäftsregeln und gesunder Menschenverstand stimmen nicht immer überein!

Um diese Geschäftsregel in der UpdateProducts Methode zu erzwingen, überprüfen wir zunächst, ob Discontinued auf true gesetzt wurde, und falls dies der Fall ist, würden wir GetProductsBySupplierID aufrufen, um festzustellen, wie viele Produkte wir vom Lieferanten dieses Produkts erworben haben. Wenn nur ein Produkt von diesem Lieferanten gekauft wird, werfen wir einen 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;
}

Reagieren auf Validierungsfehler in der Darstellungsschicht

Beim Aufrufen der BLL aus der Präsentationsschicht können wir entscheiden, ob wir versuchen sollen, Ausnahmen zu behandeln, die ausgelöst werden können, oder sie an ASP.NET weiterreichen (wodurch das HttpApplicationError-Ereignis ausgelöst wird). Um eine Ausnahme bei der programmgesteuerten Arbeit mit der BLL zu behandeln, können wir einen try... catch-Block , wie das folgende Beispiel zeigt:

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

Wie wir in zukünftigen Tutorials sehen werden, können Ausnahmen, die sich von der BLL propagieren, direkt in einem Ereignishandler behandelt werden, wenn ein Daten-Websteuerelement zum Einfügen, Aktualisieren oder Löschen von Daten verwendet wird, anstatt den Code in try...catch-Blöcke einwickeln zu müssen.

Zusammenfassung

Eine gut gestaltete Anwendung wird in unterschiedlichen Ebenen erstellt, von denen jede eine bestimmte Rolle kapselt. Im ersten Lernprogramm dieser Artikelreihe haben wir eine Datenzugriffsebene mit typierten DataSets erstellt. In diesem Lernprogramm haben wir eine Business Logic Layer als eine Reihe von Klassen im Ordner unserer Anwendung App_Code erstellt, die in unserem DAL aufrufen. Die BLL implementiert die Logik auf Feld- und Geschäftsebene für unsere Anwendung. Neben dem Erstellen einer separaten BLL, wie wir es in diesem Lernprogramm getan haben, besteht eine weitere Option darin, die Methoden der TableAdapters mit Hilfe von partiellen Klassen zu erweitern. Die Verwendung dieser Technik erlaubt es uns jedoch nicht, vorhandene Methoden zu überschreiben, noch trennt sie unsere DAL und unsere BLL so sauber wie der in diesem Artikel beschriebene Ansatz.

Mit dal und BLL sind wir bereit, auf unserer Präsentationsebene zu beginnen. Im nächsten Lernprogramm nehmen wir einen kurzen Umweg von Datenzugriffsthemen und definieren ein einheitliches Seitenlayout für die Verwendung in den Lernprogrammen.

Glückliche Programmierung!

Zum Autor

Scott Mitchell, Autor von sieben ASP/ASP.NET Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft Web Technologies zusammen. Scott arbeitet als unabhängiger Berater, Trainer und Schriftsteller. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Stunden. Er kann bei mitchell@4GuysFromRolla.comerreicht werden.

Besonderer Dank an

Diese Lernprogrammreihe wurde von vielen hilfreichen Prüfern überprüft. Leitende Prüfer für dieses Lernprogramm waren Liz Shulok, Dennis Patterson, Carlos Santos und Hilton Giesenow. Möchten Sie meine bevorstehenden MSDN-Artikel überprüfen? Wenn ja, schicken Sie mir eine Nachricht an mitchell@4GuysFromRolla.com.