Freigeben über


Erstellen einer Geschäftslogikebene (C#)

von Scott Mitchell

PDF herunterladen

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

Einführung

Die im ersten Tutorial erstellte Datenzugriffsebene (Data Access Layer, DAL) trennt die Datenzugriffslogik sauber von der Präsentationslogik. Obwohl die DAL die Datenzugriffsdetails sauber von der Präsentationsebene trennt, erzwingt sie keine möglicherweise zutreffenden Geschäftsregeln. Für unsere Anwendung möchten wir beispielsweise zulassen, dass die CategoryID Felder oder SupplierID der Products Tabelle geändert werden können, wenn das Discontinued Feld auf 1 festgelegt ist, oder wir möchten Seniority-Regeln erzwingen, indem wir Situationen verbieten, in denen ein Mitarbeiter von einer Person verwaltet wird, die nach ihm eingestellt wurde. Ein weiteres häufiges Szenario ist die Autorisierung, möglicherweise können nur Benutzer in einer bestimmten Rolle Produkte löschen oder den UnitPrice Wert ändern.

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

Die BLL trennt die Präsentationsebene von der Datenzugriffsebene und erzwingt Geschäftsregeln.

Abbildung 1: Die BLL trennt die Präsentationsebene von der Datenzugriffsebene und erzwingt Geschäftsregeln.

Schritt 1: Erstellen der BLL-Klassen

Unser BLL besteht aus vier Klassen, eine 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 in der 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 typisierte DataSet, das im ersten Tutorial 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 und dann die Klasse-Vorlage aus. Nennen Sie die vier Klassen ProductsBLL, CategoriesBLL, SuppliersBLLund EmployeesBLL.

Hinzufügen von vier neuen Klassen zum App_Code Ordner

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

Als Nächstes fügen wir jeder Klasse Methoden hinzu, um einfach die Methoden zu umschließen, die für die TableAdapters aus dem ersten Tutorial definiert wurden. Vorerst rufen diese Methoden nur direkt in die DAL auf. 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 nicht Visual Web Developer), können Sie Ihre Klassen optional mithilfe der Designer Klasse visuell entwerfen. Weitere Informationen zu diesem neuen Feature in Visual Studio finden Sie im Blog "Class Designer".

Für die ProductsBLL -Klasse müssen insgesamt sieben Methoden hinzugefügt werden:

  • 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) alle Produkte des angegebenen Lieferanten zurückgibt
  • 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, , GetProductsByCategoryIDund GetProductBySuppliersID zurückgeben, sind ziemlich einfach, da sie einfach in die DAL aufrufen. Während es in einigen Szenarien Geschäftsregeln geben kann, die auf dieser Ebene implementiert werden müssen (z. B. Autorisierungsregeln, die auf dem aktuell angemeldeten Benutzer oder der Rolle basieren, zu der der Benutzer gehört), lassen wir diese Methoden einfach unverändert. Für diese Methoden dient die BLL dann lediglich als Proxy, über den die Präsentationsebene auf die zugrunde liegenden Daten der Datenzugriffsebene zugreift.

Die AddProduct Methoden und UpdateProduct übernehmen als Parameter die Werte für die verschiedenen Produktfelder und fügen ein neues Produkt bzw. aktualisieren ein vorhandenes Produkt. Da viele Spalten der Product Tabelle Werte akzeptieren NULL können (CategoryID, SupplierIDund UnitPrice, um nur einige zu nennen), verwenden diese Eingabeparameter für AddProduct und UpdateProduct , die diesen Spalten zugeordnet sind , NULLable-Typen. Nullable-Typen sind neu in .NET 2.0 und bieten eine Technik zum Angeben, ob ein Werttyp stattdessen sein nullsoll. In C# können Sie einen Werttyp als Nullable-Typ kennzeichnen, indem Sie ? nach dem Typ hinzufügen (z. B int? x;. ). Weitere Informationen finden Sie im Abschnitt Nullable Types 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 beispielsweise für ein nicht vorhandenes Produkt die Übergabe aufruft DeleteProductProductID , hat die DELETE an die Datenbank ausgegebene Anweisung keine Auswirkungen, und daher gibt die DeleteProduct Methode zurück false.

