`

[翻译]Scott Mitchell 的ASP.NET 2.0数据教程之二:创建一个业务逻辑层

阅读更多
在ASP.NET 2.0中操作数据:创建一个业务逻辑层

英文原版

导言

本教程的第一节所描述的数据访问层(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)就可以创建新的类文件了。将这四个文件分别命名为ProductsBLLCategoriesBLLSuppliersBLL以及EmployeesBLL


图二:在BLL文件夹中添加4个新的类

接下来,让我们来给这些新建的类加上一些方法,简单的将第一节中的TableAdapter中的那些方法包装起来就行了。现在,这些方法将只能直接使用DAL中的那些方法,我们等会再来给他们加上一些业务逻辑。

注意:如果你使用的是Visual Studio 标准版或以上版本(也就是说,你不是用的Visual Web Developer),那么你还可以使用
ProductsBLL类中,我们一共需要为其添加7个方法:
l GetProducts() – 返回所有的产品
l GetProductByProductID(productID) – 返回指定ProductID的产品
l GetProductsByCategoryID(categoryID) –返回指定分类的产品
l GetProductsBySupplier(supplierID) –返回指定供应商的产品
l AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) – 向数据库中添加一条产品信息,并返回新添加的产品的ProductID
l UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID) – 更新一个数据库中已经存在的产品,如果刚好更新了一条记录,则返回true,否则返回false
l DeleteProduct(productID) – 删除指定ProductID的产品

ProductsBLL.cs
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->1usingSystem;
2usingSystem.Data;
3usingSystem.Configuration;
4usingSystem.Web;
5usingSystem.Web.Security;
6usingSystem.Web.UI;
7usingSystem.Web.UI.WebControls;
8usingSystem.Web.UI.WebControls.WebParts;
9usingSystem.Web.UI.HtmlControls;
10usingNorthwindTableAdapters;
11
12[System.ComponentModel.DataObject]
13publicclassProductsBLL
14{
15privateProductsTableAdapter_productsAdapter=null;
16protectedProductsTableAdapterAdapter
17{
18get{
19if(_productsAdapter==null)
20_productsAdapter=newProductsTableAdapter();
21
22return_productsAdapter;
23}

24}

25
26
27[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select,true)]
28publicNorthwind.ProductsDataTableGetProducts()
29{
30returnAdapter.GetProducts();
31}

32
33[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select,false)]
34publicNorthwind.ProductsDataTableGetProductByProductID(intproductID)
35{
36returnAdapter.GetProductByProductID(productID);
37}

38
39[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select,false)]
40publicNorthwind.ProductsDataTableGetProductsByCategoryID(intcategoryID)
41{
42returnAdapter.GetProductsByCategoryID(categoryID);
43}

44
45[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select,false)]
46publicNorthwind.ProductsDataTableGetProductsBySupplierID(intsupplierID)
47{
48returnAdapter.GetProductsBySupplierID(supplierID);
49}

50[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Insert,true)]
51publicboolAddProduct(stringproductName,int?supplierID,int?categoryID,stringquantityPerUnit,
52decimal?unitPrice,short?unitsInStock,short?unitsOnOrder,short?reorderLevel,
53booldiscontinued)
54{
55//新建一个ProductRow实例
56Northwind.ProductsDataTableproducts=newNorthwind.ProductsDataTable();
57Northwind.ProductsRowproduct=products.NewProductsRow();
58
59product.ProductName=productName;
60if(supplierID==null)product.SetSupplierIDNull();elseproduct.SupplierID=supplierID.Value;
61if(categoryID==null)product.SetCategoryIDNull();elseproduct.CategoryID=categoryID.Value;
62if(quantityPerUnit==null)product.SetQuantityPerUnitNull();elseproduct.QuantityPerUnit=quantityPerUnit;
63if(unitPrice==null)product.SetUnitPriceNull();elseproduct.UnitPrice=unitPrice.Value;
64if(unitsInStock==null)product.SetUnitsInStockNull();elseproduct.UnitsInStock=unitsInStock.Value;
65if(unitsOnOrder==null)product.SetUnitsOnOrderNull();elseproduct.UnitsOnOrder=unitsOnOrder.Value;
66if(reorderLevel==null)product.SetReorderLevelNull();elseproduct.ReorderLevel=reorderLevel.Value;
67product.Discontinued=discontinued;
68
69//添加新产品
70products.AddProductsRow(product);
71introwsAffected=Adapter.Update(products);
72
73//如果刚好新增了一条记录,则返回true,否则返回false
74returnrowsAffected==1;
75}

76
77[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update,true)]
78publicboolUpdateProduct(stringproductName,int?supplierID,int?categoryID,stringquantityPerUnit,
79decimal?unitPrice,short?unitsInStock,short?unitsOnOrder,short?reorderLevel,
80booldiscontinued,intproductID)
81{
82Northwind.ProductsDataTableproducts=Adapter.GetProductByProductID(productID);
83if(products.Count==0)
84//没有找到匹配的记录,返回false
85returnfalse;
86
87Northwind.ProductsRowproduct=products[0];
88
89product.ProductName=productName;
90if(supplierID==null)product.SetSupplierIDNull();elseproduct.SupplierID=supplierID.Value;
91if(categoryID==null)product.SetCategoryIDNull();elseproduct.CategoryID=categoryID.Value;
92if(quantityPerUnit==null)product.SetQuantityPerUnitNull();elseproduct.QuantityPerUnit=quantityPerUnit;
93if(unitPrice==null)product.SetUnitPriceNull();elseproduct.UnitPrice=unitPrice.Value;
94if(unitsInStock==null)product.SetUnitsInStockNull();elseproduct.UnitsInStock=unitsInStock.Value;
95if(unitsOnOrder==null)product.SetUnitsOnOrderNull();elseproduct.UnitsOnOrder=unitsOnOrder.Value;
96if(reorderLevel==null)product.SetReorderLevelNull();elseproduct.ReorderLevel=reorderLevel.Value;
97product.Discontinued=discontinued;
98
99//更新产品记录
100introwsAffected=Adapter.Update(product);
101
102//如果刚好更新了一条记录,则返回true,否则返回false
103returnrowsAffected==1;
104}

105
106[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Delete,true)]
107publicboolDeleteProduct(intproductID)
108{
109introwsAffected=Adapter.Delete(productID);
110
111//如果刚好删除了一条记录,则返回true,否则返回false
112returnrowsAffected==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 DataSetsProblem 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类之后,我们还要添加一些为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
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->1[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update,true)]
2publicboolUpdateSupplierAddress(intsupplierID,stringaddress,stringcity,stringcountry)
3{
4Northwind.SuppliersDataTablesuppliers=Adapter.GetSupplierBySupplierID(supplierID);
5if(suppliers.Count==0)
6//没有找到匹配的项,返回false
7returnfalse;
8else
9{
10Northwind.SuppliersRowsupplier=suppliers[0];
11
12if(address==null)supplier.SetAddressNull();elsesupplier.Address=address;
13if(city==null)supplier.SetCityNull();elsesupplier.City=city;
14if(country==null)supplier.SetCountryNull();elsesupplier.Country=country;
15
16//更新供应商的关于地址的信息
17introwsAffected=Adapter.Update(supplier);
18
19//如果刚好更新了一条记录,则返回true,否则返回false
20returnrowsAffected==1;
21}

22}

23
可以从页面顶部的链接处下载BLL类的完整代码。

第二步:通过BLL类访问类型化数据集
在本教程的第一节中,我们给出了直接使用类型化数据集的例子,不过在我们添加了BLL类之后,表示层就可以通过BLL来工作了。在本教程的第一节中的AllProducts.aspx的例子中,ProductsTableAdapter用于将产品列表绑定到GridView上,代码如下所示:
1ProductsTableAdapterproductsAdapter=newProductsTableAdapter();
2GridView1.DataSource=productsAdapter.GetProducts();
3GridView1.DataBind();
要使用新的BLL类,我们所需要做的仅仅是简单的修改一下第一行代码。用ProductBLL对象来代替 ProductsTableAdapter即可:
1ProductsBLLproductLogic=newProductsBLL();
2GridView1.DataSource=productLogic.GetProducts();
3GridView1.DataBind();
BLL类也可以通过使用ObjectDataSource来清晰明了的访问(就像类型化数据集一样)。我们将在接下来的教程中详细的讨论ObjectDataSource。


图三:GridView中显示的产品列表

第三步:给DataRow添加字段级验证
字段级验证是指在插入或更新时检查业务对象所涉及到的所有属性值。拿产品来举个例,某些字段级的验证规则如下所示:
· ProductName字段不得超过40个字符
· QuantityPerUnit字段不得超过20个字符
· ProductIDProductName以及Discontinued字段是必填的,而其他字段则是可填可不填的
· UnitPriceUnitsInStockUnitsOnOrder以及ReorderLevel字段不得小于0
这些规则可以或者说是应该在数据库层被描述出来。ProductName和QuantityPerUnit字段上的字符数限制可以通过Products表中相应列的数据类型来实现(分别为nvarchar(40) and nvarchar(20))。字段“是否必填”可以通过将数据库中表的相应列设置为“允许为NULL”来实现。为了保证UnitPriceUnitsInStockUnitsOnOrder以及ReorderLevel字段的值不小于0,可以分别在它们的相应列上加一个约束
除了在数据库中应用了这些规则之外,它们同时也将被其应用在DataSet上。事实上,字段长度和是否允许为空等信息已经被应用到了各DataTable的DataColumn集合中。我们可以在数据集设计器(DataSet Designer)中看到已经存在的字段级验证,从某个DataTable中选择一个字段,然后在属性窗口中就可以找到了。如图四所示,ProductDataTable中的QuantityPerUnit字段允许空值并且最大长度为20各字符。如果我们试图给某个ProductsDataRowQuantityPerUnit属性设置一个长度大于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,以保证UnitPriceUnitsInStockUnitsOnOrder以及ReorderLevel字段的值不小于0。如果这些列的值超出范围就抛出一个ArgumentException。

ProductsDataTable.ColumnChanging.cs
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->1publicpartialclassNorthwind
2{
3publicpartialclassProductsDataTable
4{
5publicoverridevoidBeginInit()
6Codehighlighter1_1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics