Udostępnij za pośrednictwem


Tworzenie warstwy logiki biznesowej (VB)

Autor : Scott Mitchell

Pobierz plik PDF

W tym samouczku dowiesz się, jak centralizować reguły biznesowe w warstwie logiki biznesowej (BLL), która służy jako pośrednik do wymiany danych między warstwą prezentacji a warstwą dostępu do danych (DAL).

Wprowadzenie

Warstwa dostępu do danych (DAL) utworzona w pierwszym samouczku wyraźnie oddziela logikę dostępu do danych od logiki prezentacji. Jednak, chociaż DAL czysto oddziela szczegóły dostępu do danych od warstwy prezentacji, nie wymusza on żadnych reguł biznesowych, które mogą mieć zastosowanie. Na przykład, dla naszej aplikacji, możemy chcieć nie zezwalać na modyfikowanie pól CategoryID lub SupplierID tabeli Products, gdy pole Discontinued jest ustawione na 1, lub możemy chcieć wymusić reguły seniorstwa, zakazując sytuacji, w których pracownik jest zarządzany przez osobę zatrudnioną później niż on. Innym typowym scenariuszem jest autoryzacja, prawdopodobnie tylko użytkownicy w określonej roli mogą usuwać produkty lub zmieniać UnitPrice wartość.

W tym samouczku zobaczymy, jak scentralizować te reguły biznesowe w Warstwie Logiki Biznesowej (BLL), która służy jako pośrednik do wymiany danych między warstwą prezentacji a DAL. W rzeczywistej aplikacji BLL należy zaimplementować jako oddzielny projekt Biblioteki klas; Jednak w przypadku tych samouczków wdrożymy BLL jako serię klas w naszym App_Code folderze, aby uprościć strukturę projektu. Rysunek 1 ilustruje relacje architektury między warstwą prezentacji, BLL i DAL.

BLL oddziela warstwę prezentacji od warstwy dostępu do danych i nakłada reguły biznesowe

Rysunek 1. BLL oddziela warstwę prezentacji od warstwy dostępu do danych i nakłada reguły biznesowe

Zamiast tworzyć oddzielne klasy w celu zaimplementowania logiki biznesowej, możemy również umieścić tę logikę bezpośrednio w typowym zestawie danych z klasami częściowymi. Aby zapoznać się z przykładem tworzenia i rozszerzania Typed DataSet, odwołaj się do pierwszego samouczka.

Krok 1. Tworzenie klas BLL

Nasza BLL będzie składać się z czterech klas, po jednej dla każdego TableAdaptera w DAL; każda z tych klas BLL będzie zawierać metody pobierania, wstawiania, aktualizowania i usuwania z odpowiedniego obiektu TableAdapter w DAL, stosując odpowiednie reguły biznesowe.

Aby bardziej czysto oddzielić klasy związane z DAL i BLL, utwórzmy dwa podfoldery w folderze App_Code, DAL i BLL. Po prostu kliknij prawym przyciskiem myszy App_Code folder w Eksploratorze rozwiązań i wybierz pozycję Nowy folder. Po utworzeniu tych dwóch folderów przenieś Typed DataSet utworzony w pierwszym samouczku do podfolderu DAL.

Następnie utwórz cztery pliki klas BLL w BLL podfolderze. Aby to osiągnąć, kliknij prawym przyciskiem myszy na podfolder BLL, wybierz polecenie Dodaj nowy element i wybierz szablon Klasa. Nazwij cztery klasy ProductsBLL, CategoriesBLL, SuppliersBLLi EmployeesBLL.

Dodawanie czterech nowych klas do folderu App_Code

Rysunek 2. Dodawanie czterech nowych klas do App_Code folderu

Następnie dodajmy metody do każdej z klas, aby po prostu opakowywać metody zdefiniowane dla elementów TableAdapters z pierwszego samouczka. Na razie te metody będą po prostu wywoływać bezpośrednio DAL. Wrócimy później, aby dodać dowolną wymaganą logikę biznesową.

Uwaga / Notatka

Jeśli używasz programu Visual Studio Standard Edition lub nowszego (czyli nie używasz programu Visual Web Developer), możesz opcjonalnie wizualnie zaprojektować klasy przy użyciu projektanta klas. Aby uzyskać więcej informacji na temat tej nowej funkcji w programie Visual Studio, zapoznaj się z blogem projektanta klas .

