除了字段级的验证,可能还有一些不能在单个列中表示的包含不同实体或概念的更高级的业务规则,比如:
·如果一个产品被标记为“停用”,那么它的单价就不能被修改
·一个雇员的居住地必须与他(她)的主管的居住地相同
·如果某个产品是某供应商唯一提供的产品,那么这个产品就不能被标记为“停用”
BLL类应该保证始终都验证应用程序的业务规则。这些验证可以直接的添加到应用他们的方法中。
想象一下,我们的业务规则表明了如果一个产品是给定的供应商的唯一产品,那么它就不能被标记为“停用”。也就是说,如果产品X是我们从供应商Y处购买的唯一一个产品,那么我们就不能将X标记为停用;然而,如果供应商Y提供给我们的一共有3样产品,分别是A、B和C,那么我们可以将其中任何一个或者三个全部都标记为“停用”。挺奇怪的业务规则,是吧?但是商业上的规则通常就是跟我们平常的感觉不太一样。
要在UpdateProducts方法中应用这个业务规则,那么我们就应该先检查Discontinued是否被设置为true。假如是这样的话,那么我们应该先调用GetProductsBySupplierID来看看我们从这个供应商处一共购买了多少产品。如果我们仅仅从这个供应商处购买了这一个产品,那么我们就抛出一个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) // 没有找到匹配项,返回false return false; Northwind.ProductsRow product = products[0]; // 业务规则检查 – 不能停用某供应商所提供的唯一一个产品 if (discontinued) { // 获取我们从这个供应商处获得的所有产品 Northwind.ProductsDataTable productsBySupplier = Adapter.GetProductsBySupplierID(product.SupplierID); if (productsBySupplier.Count == 1) // 这是我们从这个供应商处获得的唯一一个产品 throw new ApplicationException("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分开得足够清晰。