Tworzenie warstwy logiki biznesowej (C#)
Autor : Scott Mitchell
W tym samouczku zobaczymy, jak scentralizować reguły biznesowe w warstwie logiki biznesowej (BLL), która służy jako pośrednik w wymianie danych między warstwą prezentacji a warstwą DAL.
Wprowadzenie
Warstwa dostępu do danych (DAL) utworzona w pierwszym samouczku wyraźnie oddziela logikę dostępu do danych od logiki prezentacji. Jednak podczas gdy funkcja DAL czysta oddziela szczegóły dostępu do danych od warstwy prezentacji, nie wymusza żadnych reguł biznesowych, które mogą być stosowane. Na przykład w przypadku naszej aplikacji możemy nie zezwalać CategoryID
na modyfikowanie pól Products
lub SupplierID
tabeli, gdy Discontinued
pole jest ustawione na 1, lub możemy chcieć wymusić reguły seniorstwa, zabraniając sytuacji, w których pracownik jest zarządzany przez kogoś, kto został zatrudniony po nich. 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 w wymianie danych między warstwą prezentacji a warstwą DAL. W rzeczywistych aplikacjach 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 przedstawia 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 jednym dla każdego tableAdapter 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 dokładniej 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 Eksplorator rozwiązań i wybierz pozycję Nowy folder. Po utworzeniu tych dwóch folderów przenieś typowy zestaw danych 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 BLL
podfolder, 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 opakować metody zdefiniowane dla elementów TableAdapters z pierwszego samouczka. Na razie te metody będą po prostu wywoływać bezpośrednio do dal; Wrócimy później, aby dodać dowolną potrzebną logikę biznesową.
Uwaga
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 Projektant Klas. Aby uzyskać więcej informacji na temat tej nowej funkcji w programie Visual Studio, zapoznaj się z blogami Projektant klas.
ProductsBLL
Dla klasy musimy dodać łącznie siedem metod:
GetProducts()
zwraca wszystkie produktyGetProductByProductID(productID)
zwraca produkt o określonym identyfikatorze produktuGetProductsByCategoryID(categoryID)
Zwraca wszystkie produkty z określonej kategoriiGetProductsBySupplier(supplierID)
zwraca wszystkie produkty od określonego dostawcyAddProduct(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 rekorduUpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID)
aktualizuje istniejący produkt w bazie danych przy użyciu przekazanych wartości; metoda zwracatrue
wartość , jeśli został zaktualizowany dokładnie jeden wiersz,false
w przeciwnym razieDeleteProduct(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 są wywoływane do dal. W niektórych scenariuszach mogą istnieć reguły biznesowe, które muszą zostać zaimplementowane na tym poziomie (takie jak reguły autoryzacji oparte na aktualnie zalogowanym użytkowniku lub roli, do której należy użytkownik), po prostu pozostawimy te metody tak, jak jest. W przypadku tych metod 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.
Obie AddProduct
metody i UpdateProduct
przyjmują jako parametry wartości dla różnych pól produktu i dodają nowy produkt lub zaktualizują istniejący. Ponieważ wiele Product
kolumn tabeli może akceptować NULL
wartości (CategoryID
, SupplierID
, i UnitPrice
, do kilku), te parametry wejściowe dla AddProduct
i UpdateProduct
mapowania na takie kolumny używają typów dopuszczających wartość null. Typy dopuszczające wartość null są nowe dla platformy .NET 2.0 i stanowią technikę wskazującą, czy typ wartości powinien mieć wartość null
. W języku C# można oznaczyć typ wartości jako typ dopuszczalny do wartości null, dodając ?
wartość po typie (na przykład int? x;
). Aby uzyskać więcej informacji, zapoznaj się z sekcją Typy dopuszczane do wartości null w Przewodniku programowania w języku 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 problemu w wierszu. Jeśli na przykład deweloper strony wywołuje DeleteProduct
przekazanie ProductID
elementu dla nieistniejącego produktu, DELETE
instrukcja wystawiona dla bazy danych nie będzie miała wpływu i dlatego DeleteProduct
metoda 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. To podejście zostało wybrane, ponieważ ProductsRow
klasa pochodzi z klasy ADO.NETDataRow
, która nie ma domyślnego konstruktora bez parametrów. Aby utworzyć nowe ProductsRow
wystąpienie, musimy najpierw utworzyć ProductsDataTable
wystąpienie, a następnie wywołać jego NewProductRow()
metodę (co robimy w AddProduct
pliku ). To niedoścignięcie zmienia głowę, gdy zwracamy się do 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 wystąpienia, obiekt ObjectDataSource spróbuje ProductsRow
go utworzyć, ale zakończy się niepowodzeniem 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 obiektówDataSources przy użyciu zestawów danych Strongly-Typed oraz Problem z obiektami ObjectDataSource i Strongly-Typed DataSet.
Następnie zarówno w AddProduct
kodzie , jak i UpdateProduct
kod tworzy ProductsRow
wystąpienie i wypełnia je wartościami, które zostały właśnie przekazane. Podczas przypisywania wartości do kolumny DataColumns obiektu DataRow mogą wystąpić różne sprawdzenia 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 generowane przez program Visual Studio nie używają typów dopuszczalnych wartości null. Zamiast tego, aby wskazać, że określona kolumna DataColumn w obiekcie DataRow powinna odpowiadać NULL
wartości bazy danych, należy użyć SetColumnNameNull()
metody .
W UpdateProduct
pliku najpierw załadujemy produkt do aktualizacji przy użyciu polecenia 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 zapewniająca, że dwóch użytkowników, którzy pracują jednocześnie nad tymi samymi danymi, nie zastępuje przypadkowo zmian. 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 klasa ProductsBLL
ma zastosowany atrybut DataObject ( [System.ComponentModel.DataObject]
składnia bezpośrednio przed instrukcją 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
element wskazuje cel metody . Jak zobaczymy w przyszłych samouczkach, ASP.NET 2.0's ObjectDataSource ułatwia deklaratywny dostęp do danych z klasy. Aby ułatwić filtrowanie listy możliwych klas do powiązania w kreatorze objectDataSource, domyślnie tylko te klasy oznaczone jako DataObjects
są wyświetlane na liście rozwijanej kreatora. 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
ProductsBLL
Po ukończeniu klasy nadal musimy dodać klasy do pracy z kategoriami, dostawcami i pracownikami. Poświęć 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)
Jedyną metodą, która warto zauważyć, jest SuppliersBLL
metoda klasy UpdateSupplierAddress
. Ta metoda udostępnia interfejs do aktualizowania tylko informacji o adresie dostawcy. Ta metoda jest odczytywana wewnętrznie w SupplierDataRow
obiekcie dla określonego supplierID
(przy użyciu GetSupplierBySupplierID
), ustawia jego właściwości związane z adresami, a następnie wywołuje metodę SupplierDataTable
w dół do metody .s Update
. Metoda jest następująca UpdateSupplierAddress
:
[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 typowanych zestawów danych za pośrednictwem klas BLL
W pierwszym samouczku przedstawiono przykłady pracy bezpośrednio z typed DataSet programowo, ale wraz z dodaniu naszych klas BLL warstwa prezentacji powinna działać względem BLL. W przykładzie z AllProducts.aspx
pierwszego samouczka ProductsTableAdapter
użyto elementu , aby powiązać listę 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 typed DataSet) 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 w pełnym rozmiarze)
Krok 3. Dodawanie weryfikacji Field-Level do klas DataRow
Sprawdzanie poprawności na poziomie pola dotyczy wartości właściwości obiektów biznesowych podczas wstawiania lub aktualizowania. Niektóre reguły weryfikacji na poziomie pola dla produktów to:
- Pole
ProductName
musi mieć długość 40 znaków lub mniej - Pole
QuantityPerUnit
musi mieć długość 20 znaków lub mniej - 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 i opcjonalne, są wyrażane przez kolumnę tabeli bazy danych.NULL
Istnieją cztery ograniczenia sprawdzania, które zapewniają, że tylko wartości większe niż lub równe zero mogą przekształcić je w UnitPrice
kolumny , , UnitsInStock
UnitsOnOrder
lub ReorderLevel
.
Oprócz wymuszania tych reguł w bazie danych należy je również wymuszać na poziomie zestawu danych. W rzeczywistości długość pola i to, czy wartość jest wymagana, czy opcjonalna, są już przechwytywane dla każdego zestawu kolumn Danych DataTable. Aby automatycznie wyświetlić istniejącą weryfikację na poziomie pola, przejdź do Projektant Zestawu danych, wybierz pole z jednej z tabel Danych, a następnie przejdź do okno 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ć ProductsDataRow
właściwość 's QuantityPerUnit
na wartość ciągu dłuższą niż 20 znaków, ArgumentException
zostanie zgłoszonych.
Rysunek 4. Kolumna danych zapewnia podstawową weryfikację Field-Level (kliknij, aby wyświetlić obraz w pełnym rozmiarze)
Niestety, nie można określić kontroli granic, takich jak UnitPrice
wartość musi być większa lub równa zero, za pośrednictwem okno Właściwości. Aby zapewnić ten typ weryfikacji na poziomie pola, należy utworzyć procedurę obsługi zdarzeń dla zdarzenia ColumnChanging tabeli DataTable. 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 pełnowymiarowy)
Następnie utwórz procedurę obsługi zdarzeń dla ColumnChanging
zdarzenia, która gwarantuje, że UnitPrice
wartości kolumn , 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 wycofany, nie można go
UnitPrice
zaktualizować - Kraj zamieszkania pracownika musi być taki sam jak kraj zamieszkania swojego kierownika
- Produkt nie może zostać wycofany, 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 zasady biznesowe określają, że produkt nie może zostać 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 możemy oznaczyć produktu X jako wycofanego; jeśli jednak dostawca Y dostarczył nam trzy produkty, A, B i C, wówczas możemy oznaczyć wszystkie te produkty jako zaprzestane. Dziwna reguła biznesowa, ale reguły biznesowe i zdrowy rozsądek nie zawsze są dostosowane!
Aby wymusić tę regułę biznesową UpdateProducts
w metodzie, zacznijmy od sprawdzenia, czy Discontinued
ustawiono wartość true
i, jeśli tak, wywołamy metodę GetProductsBySupplierID
w celu określenia, ile produktów kupiliśmy od dostawcy tego produktu. W przypadku zakupu tylko jednego produktu od tego dostawcy zgłaszamy wyjątek 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 biblioteki BLL z warstwy prezentacji możemy zdecydować, czy należy podjąć próbę obsługi wszelkich wyjątków, które mogą zostać zgłoszone, czy pozwolić im na ASP.NET (co spowoduje wywołanie HttpApplication
zdarzenia ).Error
Aby obsłużyć wyjątek podczas programowej pracy z usługą BLL, możemy użyć polecenia wypróbuj... blok 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ługa wyjątków, które są bąbelkowe od BLL podczas używania kontrolki internetowej danych do wstawiania, aktualizowania lub usuwania danych, można obsługiwać bezpośrednio w procedurze obsługi zdarzeń, w przeciwieństwie do konieczności zawijania kodu w try...catch
blokach.
Podsumowanie
Dobrze zaprojektowana aplikacja jest oparta na odrębnych warstwach, z których każda hermetyzuje określoną rolę. W pierwszym samouczku tej serii artykułów utworzyliśmy warstwę dostępu do danych przy użyciu typowych zestawów danych; W tym samouczku utworzyliśmy warstwę logiki biznesowej jako serię klas w folderze naszej aplikacji App_Code
, które są wywoływane do naszej nazwy DAL. Usługa BLL implementuje logikę na poziomie pola i na poziomie biznesowym dla naszej aplikacji. Oprócz utworzenia oddzielnej BLL, tak jak w tym samouczku, inną opcją jest rozszerzenie metod TableAdapters za pomocą klas częściowych. Jednak użycie tej techniki nie pozwala nam przesłonić istniejących metod ani nie oddziela to naszego dal i naszego BLL tak czysto, jak podejście podjęte w tym artykule.
Po zakończeniu dal i BLL możemy rozpocząć pracę w warstwie prezentacji. W następnym samouczku zajmiemy się krótkim objazdem z tematów dostępu do danych i zdefiniujemy spójny układ strony do użycia w ramach samouczków.
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 do niego dotrzeć pod adresem mitchell@4GuysFromRolla.com. Lub za pośrednictwem swojego bloga, który można znaleźć na stronie http://ScottOnWriting.NET.
Specjalne podziękowania
Ta seria samouczków została przejrzyona przez wielu przydatnych recenzentów. Głównymi recenzentami tego samouczka byli Liz Shulok, Dennis Patterson, Carlos Santos i Hilton Giesenow. Chcesz przejrzeć nadchodzące artykuły MSDN? Jeśli tak, upuść mi wiersz pod adresemmitchell@4GuysFromRolla.com .