Beachten Sie, dass beim Hinzufügen eines neuen Produkts oder beim Aktualisieren eines vorhandenen Produkts die Feldwerte des neuen oder geänderten Produkts als Liste von Skalaren verwendet werden, anstatt ein ProductsRow instance zu akzeptieren. Dieser Ansatz wurde gewählt, weil die ProductsRow -Klasse von der ADO.NET-Klasse DataRow abgeleitet ist, die keinen standardparameterlosen Konstruktor aufweist. Um eine neue ProductsRow instance zu erstellen, müssen wir zuerst eine ProductsDataTable instance erstellen und dann dessen NewProductRow() Methode aufrufen (was wir in AddProducttun). Dieser Mangel rückt den Kopf zurück, wenn wir uns mit dem Einfügen und Aktualisieren von Produkten mit ObjectDataSource zuwenden. Kurz gesagt, die ObjectDataSource versucht, eine instance der Eingabeparameter zu erstellen. Wenn die BLL-Methode eine ProductsRow instance erwartet, versucht die ObjectDataSource, eine zu erstellen, schlägt jedoch aufgrund des Fehlens eines parameterlosen Standardkonstruktors 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 erstellt der Code in AddProduct und UpdateProducteine ProductsRow instance und füllt ihn mit den gerade übergebenen Werten auf. Beim Zuweisen von Werten zu DataColumns eines DataRow können verschiedene Überprüfungen auf Feldebene durchgeführt werden. Das manuelle Einfügen der übergebenen Werte in einen DataRow-Code trägt daher dazu bei, die Gültigkeit der Daten, die an die BLL-Methode übergeben werden, sicherzustellen. Leider verwenden die stark typisierten DataRow-Klassen, die von Visual Studio generiert werden, keine NULLable-Typen. Um stattdessen anzugeben, dass ein bestimmter DataColumn in einem DataRow einem NULL Datenbankwert entsprechen soll, müssen wir die SetColumnNameNull() -Methode verwenden.

In UpdateProduct laden wir zuerst in das Produkt, um mit zu aktualisieren GetProductByProductID(productID). Obwohl dies wie eine unnötige Reise in die Datenbank erscheinen mag, wird sich diese zusätzliche Reise in zukünftigen Tutorials als lohnenswert erweisen, in denen die optimistische Parallelität untersucht wird. Die optimistische Parallelität ist eine Technik, mit der sichergestellt wird, dass zwei Benutzer, die gleichzeitig an den gleichen Daten arbeiten, die Änderungen des anderen nicht versehentlich überschreiben. Durch das Abrufen des gesamten Datensatzes wird es auch einfacher, Updatemethoden in der BLL zu erstellen, die nur eine Teilmenge der DataRow-Spalten ändern. Wenn wir die SuppliersBLL Klasse untersuchen, sehen wir ein solches Beispiel.

Beachten Sie schließlich, dass für die ProductsBLL Klasse das DataObject-Attribut angewendet wird (die [System.ComponentModel.DataObject] Syntax direkt vor der Klassenanweisung am Anfang der Datei) und die Methoden dataObjectMethodAttribute-Attribute aufweisen. Das DataObject -Attribut kennzeichnet die -Klasse als ein Objekt, das für die Bindung an ein ObjectDataSource-Steuerelement geeignet ist, während der den DataObjectMethodAttribute Zweck der Methode angibt. Wie wir in zukünftigen Tutorials sehen werden, erleichtert die ObjectDataSource von ASP.NET 2.0 den deklarativen Zugriff auf Daten aus einer Klasse. Um die Liste der möglichen Klassen zu filtern, an die im ObjectDataSource-Assistenten gebunden werden soll, werden standardmäßig nur die Klassen markiert, die in DataObjects der Dropdownliste des Assistenten angezeigt werden. Die ProductsBLL -Klasse funktioniert auch ohne diese Attribute, aber das Hinzufügen dieser Attribute erleichtert die Arbeit im ObjectDataSource-Assistenten.

Hinzufügen der anderen Klassen

Wenn die ProductsBLL Klasse abgeschlossen ist, 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 einzige Methode, die erwähnenswert ist, ist die -Methode der SuppliersBLLUpdateSupplierAddress -Klasse. Diese Methode bietet eine Schnittstelle zum Aktualisieren nur der Adressinformationen des Lieferanten. Intern liest diese Methode das -Objekt für das SupplierDataRow angegebene supplierID (mithilfe von GetSupplierBySupplierID), legt die adressbezogenen Eigenschaften fest und ruft dann die SupplierDataTableMethode "s 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;
    }
}

Meine vollständige Implementierung der BLL-Klassen finden Sie im Download dieses Artikels.

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

