导言

  本教程的第一节所描述的数据访问层(Data Access Layer,以下简称为DAL)已经清晰地将表示逻辑与数据访问逻辑区分开了。不过,即使DAL将数据访问的细节从表示层中分离出来了,可它却不能处理任何的业务规则。比如说,我们可能不希望产品表中那些被标记为“停用”的产品的“分类编号”或“供应商编号”被更新;我们还可能需要应用一些资历规则,比如说我们都不希望被比自己的资历还要浅的人管理。另外一个比较常见的情况就是授权,比如说只有那些具有特殊权限的用户可以删除产品或是更改单价。

  我们其实可以将业务逻辑层(Business Logic Layer,以下简称BLL)看作是在数据访问层和表示层之间进行数据交换的桥梁,在这个章节中,我们将讨论一下如何将这些业务规则集成到一个BLL中。需要说明的是,在一个实际的应用程序中,BLL都是以类库(Class Library)的形式来实现的,不过为了简化工程的结构,在本教程中我们将BLL实现为App_Code文件夹中的一系列的类。图一向我们展示了表示层、BLL以及DAL三者之间的结构关系。

在ASP.NET 2.0中操作数据之二:创建一个业务逻辑层

图一: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。

在ASP.NET 2.0中操作数据之二:创建一个业务逻辑层

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

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

注意:如果你使用的是Visual Studio 标准版或以上版本(也就是说,你不是用的Visual Web Developer),那么你还可以使用
在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的产品

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"htmlcode">
[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)
 // 没有找到匹配的项,返回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;

 // 更新供应商的关于地址的信息
 int rowsAffected = Adapter.Update(supplier);

 // 如果刚好更新了一条记录,则返回true,否则返回false
 return rowsAffected == 1;
 }
}

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

第二步:通过BLL类访问类型化数据集

在本教程的第一节中,我们给出了直接使用类型化数据集的例子,不过在我们添加了BLL类之后,表示层就可以通过BLL来工作了。在本教程的第一节中的AllProducts.aspx的例子中,ProductsTableAdapter用于将产品列表绑定到GridView上,代码如下所示:

ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();

要使用新的BLL类,我们所需要做的仅仅是简单的修改一下第一行代码。用ProductBLL对象来代替 ProductsTableAdapter即可:

ProductsBLL productLogic = new ProductsBLL();
GridView1.DataSource = productLogic.GetProducts();
GridView1.DataBind();

BLL类也可以通过使用ObjectDataSource来清晰明了的访问(就像类型化数据集一样)。我们将在接下来的教程中详细的讨论ObjectDataSource。

在ASP.NET 2.0中操作数据之二:创建一个业务逻辑层

图三: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 Designer)中看到已经存在的字段级验证,从某个DataTable中选择一个字段,然后在属性窗口中就可以找到了。如图四所示,ProductDataTable中的QuantityPerUnit字段允许空值并且最大长度为20各字符。如果我们试图给某个ProductsDataRow的QuantityPerUnit属性设置一个长度大于20个字符的字符串,将会有一个ArgumentException被抛出。

在ASP.NET 2.0中操作数据之二:创建一个业务逻辑层

图四:DataColumn提供了基本的字段级验证

  不幸的是,我们不能通过属性窗口指定一个边界检查,比如UnitPrice的值不能小于0。为了提供这样的字段级验证,我们需要为DataTable的ColumnChanging事件建立一个Event Handler。正如上一节教程中所提到的那样,由类型化数据集创建的DataSet、DataTable还有DataRow对象可以通过partial类来进行扩展。使用这个技术,我们可以为ProductDataTable创建一个ColumnChanging的Event Handler。我们先在App_Code文件夹中新建一个名为ProductsDataTable.ColumnChanging.cs的类文件,如下图所示。

在ASP.NET 2.0中操作数据之二:创建一个业务逻辑层

