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
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.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;
}
}
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ć null
. W języku C# można oznaczyć typ wartości jako typ dopuszczający null, dodając ?
po typie (na przykład int? x;
). Aby uzyskać więcej informacji, zapoznaj się z sekcją Typy Nullable w Przewodniku Programowania C#.
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 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;
}
}
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.
ProductsTableAdapter productsAdapter = 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
:
ProductsBLL productLogic = 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.cs
.
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.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);
}
}
}
}
}
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
.
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;
}
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 programowo obsłużyć wyjątek podczas pracy z BLL, możemy użyć try...catch, jak pokazano w poniższym przykładzie:
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);
}
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.