Im ersten Tutorial haben wir Beispiele für die programmgesteuerte direkte Arbeit mit dem Typisiertes DataSet gesehen, aber mit dem Hinzufügen unserer BLL-Klassen sollte die Präsentationsebene stattdessen für die BLL funktionieren. Im Beispiel aus AllProducts.aspx dem ersten Tutorial wurde verwendet ProductsTableAdapter , um die Liste der Produkte an eine GridView zu binden, wie im folgenden Code gezeigt:

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

Um die neuen BLL-Klassen zu verwenden, muss lediglich die erste Codezeile geändert werden, indem Sie einfach das ProductsTableAdapter Objekt durch ein ProductBLL -Objekt ersetzen:

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

Auf die BLL-Klassen kann auch deklarativ zugegriffen werden (wie auch auf das Typisierte DataSet) mithilfe der ObjectDataSource. In den folgenden Tutorials wird die ObjectDataSource ausführlicher erläutert.

Die Liste der Produkte wird in einer GridView angezeigt.

Abbildung 3: Die Liste der Produkte wird in einer GridView angezeigt (Klicken Sie hier, um das bild in voller Größe anzuzeigen)

Schritt 3: Hinzufügen Field-Level Validierung zu den DataRow-Klassen

Bei der Überprüfung auf Feldebene handelt es sich um Überprüfungen, die sich auf die Eigenschaftswerte der Geschäftsobjekte beim Einfügen oder Aktualisieren beziehen. Zu den Validierungsregeln auf Feldebene für Produkte gehören:

  • Das ProductName Feld muss mindestens 40 Zeichen lang sein.
  • Das QuantityPerUnit Feld muss mindestens 20 Zeichen lang sein.
  • Die ProductIDFelder , ProductNameund Discontinued sind erforderlich, aber alle anderen Felder sind optional.
  • Die UnitPriceFelder , UnitsInStock, UnitsOnOrderund ReorderLevel müssen größer oder gleich 0 sein.

Diese Regeln können und sollten auf Datenbankebene ausgedrückt werden. Das Zeichenlimit für die ProductName Felder und QuantityPerUnit wird von den Datentypen dieser Spalten in der Products Tabelle (nvarchar(40) bzw nvarchar(20). ) erfasst. Gibt an, ob Felder erforderlich und optional sind, wenn die Datenbanktabellenspalte s zulässt NULL . Es gibt vier Überprüfungseinschränkungen , die sicherstellen, dass nur Werte größer als oder gleich 0 in die UnitPriceSpalten , UnitsInStock, UnitsOnOrderoder ReorderLevel 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 die Angabe, 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 einer der DataTables aus, und wechseln Sie dann zum Eigenschaftenfenster. Wie Abbildung 4 zeigt, hat die QuantityPerUnit DataColumn in eine ProductsDataTable maximale Länge von 20 Zeichen und lässt Werte zu NULL . Wenn wir versuchen, die ProductsDataRow's-Eigenschaft QuantityPerUnit auf einen Zeichenfolgenwert festzulegen, der länger als 20 Zeichen ist, wird ein ArgumentException ausgelöst.

DataColumn bietet grundlegende Field-Level Validierung

Abbildung 4: DataColumn bietet grundlegende Field-Level Validierung (Klicken Sie hier, um das bild in voller Größe anzuzeigen)

Leider können wir keine Begrenzungsprüfungen angeben, z. B. dass der UnitPrice Wert größer als oder gleich 0 sein muss, über den Eigenschaftenfenster. Um diese Art der Validierung auf Feldebene bereitzustellen, müssen wir einen Ereignishandler für das ColumnChanging-Ereignis der DataTable erstellen. Wie im vorherigen Tutorial erwähnt, können die vom Typed DataSet erstellten DataSet-Objekte DataSet, DataTables und DataRow durch die Verwendung von Teilklassen 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 App_Code 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 Ereignis, der ColumnChanging sicherstellt, dass die UnitPriceSpaltenwerte , UnitsInStock, UnitsOnOrderund ReorderLevel (falls nicht NULL) größer oder gleich 0 sind. Wenn sich eine solche Spalte außerhalb des Bereichs befindet, lösen Sie einen aus 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 benutzerdefinierter Geschäftsregeln zu den Klassen der BLL

