ASP.NET2.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)就可以创建新的类文件了。将这四个文件分别命名为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向导中的操作更加轻松和心情愉快。
posted on 2007-07-28 22:03  石川  阅读(206)  评论(0编辑  收藏  举报