Freigeben über


Erstellen einer Geschäftslogikschicht (VB)

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.

Anstatt separate Klassen zu erstellen, um unsere Geschäftslogik zu implementieren, könnten wir diese Logik alternativ direkt im Typed DataSet mit partiellen Klassen platzieren. Ein Beispiel für das Erstellen und Erweitern eines typisierten DataSets finden Sie im ersten Lernprogramm.

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.vb

Imports NorthwindTableAdapters

<System.ComponentModel.DataObject()> _
Public Class ProductsBLL

    Private _productsAdapter As ProductsTableAdapter = Nothing
    Protected ReadOnly Property Adapter() As ProductsTableAdapter
        Get
            If _productsAdapter Is Nothing Then
                _productsAdapter = New ProductsTableAdapter()
            End If

            Return _productsAdapter
        End Get
    End Property

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, True)> _
    Public Function GetProducts() As Northwind.ProductsDataTable
        Return Adapter.GetProducts()
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductByProductID(ByVal productID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductByProductID(productID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductsByCategoryID(ByVal categoryID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductsByCategoryID(categoryID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductsBySupplierID(ByVal supplierID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductsBySupplierID(supplierID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Insert, True)> _
    Public Function AddProduct( _
        productName As String, supplierID As Nullable(Of Integer), _
        categoryID As Nullable(Of Integer), quantityPerUnit As String, _
        unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
        unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
        discontinued As Boolean) _
        As Boolean

        Dim products As New Northwind.ProductsDataTable()
        Dim product As Northwind.ProductsRow = products.NewProductsRow()

        product.ProductName = productName
        If Not supplierID.HasValue Then
            product.SetSupplierIDNull()
        Else
            product.SupplierID = supplierID.Value
        End If

        If Not categoryID.HasValue Then
            product.SetCategoryIDNull()
        Else
            product.CategoryID = categoryID.Value
        End If

        If quantityPerUnit Is Nothing Then
            product.SetQuantityPerUnitNull()
        Else
            product.QuantityPerUnit = quantityPerUnit
        End If

        If Not unitPrice.HasValue Then
            product.SetUnitPriceNull()
        Else
            product.UnitPrice = unitPrice.Value
        End If

        If Not unitsInStock.HasValue Then
            product.SetUnitsInStockNull()
        Else
            product.UnitsInStock = unitsInStock.Value
        End If

        If Not unitsOnOrder.HasValue Then
            product.SetUnitsOnOrderNull()
        Else
            product.UnitsOnOrder = unitsOnOrder.Value
        End If

        If Not reorderLevel.HasValue Then
            product.SetReorderLevelNull()
        Else
            product.ReorderLevel = reorderLevel.Value
        End If

        product.Discontinued = discontinued

        products.AddProductsRow(product)
        Dim rowsAffected As Integer = Adapter.Update(products)

        Return rowsAffected = 1
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Update, True)> _
    Public Function UpdateProduct(_
        productName As String, supplierID As Nullable(Of Integer), _
        categoryID As Nullable(Of Integer), quantityPerUnit As String, _
        unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
        unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
        discontinued As Boolean, productID As Integer) _
        As Boolean

        Dim products As Northwind.ProductsDataTable = _
            Adapter.GetProductByProductID(productID)

        If products.Count = 0 Then
            Return False
        End If

        Dim product as Northwind.ProductsRow = products(0)

        product.ProductName = productName
        If Not supplierID.HasValue Then
            product.SetSupplierIDNull()
        Else
            product.SupplierID = supplierID.Value
        End If

        If Not categoryID.HasValue Then
            product.SetCategoryIDNull()
        Else
            product.CategoryID = categoryID.Value
        End If

        If quantityPerUnit Is Nothing Then
            product.SetQuantityPerUnitNull()
        Else
            product.QuantityPerUnit = quantityPerUnit
        End If

        If Not unitPrice.HasValue Then
            product.SetUnitPriceNull()
        Else
            product.UnitPrice = unitPrice.Value
        End If

        If Not unitsInStock.HasValue Then
            product.SetUnitsInStockNull()
        Else
            product.UnitsInStock = unitsInStock.Value
        End If

        If Not unitsOnOrder.HasValue Then
            product.SetUnitsOnOrderNull()
        Else
            product.UnitsOnOrder = unitsOnOrder.Value
        End If

        If Not reorderLevel.HasValue Then
            product.SetReorderLevelNull()
        Else
            product.ReorderLevel = reorderLevel.Value
        End If

        product.Discontinued = discontinued

        Dim rowsAffected As Integer = Adapter.Update(product)

        Return rowsAffected = 1
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Delete, True)> _
    Public Function DeleteProduct(ByVal productID As Integer) As Boolean
        Dim rowsAffected As Integer = Adapter.Delete(productID)

        Return rowsAffected = 1
    End Function
End Class

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 Nothingsoll. Weitere Informationen finden Sie im Blogeintrag "The Truth About Nullable Types and VB" vonPaul Vick und in der technischen Dokumentation zur Nullable-Struktur.

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 Function UpdateSupplierAddress(ByVal supplierID As Integer, _
    ByVal address As String, ByVal city As String, ByVal country As String) _
    As Boolean

    Dim suppliers As Northwind.SuppliersDataTable = _
        Adapter.GetSupplierBySupplierID(supplierID)

    If suppliers.Count = 0 Then
        Return False
    Else
        Dim supplier As Northwind.SuppliersRow = suppliers(0)

        If address Is Nothing Then
            supplier.SetAddressNull()
        Else
            supplier.Address = address
        End If

        If city Is Nothing Then
            supplier.SetCityNull()
        Else
            supplier.City = city
        End If

        If country Is Nothing Then
            supplier.SetCountryNull()
        Else
            supplier.Country = country
        End If

        Dim rowsAffected As Integer = Adapter.Update(supplier)

        Return rowsAffected = 1
    End If
End Function

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:

Dim productsAdapter As 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.

Dim productLogic As 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.vb.

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.vb

Imports System.data

Partial Public Class Northwind
    Partial Public Class ProductsDataTable
        Public Overrides Sub BeginInit()
            AddHandler Me.ColumnChanging, AddressOf ValidateColumn
        End Sub

        Sub ValidateColumn(sender As Object, e As DataColumnChangeEventArgs)
            If e.Column.Equals(Me.UnitPriceColumn) Then
                If Not Convert.IsDBNull(e.ProposedValue) AndAlso _
                    CType(e.ProposedValue, Decimal) < 0 Then
                    Throw New ArgumentException( _
                        "UnitPrice cannot be less than zero", "UnitPrice")
                End If
            ElseIf e.Column.Equals(Me.UnitsInStockColumn) OrElse _
                e.Column.Equals(Me.UnitsOnOrderColumn) OrElse _
                e.Column.Equals(Me.ReorderLevelColumn) Then
                If Not Convert.IsDBNull(e.ProposedValue) AndAlso _
                    CType(e.ProposedValue, Short) < 0 Then
                    Throw New ArgumentException(String.Format( _
                        "{0} cannot be less than zero", e.Column.ColumnName), _
                        e.Column.ColumnName)
                End If
            End If
        End Sub
    End Class
End Class

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.

<System.ComponentModel.DataObjectMethodAttribute_
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct( _
    productName As String, supplierID As Nullable(Of Integer), _
    categoryID As Nullable(Of Integer), quantityPerUnit As String, _
    unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
    unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
    discontinued As Boolean, productID As Integer) _
    As Boolean

    Dim products As Northwind.ProductsDataTable = _
        Adapter.GetProductByProductID(productID)

    If products.Count = 0 Then
        Return False
    End If

    Dim product As Northwind.ProductsRow = products(0)

    If discontinued Then
        Dim productsBySupplier As Northwind.ProductsDataTable = _
            Adapter.GetProductsBySupplierID(product.SupplierID)

        If productsBySupplier.Count = 1 Then
            Throw New ApplicationException( _
                "You cannot mark a product as discontinued if it is " & _
                "the only product purchased from a supplier")
        End If
    End If

    product.ProductName = productName

    If Not supplierID.HasValue Then
        product.SetSupplierIDNull()
    Else
        product.SupplierID = supplierID.Value
    End If

    If Not categoryID.HasValue Then
        product.SetCategoryIDNull()
    Else
        product.CategoryID = categoryID.Value
    End If

    If quantityPerUnit Is Nothing Then
        product.SetQuantityPerUnitNull()
    Else
        product.QuantityPerUnit = quantityPerUnit
    End If

    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If

    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If

    If Not unitsOnOrder.HasValue Then
        product.SetUnitsOnOrderNull()
    Else
        product.UnitsOnOrder = unitsOnOrder.Value
    End If

    If Not reorderLevel.HasValue Then
        product.SetReorderLevelNull()
    Else
        product.ReorderLevel = reorderLevel.Value
    End If

    product.Discontinued = discontinued

    Dim rowsAffected As Integer = Adapter.Update(product)

    Return rowsAffected = 1
End Function

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 beim programmgesteuerten Arbeiten mit der BLL zu behandeln, können wir einen Try...Catch Block verwenden, wie das folgende Beispiel zeigt:

Dim productLogic As New ProductsBLL()

Try
    productLogic.UpdateProduct("Scotts Tea", 1, 1, Nothing, _
      -14, 10, Nothing, Nothing, False, 1)
Catch ae As ArgumentException
    Response.Write("There was a problem: " & ae.Message)
End Try

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, schreiben Sie mir an mitchell@4GuysFromRolla.com.