本教程的
第一節所描述的資料訪問層(Data Access Layer,以下簡稱為DAL)已經清晰地將表示邏輯與資料訪問邏輯區分開了。不過,即使DAL將資料訪問的細節從展示層中分離出來了,可它卻不能處理任何的商務規則。比如說,我們可能不希望產品表中那些被標記為“停用”的產品的“分類編號”或“供應商編號”被更新;我們還可能需要應用一些資曆規則,比如說我們都不希望被比自己的資曆還要淺的人管理。另外一個比較常見的情況就是授權,比如說只有那些具有特殊許可權的使用者可以刪除產品或是更改單價。
我們其實可以將商務邏輯層(Business Logic Layer,以下簡稱BLL)看作是在資料訪問層和展示層之間進行資料交換的橋樑,在這個章節中,我們將討論一下如何將這些商務規則整合到一個BLL中。需要說明的是,在一個實際的應用程式中,BLL都是以類庫(Class Library)的形式來實現的,不過為了簡化工程的結構,在本教程中我們將BLL實現為App_Code檔案夾中的一系列的類。圖一向我們展示了展示層、BLL以及DAL三者之間的結構關係。
圖一:BLL將展示層與DAL隔開了,並且加入了商務規則
第一步:建立BLL類
我們的BLL由4個類組成,每一個BLL類都對應DAL中的一個TableAdapter,它們都從各自的TableAdapter中得到讀取、插入、修改以及刪除等方法以應用合適的商務規則。
為了更加清晰的區分DAL和BLL的類,我們在App_Code檔案夾中建立兩個子檔案夾,分別命名為DAL和BLL。你僅僅需要在解決方案瀏覽器(Solution Explorer)中右鍵點擊App_Code檔案夾,並選擇建立檔案夾(New Folder),就可以建立新的子檔案夾了。建好了這兩個檔案夾之後,把第一節中所建立的類型化資料集(Typed DataSet)移到DAL檔案夾中。
然後,在BLL檔案夾中建立4個類檔案。同樣,你僅僅需要在解決方案瀏覽器(Solution Explorer)中右鍵點擊BLL檔案夾,並選擇建立項目(New Item),然後在彈出的對話方塊中選擇類模板(Class template)就可以建立新的類檔案了。將這四個檔案分別命名為ProductsBLL、CategoriesBLL、SuppliersBLL以及EmployeesBLL。
圖二:在BLL檔案夾中添加4個新的類
接下來,讓我們來給這些建立的類加上一些方法,簡單的將第一節中的TableAdapter中的那些方法封裝起來就行了。現在,這些方法將只能直接使用DAL中的那些方法,我們等會再來給他們加上一些商務邏輯。
注意:如果你使用的是Visual Studio 標準版或以上版本(也就是說,你不是用的Visual Web Developer),那麼你還可以使用Class Designer來可視化的設計你的類。你可以在Class Designer Blog上得到關於Visual Studio的這項新功能的詳細資料。
在ProductsBLL類中,我們一共需要為其添加7個方法:
·GetProducts() – 返回所有的產品
·GetProductByProductID(productID) – 返回指定ProductID的產品
·GetProductsByCategoryID(categoryID) –返回指定分類的產品
·GetProductsBySupplier(supplierID) –返回指定供應商的產品
·AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) – 向資料庫中添加一條產品資訊,並返回新添加的產品的ProductID
·UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID) – 更新一個資料庫中已經存在的產品,如果剛好更新了一條記錄,則返回true,否則返回false
·DeleteProduct(productID) – 刪除指定ProductID的產品
ProductsBLL.cs
1using System;
2using System.Data;
3using System.Configuration;
4using System.Web;
5using System.Web.Security;
6using System.Web.UI;
7using System.Web.UI.WebControls;
8using System.Web.UI.WebControls.WebParts;
9using System.Web.UI.HtmlControls;
10using NorthwindTableAdapters;
11
12[System.ComponentModel.DataObject]
13public class ProductsBLL
14{
15 private ProductsTableAdapter _productsAdapter = null;
16 protected ProductsTableAdapter Adapter
17 {
18 get {
19 if (_productsAdapter == null)
20 _productsAdapter = new ProductsTableAdapter();
21
22 return _productsAdapter;
23 }
24 }
25
26
27[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, true)]
28 public Northwind.ProductsDataTable GetProducts()
29 {
30 return Adapter.GetProducts();
31 }
32
33 [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, false)]
34 public Northwind.ProductsDataTable GetProductByProductID(int productID)
35 {
36 return Adapter.GetProductByProductID(productID);
37 }
38
39[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, false)]
40 public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID)
41 {
42 return Adapter.GetProductsByCategoryID(categoryID);
43 }
44
45[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, false)]
46 public Northwind.ProductsDataTable GetProductsBySupplierID(int supplierID)
47 {
48 return Adapter.GetProductsBySupplierID(supplierID);
49 }
50 [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Insert, true)]
51 public bool AddProduct(string productName, int? supplierID, int? categoryID, string quantityPerUnit,
52 decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, short? reorderLevel,
53 bool discontinued)
54 {
55 // 建立一個ProductRow執行個體
56 Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
57 Northwind.ProductsRow product = products.NewProductsRow();
58
59 product.ProductName = productName;
60 if (supplierID == null) product.SetSupplierIDNull(); else product.SupplierID = supplierID.Value;
61 if (categoryID == null) product.SetCategoryIDNull(); else product.CategoryID = categoryID.Value;
62 if (quantityPerUnit == null) product.SetQuantityPerUnitNull(); else product.QuantityPerUnit = quantityPerUnit;
63 if (unitPrice == null) product.SetUnitPriceNull(); else product.UnitPrice = unitPrice.Value;
64 if (unitsInStock == null) product.SetUnitsInStockNull(); else product.UnitsInStock = unitsInStock.Value;
65 if (unitsOnOrder == null) product.SetUnitsOnOrderNull(); else product.UnitsOnOrder = unitsOnOrder.Value;
66 if (reorderLevel == null) product.SetReorderLevelNull(); else product.ReorderLevel = reorderLevel.Value;
67 product.Discontinued = discontinued;
68
69 // 添加新產品
70 products.AddProductsRow(product);
71 int rowsAffected = Adapter.Update(products);
72
73 // 如果剛好新增了一條記錄,則返回true,否則返回false
74 return rowsAffected == 1;
75 }
76
77 [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, true)]
78 public bool UpdateProduct(string productName, int? supplierID, int? categoryID, string quantityPerUnit,
79 decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, short? reorderLevel,
80 bool discontinued, int productID)
81 {
82 Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
83 if (products.Count == 0)
84 // 沒有找到匹配的記錄,返回false
85 return false;
86
87 Northwind.ProductsRow product = products[0];
88
89 product.ProductName = productName;
90 if (supplierID == null) product.SetSupplierIDNull(); else product.SupplierID = supplierID.Value;
91 if (categoryID == null) product.SetCategoryIDNull(); else product.CategoryID = categoryID.Value;
92 if (quantityPerUnit == null) product.SetQuantityPerUnitNull(); else product.QuantityPerUnit = quantityPerUnit;
93 if (unitPrice == null) product.SetUnitPriceNull(); else product.UnitPrice = unitPrice.Value;
94 if (unitsInStock == null) product.SetUnitsInStockNull(); else product.UnitsInStock = unitsInStock.Value;
95 if (unitsOnOrder == null) product.SetUnitsOnOrderNull(); else product.UnitsOnOrder = unitsOnOrder.Value;
96 if (reorderLevel == null) product.SetReorderLevelNull(); else product.ReorderLevel = reorderLevel.Value;
97 product.Discontinued = discontinued;
98
99 // 更新產品記錄
100 int rowsAffected = Adapter.Update(product);
101
102 // 如果剛好更新了一條記錄,則返回true,否則返回false
103 return rowsAffected == 1;
104 }
105
106 [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Delete, true)]
107 public bool DeleteProduct(int productID)
108 {
109 int rowsAffected = Adapter.Delete(productID);
110
111 // 如果剛好刪除了一條記錄,則返回true,否則返回false
112 return rowsAffected == 1;
113 }
114}
115
GetProducts、GetProductByProductID、GetProductsByCategoryID以及 GetProductBySuppliersID等方法都僅僅是簡簡單單的直接調用DAL中的方法來返回資料。不過在有的情況下,我們還可能需要給它們實現一些商務規則(比如說授權規則,不同的使用者或不用角色應該可以看到不同的資料),現在我們簡單的將它們做成這樣就可以了。那麼,對於這些方法來說,BLL僅僅是作為展示層與DAL之間的代理。
AddProduct和UpdateProduct這兩個方法都使用參數中的那些產品資訊去添加或是更新一條產品記錄。由於Product表中有許多欄位都允許空值(CategoryID、SupplierID、UnitPrice……等等),所以AddProduct和UpdateProduct中相應的參數就使用nullable types。Nullable types是.NET 2.0中新提供的一種用於標明一個實值型別是否可以為空白的技術。在C#中,你可以在一個允許為空白的實值型別後面加上一個問號(比如,int x;)。關於Nullable Types的詳細資料,你可以參考C# Programming Guide。
由於插入、修改和刪除可能不會影響任何行,所以這三種方法均返回一個bool值用於表示操作是否成功。比如說,頁面開發人員使用一個並不存在的ProductID去調用DeleteProduct,很顯然,提交給資料庫的DELETE語句將不會有任何作用,所以DeleteProduct會返回false。
注意:當我們在添加或更新一個產品的詳細資料時,都是接受由產品資訊組成的一個標量列表,而不是直接接受一個ProductsRow執行個體。因為ProductsRow是繼承於ADO.NET的DataRow,而DataRow沒有預設的無參建構函式,為了建立一個ProductsRow的執行個體,我們必須先建立一個ProductsDataTable的執行個體,然後調用它的NewProductRow方法(就像我們在AddProduct方法中所做的那樣)。不過,當我在使用ObjectDataSource來插入或更新時,這樣做的缺點就會暴露出來了。簡單的講,ObjectDataSource會試圖為輸入的參數建立一個執行個體,如果BLL方法希望得到一個ProductsRow,那麼ObjectDataSource就將會試圖去建立一個,不過很顯然,這樣的操作一定會失敗,因為沒有一個預設的無參建構函式。這個問題的詳細資料,可以在ASP.NET論壇的以下兩個文章中找到: Updating ObjectDataSources with Strongly-Typed DataSets、Problem With ObjectDataSource and Strongly-Typed DataSet。
之後,在AddProduct和UpdateProduct中,我們建立了一個ProductsRow執行個體,並將傳入的參數賦值給它。當給一個DataRow的DataColumns賦值時,各種欄位級的有效性驗證都有可能會被觸發。因此,我們應該手工的驗證一下傳入的參數以保證傳遞給BLL方法的資料是有效。不幸的是,Visual Studio產生的強型別資料集(strongly-typed DataRow)並沒有使用nullable values。要表明DataRow中的一個DataColumn可以接受空值,我們就必須得使用SetColumnNameNull方法。
在UpdateProduct中,我們先使用GetProductByProductID(productID)方法將需要更新的產品資訊讀取出來。這樣做好像沒有什麼必要,不過我們將在之後的關於並發最佳化(Optimistic concurrency)的課程中證明這個額外的操作是有它的作用的。並發最佳化是一種保證兩個使用者同時操作一個資料而不會發生衝突的技術。擷取整條記錄同時也可以使建立一個僅更新DataRow的一部分列的方法更加容易,我們可以在SuppliersBLL類中找到這樣的例子。
最後,注意我們在ProductsBLL類上面加上了DataObject 標籤(就是在類聲明語句的上面的[System.ComponentModel.DataObject]),各方法上面還有DataObjectMethodAttribute 標籤。DataObject標籤把這個類標記為可以綁定到一個ObjectDataSource控制項,而DataObjectMethodAttribute則說明了這個方法的目的。我們將在後面的教程中看到,ASP.NET 2.0的ObjectDataSource使從一個類中訪問資料更加容易。為了ObjectDataSource嚮導能夠對現有的類進行合適的篩選,在類列表中預設僅顯示標記為DataObject的類。當然,其實ProductsBLL類就算沒有這個標籤也可以工作,但是加上它可以使我們在ObjectDataSource嚮導中的操作更加輕鬆和心情愉快。
添加其他的類
完成了ProductsBLL類之後,我們還要添加一些為categories、suppliers和employees服務的類。讓我們花點時間來建立下面的類,根據上面的例子來做就是了:
· CategoriesBLL.cs
o GetCategories()
o GetCategoryByCategoryID(categoryID)
· SuppliersBLL.cs
o GetSuppliers()
o GetSupplierBySupplierID(supplierID)
o GetSuppliersByCountry(country)
o UpdateSupplierAddress(supplierID, address, city, country)
· EmployeesBLL.cs
o GetEmployees()
o GetEmployeeByEmployeeID(employeeID)
o GetEmployeesByManager(managerID)
SuppliersBLL類中的UpdateSupplierAddress方法是一個值得注意的東西。這個方法提供了一個僅僅更新供應商地址資訊的介面。它首先根據指定的SupplierID讀出一個SupplierDataRow(使用GetSupplierBySupplierID方法),設定其關於地址的所有屬性,然後調用SupplierDataTable的Update方法。UpdateSupplierAddress方法的代碼如下所示:
UpdateSupplierAddress
1[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, true)]
2public bool UpdateSupplierAddress(int supplierID, string address, string city, string country)
3{
4 Northwind.SuppliersDataTable suppliers = Adapter.GetSupplierBySupplierID(supplierID);
5 if (suppliers.Count == 0)
6 // 沒有找到匹配的項,返回false
7 return false;
8 else
9 {
10 Northwind.SuppliersRow supplier = suppliers[0];
11
12 if (address == null) supplier.SetAddressNull(); else supplier.Address = address;
13 if (city == null) supplier.SetCityNull(); else supplier.City = city;
14 if (country == null) supplier.SetCountryNull(); else supplier.Country = country;
15
16 // 更新供應商的關於地址的資訊
17 int rowsAffected = Adapter.Update(supplier);
18
19 // 如果剛好更新了一條記錄,則返回true,否則返回false
20 return rowsAffected == 1;
21 }
22}
23
可以從頁面頂部的連結處下載BLL類的完整代碼。
第二步:通過BLL類訪問類型化資料集
在本教程的第一節中,我們給出了直接使用類型化資料集的例子,不過在我們添加了BLL類之後,展示層就可以通過BLL來工作了。在本教程的第一節中的AllProducts.aspx的例子中,ProductsTableAdapter用於將產品列表綁定到GridView上,代碼如下所示:
1 ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
2 GridView1.DataSource = productsAdapter.GetProducts();
3 GridView1.DataBind();
要使用新的BLL類,我們所需要做的僅僅是簡單的修改一下第一行代碼。用ProductBLL對象來代替 ProductsTableAdapter即可:
1 ProductsBLL productLogic = new ProductsBLL();
2 GridView1.DataSource = productLogic.GetProducts();
3 GridView1.DataBind();
BLL類也可以通過使用ObjectDataSource來清晰明了的訪問(就像類型化資料集一樣)。我們將在接下來的教程中詳細的討論ObjectDataSource。
圖三:GridView中顯示的產品列表
第三步:給DataRow添加欄位級驗證
欄位級驗證是指在插入或更新時檢查業務對象所涉及到的所有屬性值。拿產品來舉個例,某些欄位級的驗證規則如下所示:
· ProductName欄位不得超過40個字元
· QuantityPerUnit欄位不得超過20個字元
· ProductID、ProductName以及Discontinued欄位是必填的,而其他欄位則是可填可不填的
· UnitPrice、UnitsInStock、UnitsOnOrder以及ReorderLevel欄位不得小於0
這些規則可以或者說是應該在資料庫層被描述出來。ProductName和QuantityPerUnit欄位上的字元數限制可以通過Products表中相應列的資料類型來實現(分別為nvarchar(40) and nvarchar(20))。欄位“是否必填”可以通過將資料庫中表的相應列設定為“允許為NULL”來實現。為了保證UnitPrice、UnitsInStock、UnitsOnOrder以及ReorderLevel欄位的值不小於0,可以分別在它們的相應列上加一個約束。
除了在資料庫中應用了這些規則之外,它們同時也將被其應用在DataSet上。事實上,欄位長度和是否允許為空白等資訊已經被應用到了各DataTable的DataColumn集合中。我們可以在DataSet 設計工具(DataSet Designer)中看到已經存在的欄位級驗證,從某個DataTable中選擇一個欄位,然後在屬性視窗中就可以找到了。四所示,ProductDataTable中的QuantityPerUnit欄位允許空值並且最大長度為20各字元。如果我們試圖給某個ProductsDataRow的QuantityPerUnit屬性設定一個長度大於20個字元的字串,將會有一個ArgumentException被拋出。
圖四:DataColumn提供了基本的欄位級驗證
不幸的是,我們不能通過屬性視窗指定一個邊界檢查,比如UnitPrice的值不能小於0。為了提供這樣的欄位級驗證,我們需要為DataTable的ColumnChanging事件建立一個Event Handler。正如上一節教程中所提到的那樣,由類型化資料集建立的DataSet、DataTable還有DataRow對象可以通過partial類來進行擴充。使用這個技術,我們可以為ProductDataTable建立一個ColumnChanging的Event Handler。我們先在App_Code檔案夾中建立一個名為ProductsDataTable.ColumnChanging.cs的類檔案,如所示。
圖五:在App_Code檔案夾中添加新類
然後,給ColumnChanging事件建立一個Event handler,以保證UnitPrice、UnitsInStock、UnitsOnOrder以及ReorderLevel欄位的值不小於0。如果這些列的值超出範圍就拋出一個ArgumentException。
ProductsDataTable.ColumnChanging.cs
1public partial class Northwind
2{
3 public partial class ProductsDataTable
4 {
5 public override void BeginInit()
6 {
7 this.ColumnChanging += ValidateColumn;
8 }
9
10 void ValidateColumn(object sender, DataColumnChangeEventArgs e)
11 {
12 if(e.Column.Equals(this.UnitPriceColumn))
13 {
14 if(!Convert.IsDBNull(e.ProposedValue) && (decimal)e.ProposedValue < 0)
15 {
16 throw new ArgumentException("UnitPrice cannot be less than zero", "UnitPrice");
17 }
18 }
19 else if (e.Column.Equals(this.UnitsInStockColumn) ||
20 e.Column.Equals(this.UnitsOnOrderColumn) ||
21 e.Column.Equals(this.ReorderLevelColumn))
22 {
23 if (!Convert.IsDBNull(e.ProposedValue) && (short)e.ProposedValue < 0)
24 {
25 throw new ArgumentException(string.Format("{0} cannot be less than zero", e.Column.ColumnName), e.Column.ColumnName);
26 }
27 }
28 }
29 }
30}
第四步:給BLL類添加商務規則
除了欄位級的驗證,可能還有一些不能在單個列中表示的包含不同實體或概念的更進階的商務規則,比如:
· 如果一個產品被標記為“停用”,那麼它的單價就不能被修改
· 一個僱員的居住地必須與他(她)的主管的居住地相同
· 如果某個產品是某供應商唯一提供的產品,那麼這個產品就不能被標記為“停用”
BLL類應該保證始終都驗證應用程式的商務規則。這些驗證可以直接的添加到應用他們的方法中。
想象一下,我們的商務規則表明了如果一個產品是給定的供應商的唯一產品,那麼它就不能被標記為“停用”。也就是說,如果產品X是我們從供應商Y處購買的唯一一個產品,那麼我們就不能將X標記為停用;然而,如果供應商Y提供給我們的一共有3樣產品,分別是A、B和C,那麼我們可以將其中任何一個或者三個全部都標記為“停用”。挺奇怪的商務規則,是吧?但是商業上的規則通常就是跟我們平常的感覺不太一樣。
要在UpdateProducts方法中應用這個商務規則,那麼我們就應該先檢查Discontinued是否被設定為true。假如是這樣的話,那麼我們應該先調用GetProductsBySupplierID來看看我們從這個供應商處一共購買了多少產品。如果我們僅僅從這個供應商處購買了這一個產品,那麼我們就拋出一個ApplicationException。
UpdateProduct
1public bool UpdateProduct(string productName, int? supplierID, int? categoryID, string quantityPerUnit,
2 decimal unitPrice, short? unitsInStock, short? unitsOnOrder, short? reorderLevel,
3 bool discontinued, int productID)
4{
5 Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
6 if (products.Count == 0)
7 // 沒有找到匹配項,返回false
8 return false;
9
10 Northwind.ProductsRow product = products[0];
11
12 // 商務規則檢查 – 不能停用某供應商所提供的唯一一個產品
13 if (discontinued)
14 {
15 // 擷取我們從這個供應商處獲得的所有產品
16 Northwind.ProductsDataTable productsBySupplier = Adapter.GetProductsBySupplierID(product.SupplierID);
17
18 if (productsBySupplier.Count == 1)
19 // 這是我們從這個供應商處獲得的唯一一個產品
20 throw new ApplicationException("You cannot mark a product as discontinued if its the only product purchased from a supplier");
21 }
22
23 product.ProductName = productName;
24 if (supplierID == null) product.SetSupplierIDNull(); else product.SupplierID = supplierID.Value;
25 if (categoryID == null) product.SetCategoryIDNull(); else product.CategoryID = categoryID.Value;
26 if (quantityPerUnit == null) product.SetQuantityPerUnitNull(); else product.QuantityPerUnit = quantityPerUnit;
27 if (unitPrice == null) product.SetUnitPriceNull(); else product.UnitPrice = unitPrice.Value;
28 if (unitsInStock == null) product.SetUnitsInStockNull(); else product.UnitsInStock = unitsInStock.Value;
29 if (unitsOnOrder == null) product.SetUnitsOnOrderNull(); else product.UnitsOnOrder = unitsOnOrder.Value;
30 if (reorderLevel == null) product.SetReorderLevelNull(); else product.ReorderLevel = reorderLevel.Value;
31 product.Discontinued = discontinued;
32
33 // 更新產品記錄
34 int rowsAffected = Adapter.Update(product);
35
36 // 如果剛好更新了一條記錄,則返回true,否則返回false
37 return rowsAffected == 1;
38}
39
在展示層中響應驗證錯誤
當我們從展示層中調用BLL時,我們可以決定是否要處理某個可能會被拋出的異常或者讓它直接拋給ASP.NET(這樣將會引發HttpApplication的出錯事件)。在使用BLL的時候,如果要以編程的方式處理一個異常,我們可以使用try...catch塊,就像下面的樣本一樣:
1 ProductsBLL productLogic = new ProductsBLL();
2
3 // 更新ProductID為1的產品資訊
4 try
5 {
6 // 這個操作將會失敗,因為我們試圖使用一個小於0的UnitPrice
7 productLogic.UpdateProduct("Scott's Tea", 1, 1, null, -14m, 10, null, null, false, 1);
8 }
9 catch (ArgumentException ae)
10 {
11 Response.Write("There was a problem: " + ae.Message);
12 }
我們將在後面的教程中看到,當通過一個資料Web控制項(data Web Control)來進行插入、修改或刪除操作資料時,處理從BLL中拋出的異常可以直接在一個Event Handler中進行,而不需要使用try…catch塊來封裝代碼。
總結
一個具有良好架構的應用程式都擁有清晰的階層,每一個層次都封裝了一個特定的角色。在本教程的第一篇中,我們用類型化資料集建立了一個資料訪問層;這一篇中,我們又建立了一個商務邏輯層,它由App_Code中一系列的類構成,並調用DAL中相應的方法。BLL為我們的應用程式實現了欄位級和業務級的邏輯。除了建立一個獨立的BLL,就像我們在本節中所做的那樣,另外一個選擇是使用partial類來擴充TableAdapter中的方法。然而,使用這個技術並不能使我們可以重寫已經存在的方法,也不能將我們的DAL和BLL分開得足夠清晰。
完成了DAL和BLL之後,我們就準備開始處理展示層了。在下一個教程中,我們將簡單的介紹一些資料訪問的主題,並為整個教程定義一個一致的頁面呈現。