Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Autor : Scott Mitchell
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.
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
, SuppliersBLL
i EmployeesBLL
.
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; zwracaTrue
, 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.
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
,ProductName
iDiscontinued
są wymagane, ale wszystkie inne pola są opcjonalne - Pola
UnitPrice
,UnitsInStock
,UnitsOnOrder
iReorderLevel
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ść ProductsDataRow
QuantityPerUnit
na wartość ciągu dłuższą niż 20 znaków, ArgumentException
zostanie zgłoszone.
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
.
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 HttpApplication
Error
). 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.