图五:在App_Code文件夹中添加新类

  然后,给ColumnChanging事件创建一个Event handler,以保证UnitPrice、UnitsInStock、UnitsOnOrder以及ReorderLevel字段的值不小于0。如果这些列的值超出范围就抛出一个ArgumentException。

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);
  }
  }
  }
 }
}

第四步:给BLL类添加业务规则

除了字段级的验证,可能还有一些不能在单个列中表示的包含不同实体或概念的更高级的业务规则,比如:
·如果一个产品被标记为“停用”,那么它的单价就不能被修改
·一个雇员的居住地必须与他(她)的主管的居住地相同
·如果某个产品是某供应商唯一提供的产品,那么这个产品就不能被标记为“停用”

  BLL类应该保证始终都验证应用程序的业务规则。这些验证可以直接的添加到应用他们的方法中。
想象一下,我们的业务规则表明了如果一个产品是给定的供应商的唯一产品,那么它就不能被标记为“停用”。也就是说,如果产品X是我们从供应商Y处购买的唯一一个产品,那么我们就不能将X标记为停用;然而,如果供应商Y提供给我们的一共有3样产品,分别是A、B和C,那么我们可以将其中任何一个或者三个全部都标记为“停用”。挺奇怪的业务规则,是吧?但是商业上的规则通常就是跟我们平常的感觉不太一样。

  要在UpdateProducts方法中应用这个业务规则,那么我们就应该先检查Discontinued是否被设置为true。假如是这样的话,那么我们应该先调用GetProductsBySupplierID来看看我们从这个供应商处一共购买了多少产品。如果我们仅仅从这个供应商处购买了这一个产品,那么我们就抛出一个ApplicationException。

public bool UpdateProduct(string productName, int"You cannot mark a product as discontinued if its 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;

 // 更新产品记录
 int rowsAffected = Adapter.Update(product);

 // 如果刚好更新了一条记录,则返回true,否则返回false
 return rowsAffected == 1;
}

在表示层中响应验证错误

  当我们从表示层中调用BLL时,我们可以决定是否要处理某个可能会被抛出的异常或者让它直接抛给ASP.NET(这样将会引发HttpApplication的出错事件)。在使用BLL的时候,如果要以编程的方式处理一个异常,我们可以使用try...catch块,就像下面的示例一样:

ProductsBLL productLogic = new ProductsBLL();

// 更新ProductID为1的产品信息

try
{
 // 这个操作将会失败,因为我们试图使用一个小于0的UnitPrice
 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);
}

  我们将在后面的教程中看到,当通过一个数据Web控件(data Web Control)来进行插入、修改或删除操作数据时,处理从BLL中抛出的异常可以直接在一个Event Handler中进行,而不需要使用try…catch块来包装代码。

总结

  一个具有良好架构的应用程序都拥有清晰的层次结构,每一个层次都封装了一个特定的角色。在本教程的第一篇中,我们用类型化数据集创建了一个数据访问层;这一篇中,我们又建立了一个业务逻辑层,它由App_Code中一系列的类构成,并调用DAL中相应的方法。BLL为我们的应用程序实现了字段级和业务级的逻辑。除了创建一个独立的BLL,就像我们在本节中所做的那样,另外一个选择是使用partial类来扩展TableAdapter中的方法。然而,使用这个技术并不能使我们可以重写已经存在的方法,也不能将我们的DAL和BLL分开得足够清晰。

  完成了DAL和BLL之后,我们就准备开始处理表示层了。在下一个教程中,我们将简单的介绍一些数据访问的主题,并为整个教程定义一个一致的页面呈现。
编程愉快!

关于作者
Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用微软Web技术。Scott是个独立的技术咨询顾问,培训师,作家,最近完成了将由Sams出版社出版的新作,24小时内精通ASP.NET 2.0。他的联系电邮为mitchell@4guysfromrolla.com,也可以通过他的博客http://ScottOnWriting.NET与他联系。

标签:
ASP.NET,业务逻辑层

免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
桃源资源网 Design By www.nqtax.com