ProductsBLL Dla klasy musimy dodać łącznie siedem metod:

  • GetProducts() zwraca wszystkie produkty
  • GetProductByProductID(productID) zwraca produkt z określonym identyfikatorem produktu
  • GetProductsByCategoryID(categoryID) zwraca wszystkie produkty z określonej kategorii
  • GetProductsBySupplier(supplierID) zwraca wszystkie produkty od określonego dostawcy
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) Wstawia nowy produkt do bazy danych przy użyciu przekazanych wartości; ProductID zwraca wartość nowo wstawionego rekordu
  • UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID) aktualizuje istniejący produkt w bazie danych przy użyciu przekazanych wartości; zwraca True , jeśli dokładnie jeden wiersz został zaktualizowany, False w przeciwnym razie
  • DeleteProduct(productID) usuwa określony produkt z bazy danych

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

Metody, które po prostu zwracają dane GetProducts, GetProductByProductID, GetProductsByCategoryID, i GetProductBySuppliersID są dość proste, ponieważ po prostu wywołują operacje w DAL. W niektórych scenariuszach mogą istnieć reguły biznesowe, które należy zaimplementować na tym poziomie (takie jak reguły autoryzacji na podstawie aktualnie zalogowanego użytkownika lub roli, do której należy użytkownik), po prostu pozostawimy te metody as-is. W przypadku tych metod usługa BLL służy jedynie jako serwer proxy, za pośrednictwem którego warstwa prezentacji uzyskuje dostęp do danych bazowych z warstwy dostępu do danych.

Metody AddProduct i UpdateProduct przyjmują jako parametry wartości dla różnych pól produktu i dodają nowy produkt lub zaktualizują istniejący, odpowiednio. Ponieważ wiele kolumn tabeli Product może akceptować wartości NULL (CategoryID, SupplierID i UnitPrice, by wymienić kilka), parametry wejściowe dla AddProduct i UpdateProduct, które są mapowane na takie kolumny, używają typów dopuszczających wartość null. Typy nulowalne są nowe w .NET 2.0 i zapewniają technikę określającą, czy typ wartości powinien być Nothing. Aby uzyskać więcej informacji, zapoznaj się z wpisem na blogu Paula Vicka The Truth About Nullable Types and VB oraz dokumentacją techniczną dotyczącą struktury Nullable.

Wszystkie trzy metody zwracają wartość logiczną wskazującą, czy wiersz został wstawiony, zaktualizowany lub usunięty, ponieważ operacja może nie spowodować wystąpienia objętego wiersza. Jeśli na przykład deweloper strony wywołuje DeleteProduct, przekazując ProductID dla nieistniejącego produktu, zapytanie DELETE do bazy danych nie wpłynie na wynik, i dlatego metoda DeleteProduct zwróci wartość False.

Należy pamiętać, że podczas dodawania nowego produktu lub aktualizowania istniejącego przyjmujemy wartości pól nowego lub zmodyfikowanego produktu jako listę skalarnych, w przeciwieństwie do akceptowania ProductsRow wystąpienia. Ta metoda została wybrana, ponieważ ProductsRow klasa pochodzi z klasy ADO.NET DataRow , która nie ma domyślnego konstruktora bez parametrów. Aby utworzyć nowe wystąpienie ProductsRow, musimy najpierw utworzyć wystąpienie ProductsDataTable, a następnie wywołać jego metodę NewProductRow() (co robimy w pliku AddProduct). Ta wada ukazuje się podczas wstawiania i aktualizowania produktów przy użyciu obiektu ObjectDataSource. Krótko mówiąc, obiekt ObjectDataSource spróbuje utworzyć wystąpienie parametrów wejściowych. Jeśli metoda BLL oczekuje instancji ProductsRow, obiekt ObjectDataSource spróbuje ją utworzyć, ale nie powiedzie się to z powodu braku domyślnego konstruktora bez parametrów. Aby uzyskać więcej informacji na temat tego problemu, zapoznaj się z następującymi wpisami na forach ASP.NET: Aktualizowanie ObjectDataSources przy użyciu zestawów danych Strongly-Typed oraz Problem z ObjectDataSource i Strongly-Typed DataSet.

Następnie zarówno w AddProduct, jak i w UpdateProduct, kod tworzy wystąpienie ProductsRow i wypełnia je wartościami, które właśnie zostały przekazane. Podczas przypisywania wartości do kolumn DataColumns obiektu DataRow mogą wystąpić różne testy sprawdzania poprawności na poziomie pola. W związku z tym ręczne umieszczenie przekazanych wartości z powrotem do elementu DataRow pomaga zapewnić ważność danych przekazywanych do metody BLL. Niestety silnie typizowane klasy DataRow wygenerowane przez program Visual Studio nie używają typów dopuszczanych do wartości null. Aby wskazać, że określona kolumna DataColumn w obiekcie DataRow powinna odpowiadać wartości NULL w bazie danych, należy użyć metody SetColumnNameNull().

W UpdateProduct najpierw ładujemy produkt do zaktualizowania przy użyciu GetProductByProductID(productID). Chociaż może to wydawać się niepotrzebną podróżą do bazy danych, ta dodatkowa podróż okaże się opłacalna w przyszłych samouczkach, które eksplorują optymistyczną współbieżność. Optymistyczna współbieżność to technika, która zapewnia, że dwóch użytkowników, pracujących jednocześnie nad tymi samymi danymi, nie nadpisuje przypadkowo zmian dokonanych przez siebie nawzajem. Pobranie całego rekordu ułatwia również tworzenie metod aktualizacji w usłudze BLL, które modyfikują tylko podzestaw kolumn DataRow. Podczas eksplorowania SuppliersBLL klasy zobaczymy taki przykład.

Na koniec należy pamiętać, że ProductsBLL klasa ma zastosowany atrybut DataObject (syntaks [System.ComponentModel.DataObject] jest umieszczona bezpośrednio przed deklaracją klasy w górnej części pliku), a metody mają atrybuty DataObjectMethodAttribute. Atrybut DataObject oznacza klasę jako obiekt odpowiedni do powiązania z kontrolką ObjectDataSource, natomiast DataObjectMethodAttribute parametr wskazuje przeznaczenie metody. Jak zobaczymy w przyszłych samouczkach, ASP.NET 2.0 ObjectDataSource ułatwia deklaratywne uzyskiwanie dostępu do danych z klasy. Aby ułatwić filtrowanie listy możliwych klas do powiązania w kreatorze ObjectDataSource, domyślnie na liście rozwijanej kreatora są wyświetlane tylko te klasy, które są oznaczone jako DataObjects. Klasa ProductsBLL będzie działać równie dobrze bez tych atrybutów, ale dodanie ich ułatwia pracę z kreatorem ObjectDataSource.

Dodawanie innych klas

Po zakończeniu ProductsBLL zajęć nadal musimy dodać klasy do pracy z kategoriami, dostawcami i pracownikami. Pośmiń chwilę na utworzenie następujących klas i metod przy użyciu pojęć z powyższego przykładu:

  • CategoriesBLL.cs

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

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

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

Warto zauważyć jedną z metod, a mianowicie metodę SuppliersBLL klasy UpdateSupplierAddress. Ta metoda udostępnia interfejs umożliwiający aktualizowanie tylko informacji o adresie dostawcy. Wewnętrznie ta metoda odczytuje obiekt SupplierDataRow dla określonego supplierID (korzystając z GetSupplierBySupplierID), ustawia jego właściwości związane z adresami, a następnie wywołuje metodę SupplierDataTable na Update. Metoda UpdateSupplierAddress jest następująca:

<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

Zapoznaj się z pobieraniem tego artykułu, aby uzyskać pełną implementację klas BLL.

Krok 2. Uzyskiwanie dostępu do typowych zestawów danych za pośrednictwem klas BLL

W pierwszym samouczku przedstawiliśmy przykłady pracy bezpośrednio z Typed DataSet programistycznie, jednak po dodaniu naszych klas BLL, warstwa interfejsu użytkownika powinna działać względem BLL. Na przykładzie AllProducts.aspx z pierwszego samouczka, ProductsTableAdapter został użyty do powiązania listy produktów z elementem GridView, jak pokazano w poniższym kodzie.

Dim productsAdapter As New ProductsTableAdapter()
GridView1.DataSource = productsAdapter.GetProducts()
GridView1.DataBind()

Aby użyć nowych klas BLL, wszystko, co należy zmienić, to pierwszy wiersz kodu po prostu zastąpić ProductsTableAdapter obiekt obiektem ProductBLL :

Dim productLogic As New ProductsBLL()
GridView1.DataSource = productLogic.GetProducts()
GridView1.DataBind()

Dostęp do klas BLL można również uzyskać deklaratywnie (podobnie jak typowy zestaw danych) przy użyciu obiektu ObjectDataSource. Bardziej szczegółowo omówimy obiekt ObjectDataSource w poniższych samouczkach.