Zusätzlich zur Validierung auf Feldebene gibt es möglicherweise allgemeine benutzerdefinierte Geschäftsregeln, die verschiedene Entitäten oder Konzepte umfassen, die auf einzelspaltener Ebene nicht ausgedrückt werden können, z. B.:

  • Wenn ein Produkt eingestellt wird, kann es UnitPrice nicht aktualisiert werden.
  • Das Wohnsitzland eines Mitarbeiters muss mit dem Wohnsitzland des Vorgesetzten identisch sein.
  • Ein Produkt kann nicht eingestellt werden, wenn es das einzige Produkt ist, das vom Lieferanten bereitgestellt wird

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

Stellen Sie sich vor, unsere Geschäftsregeln schreiben vor, dass ein Produkt nicht als eingestellt gekennzeichnet werden kann, wenn es das einzige Produkt eines bestimmten Lieferanten war. Das heißt, wenn Produkt X das einzige Produkt war, das wir vom Lieferanten Y erworben haben, könnten wir X nicht als nicht mehr als eingestellt kennzeichnen; wenn uns der Lieferant Y jedoch drei Produkte geliefert hat, A, B und C, dann könnten wir alle als nicht mehr verfügbar kennzeichnen. Eine ungewöhnliche Geschäftsregel, aber Geschäftsregeln und gesunder Menschenverstand sind nicht immer aufeinander abgestimmt!

Um diese Geschäftsregel in der UpdateProducts Methode zu erzwingen, würden wir zunächst überprüfen, ob Discontinued auf true festgelegt wurde, und wenn ja, rufen wir auf GetProductsBySupplierID , um zu ermitteln, wie viele Produkte wir vom Lieferanten dieses Produkts erworben haben. Wenn nur ein Produkt von diesem Lieferanten gekauft wird, lösen wir einen aus 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 Präsentationsebene

Beim Aufrufen der BLL von der Präsentationsebene können wir entscheiden, ob wir versuchen, ausnahmen zu behandeln, die ausgelöst werden könnten, oder ob sie bis zu ASP.NET blasen (wodurch das Ereignis des HttpApplicationEreignisses Error ausgelöst wird). Um eine Ausnahme beim programmgesteuerten Arbeiten mit dem BLL zu behandeln, können wir einen Versuch verwenden... 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, kann die Behandlung von Ausnahmen, die von der BLL bei Verwendung eines Datenwebsteuerelements zum Einfügen, Aktualisieren oder Löschen von Daten verwendet werden, direkt in einem Ereignishandler behandelt werden, anstatt Code in try...catch Blöcken umschließen zu müssen.

Zusammenfassung

Eine gut gestaltete Anwendung wird in unterschiedliche Ebenen erstellt, von denen jede eine bestimmte Rolle kapselt. Im ersten Tutorial dieser Artikelreihe haben wir eine Datenzugriffsebene mit typisierten DataSets erstellt. In diesem Tutorial haben wir eine Geschäftslogikebene als eine Reihe von Klassen im Ordner unserer Anwendung App_Code erstellt, die unsere DAL aufrufen. Die BLL implementiert die Logik auf Feld- und Geschäftsebene für unsere Anwendung. Neben dem Erstellen einer separaten BLL, wie in diesem Tutorial, besteht eine weitere Option darin, die TableAdapters-Methoden durch die Verwendung von partiellen Klassen zu erweitern. Die Verwendung dieser Technik ermöglicht es uns jedoch nicht, vorhandene Methoden außer Kraft zu setzen, und es ist nicht möglich, unseren DAL und unseren BLL so sauber zu trennen wie der Ansatz, den wir in diesem Artikel verfolgt haben.

Nachdem DAL und BLL abgeschlossen sind, können wir mit unserer Präsentationsebene beginnen. Im nächsten Tutorial machen wir einen kurzen Umweg zu Datenzugriffsthemen und definieren ein konsistentes Seitenlayout für die Verwendung in den Tutorials.

Viel Spaß beim Programmieren!

Zum Autor

Scott Mitchell, Autor von sieben ASP/ASP.NET-Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft-Webtechnologien. Scott arbeitet als unabhängiger Berater, Trainer und Autor. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Stunden. Er kann unter mitchell@4GuysFromRolla.comoder über seinen Blog erreicht werden, der unter http://ScottOnWriting.NETzu finden ist.

Besonderen Dank an

Diese Tutorialreihe wurde von vielen hilfreichen Prüfern überprüft. Leitende Prüfer für dieses Tutorial waren Liz Shulok, Dennis Patterson, Carlos Santos und Hilton Giesenow. Möchten Sie meine anstehenden MSDN-Artikel lesen? Wenn dies der Fall ist, legen Sie eine Zeile unter abmitchell@4GuysFromRolla.com.