Lista produktów jest wyświetlana w siatce

Rysunek 3. Lista produktów jest wyświetlana w siatce (kliknij, aby wyświetlić obraz pełnowymiarowy)

Krok 3. Dodawanie weryfikacji Field-Level do klas DataRow

Sprawdzanie poprawności na poziomie poszczególnych pól dotyczy wartości właściwości obiektów biznesowych podczas ich wstawiania lub aktualizowania. Niektóre reguły weryfikacji na poziomie pola dla produktów to:

  • Pole ProductName musi mieć długość nie większą niż 40 znaków
  • Pole QuantityPerUnit musi mieć maksymalnie 20 znaków długości
  • Pola ProductID, ProductNamei Discontinued są wymagane, ale wszystkie inne pola są opcjonalne
  • Pola UnitPrice, UnitsInStock, UnitsOnOrderi ReorderLevel muszą być większe lub równe zero

Te reguły mogą i powinny być wyrażone na poziomie bazy danych. Limit znaków w polach ProductName i QuantityPerUnit jest przechwytywany przez typy danych tych kolumn w Products tabeli (nvarchar(40) i nvarchar(20), odpowiednio). Czy pola są wymagane lub opcjonalne, jest określane przez to, czy kolumna tabeli bazy danych pozwala na NULL. Istnieją cztery ograniczenia sprawdzania, które zapewniają, że tylko wartości większe niż lub równe zero mogą trafić do kolumn UnitPrice, UnitsInStock, UnitsOnOrder lub ReorderLevel.

Oprócz wymuszania tych reguł w bazie danych powinny być również wymuszane na poziomie zestawu danych. W rzeczywistości długość pola i to, czy wartość jest wymagana, czy opcjonalna, są już przechwytywane dla zestawu kolumn Danych w tabeli DataTable. Aby automatycznie wyświetlić istniejącą weryfikację na poziomie pola, przejdź do Projektanta zestawu danych, wybierz pole z jednej z tabel danych, a następnie przejdź do okna Właściwości. Jak pokazano na rysunku 4, kolumna QuantityPerUnit DataColumn w obiekcie ProductsDataTable ma maksymalną długość 20 znaków i zezwala na NULL wartości. Jeśli spróbujemy ustawić właściwość ProductsDataRowQuantityPerUnit na wartość ciągu dłuższą niż 20 znaków, ArgumentException zostanie zgłoszone.

Funkcja DataColumn zapewnia podstawową weryfikację Field-Level

Rysunek 4. Kolumna danych zapewnia podstawową weryfikację Field-Level (kliknij, aby wyświetlić obraz pełnowymiarowy)

Niestety nie można określić kontroli granic, takich jak UnitPrice wartość musi być większa lub równa zero, za pośrednictwem okna Właściwości. Aby zapewnić ten typ weryfikacji na poziomie pola, musimy utworzyć procedurę obsługi zdarzeń dla zdarzenia ColumnChanging tabeli Danych. Jak wspomniano w poprzednim samouczku, obiekty DataSet, DataTables i DataRow utworzone przez typowy zestaw danych można rozszerzyć za pomocą klas częściowych. Korzystając z tej techniki, możemy utworzyć procedurę ColumnChanging obsługi zdarzeń dla ProductsDataTable klasy . Zacznij od utworzenia klasy w folderze App_Code o nazwie ProductsDataTable.ColumnChanging.vb.

Dodawanie nowej klasy do folderu App_Code

Rysunek 5. Dodawanie nowej klasy do App_Code folderu (kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Następnie utwórz obsługę zdarzeń dla zdarzenia ColumnChanging, która zapewnia, że wartości kolumn UnitPrice, UnitsInStock, UnitsOnOrder i ReorderLevel (jeśli nie NULL) są większe lub równe zero. Jeśli jakakolwiek taka kolumna jest poza zakresem, wyrzuć element 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

Krok 4. Dodawanie niestandardowych reguł biznesowych do klas BLL

Oprócz weryfikacji na poziomie pola mogą istnieć niestandardowe reguły biznesowe wysokiego poziomu, które obejmują różne jednostki lub pojęcia, które nie są wyrażalne na poziomie pojedynczej kolumny, takie jak:

  • Jeśli produkt zostanie przerwany, jego UnitPrice nie można zaktualizować
  • Kraj zamieszkania pracownika musi być taki sam jak kraj zamieszkania swojego menedżera
  • Produkt nie może zostać przerwany, jeśli jest to jedyny produkt dostarczony przez dostawcę

Klasy BLL powinny zawierać kontrole w celu zapewnienia zgodności z regułami biznesowymi aplikacji. Te kontrole można dodać bezpośrednio do metod, do których mają zastosowanie.

Załóżmy, że nasze reguły biznesowe określają, że produkt nie może być oznaczony jako zaprzestany, jeśli był to jedyny produkt od danego dostawcy. Oznacza to, że jeśli produkt X był jedynym produktem zakupionym od dostawcy Y, nie mogliśmy oznaczyć X jako przerwanego; jeśli jednak dostawca Y dostarczył nam trzy produkty, A, B i C, możemy oznaczyć dowolne i wszystkie z nich jako zaprzestane. Dziwna reguła biznesowa, ale reguły biznesowe i zdrowy rozsądek nie zawsze są dopasowane!

Aby wymusić tę regułę biznesową w metodzie UpdateProducts, zaczęlibyśmy od sprawdzenia, czy Discontinued była ustawiona na True, a jeśli tak, wywołalibyśmy GetProductsBySupplierID, aby ustalić, ile produktów zakupiliśmy od dostawcy tego produktu. Jeśli tylko jeden produkt jest kupowany od tego dostawcy, zgłaszamy element 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

Reagowanie na błędy walidacji w warstwie prezentacji

Podczas wywoływania BLL z warstwy prezentacji możemy zdecydować, czy podjąć próbę obsługi wszelkich wyjątków, które mogą zostać podniesione, czy pozwolić im zostać przekazanymi do ASP.NET (co spowoduje wywołanie zdarzenia HttpApplicationError). Aby obsłużyć wyjątek podczas programowej pracy z programem BLL, możemy użyć bloku Try...Catch, jak pokazano w poniższym przykładzie:

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

Jak zobaczymy w przyszłych samouczkach, obsługę wyjątków, które pojawiają się z warstwy logiki biznesowej podczas korzystania z kontrolki sieciowej do zarządzania danymi przy wstawianiu, aktualizowaniu lub usuwaniu danych, można bezpośrednio obsłużyć w procedurze obsługi zdarzeń, zamiast konieczności owijania kodu w Try...Catch bloki.

Podsumowanie

Dobrze zaprojektowana aplikacja jest zbudowana w różnych warstwach, z których każda oddziela określoną rolę. W pierwszym samouczku tej serii artykułów utworzyliśmy warstwę dostępu do danych przy użyciu typów zestawów danych; w tym samouczku utworzyliśmy warstwę logiki biznesowej jako serię klas w folderze naszej aplikacji App_Code, które wywołują funkcje w naszej warstwie dostępu do danych (DAL). Usługa BLL implementuje logikę na poziomie pola i na poziomie biznesowym dla naszej aplikacji. Oprócz utworzenia oddzielnego BLL, podobnie jak w tym samouczku, inną opcją jest rozszerzenie metod TableAdapters za pomocą klas częściowych. Niemniej jednak zastosowanie tej techniki nie pozwala nam zastąpić ani stosować innych istniejących metod, ani nie oddziela naszego DAL i naszego BLL tak przejrzyście, jak podejście, które przyjęliśmy w tym artykule.

Po zakończeniu DAL i BLL możemy rozpocząć pracę nad naszą warstwą prezentacji. Podczas następnego tutorialu zrobimy krótką przerwę od tematów związanych z dostępem do danych i zdefiniujemy spójny układ strony, który będzie używany we wszystkich tutorialach.

Szczęśliwe programowanie!

Informacje o autorze

Scott Mitchell, autor siedmiu książek ASP/ASP.NET i założyciel 4GuysFromRolla.com, współpracuje z technologiami internetowymi firmy Microsoft od 1998 roku. Scott pracuje jako niezależny konsultant, trener i pisarz. Jego najnowsza książka to Sams Teach Yourself ASP.NET 2.0 w ciągu 24 godzin. Można go uzyskać pod adresem mitchell@4GuysFromRolla.com.

Specjalne podziękowania

Ta seria samouczków została omówiona przez wielu przydatnych recenzentów. Recenzenci dla tego samouczka to Liz Shulok, Dennis Patterson, Carlos Santos i Hilton Giesenow. Chcesz przejrzeć nadchodzące artykuły MSDN? Jeśli tak, napisz do mnie na adres mitchell@4GuysFromRolla.com.