ASP.NET2.0数据操作之创建业务逻辑层

导言

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

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

080511160131011.gif

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

  图二:在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。

080511160280571.gif

  图三: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被抛出。

080511160280572.gif

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

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

080511160280573.gif

  图五:在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之后,我们就准备开始处理表示层了。在下一个教程中,我们将简单的介绍一些数据访问的主题,并为整个教程定义一个一致的页面呈现。



转载于:https://www.cnblogs.com/chenbg2001/archive/2009/01/23/1380517.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/262673.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不使用注解和使用注解的web-service-dao结构

一、未使用注解的web-service-dao结构 1、action类源码 其中&#xff0c;service作为一个成员属性&#xff0c;采用的是层层调用&#xff0c;service类中dao作为一个成员属性&#xff0c;再成员方法中调用&#xff1b; 2、bean.xml中装配bean&#xff1a; 3、创建spring容器&am…

awk特殊用法

一、从固定格式中取出IP所在的class&#xff0c;并列出例&#xff1a;class A { 192.168.1.1 192.168.2.1 192.168.3.1 192.168.169.69}class B { 192.168.1.1 192.168.169.69}sed -nr /\{/{:1;N;/\}/!b1;/192.168.169.69/s#^([^{]).*#\1#p} fileawk -vRScla…

XML基础——extensible markup language

一、xml概念 1、xml和html区别 其中&#xff0c;xml是纯文本文件&#xff0c;跨语言&#xff1b;浏览器有html解析器也有xml解析器&#xff1b; 2、和properties配置文件区别 二、xml语法 1、基本语法 三、xml组成部分 中国电脑默认GBK编码格式&#xff08;中文编码&#xff09…

显示单选列表对话框

通过AlertDialog.Builder类的setSingleChoiceItems方法可以创建带单按钮的列表&#xff1a;方法如下&#xff1a;1、从资源文件中装载数据&#xff1a;public Builder setSingleChoiceItems(int itemsId, int checkedItem, final OnClickListener listener)2、从数据集中装载数…

全修CALL

PUSH -1PUSH 0PUSH 0CALL 005A8690ADD ESP,0Cbp send,点全修CTRLf9一直返回到没有参数 基本上这个返回就是 功能CALL 0012EB08 005869B2 返回到 elementc.005869B2 来自 elementc.0058E8A00012EB1C 00588B1F 返回到 elementc.00588B1F 来自 elementc.005869800012EB28 …

OSChina 周六乱弹 —— 这个版本的小红帽听说过吗?

2019独角兽企业重金招聘Python工程师标准>>> 想想当年刚出来工作的时候&#xff0c;小小编还真是单纯&#xff0c;以为广阔天地大有作为&#xff0c;可是呢。。。 上热门&#xff1a;刚出来工作的时候&#xff0c;大人千叮万嘱社会很复杂&#xff0c;要学会控制自己…

XML解析

一、解析xml的两种方式 1、 其中&#xff0c;xml文件被解析之后产生的dom树可能是原xml文件内存的成千上万倍&#xff0c;所以占内存&#xff1b;一般是服务器端&#xff1b; 2、sax逐行读取解析的方式&#xff0c;读一行释放一行&#xff0c;移动端采用&#xff1b; 其中&…

Arcgis Server初学笔记(一)

什么是Arcgis Server(以下简称AS)&#xff1f; AS是一个基于web的企业级GIS解决方案。AS为创建和管理基于服务器的GIS应用提供了一个高效的框架平台。AS宿主了各种GIS资源&#xff0c;并把他们作为服务发送到客户端。Accgis Server架构 AS是一个分布系统&#xff0c;…

XML解析——Jsoup解析器

一、Jsoup解析器快速入门案例 Docement对象&#xff0c;文本对象&#xff0c;包含着各个Dom树结构 1、引入Jsoup解析器的jar包放在lib文件夹下后&#xff0c;写java代码 其中&#xff0c; 二、Jsoup对象 1、Jsoup解析器解析xml和html的有关对象 其中&#xff0c;通过统计资源定…

jQuery缓存数据——仿Map

2019独角兽企业重金招聘Python工程师标准>>> 最近在工作中遇到了这样一个情景。有些数据是从后台读取的&#xff0c;但是我暂时不需要展示在页面上&#xff0c;那怎么办呀&#xff1f;——缓存呀。今天我就来分享一下我所了解的Jquery缓存数据的方法。 首先分享1篇博…

Jsoup快速查询

一、selector选择器 二、Xpath查询 转载于:https://www.cnblogs.com/wmqiang/p/11568184.html

Servlet生命周期和方法

一、五个生命周期方法&#xff0c;有三个很重要&#xff0c;初始化方法、提供服务方法和销毁方法 1、三个主要方法 2、另外两个重写的成员方法只做了解 二、生命周期详解 其中&#xff0c;每次刷新页面都是一次对servlet访问&#xff1b; 页面访问&#xff0c;根据域名找到主机…

Servlet3.0注解配置访问路径和urlParttern配置

一、Servlet用注解配置访问路径 二、IDEA的tomcat相关配置 其中&#xff0c;第一点的配置文件&#xff0c;直接在IDEA的可视化操作界面修改就可以改掉配置文件中内容&#xff1b; 三、urlParttern配置 其中&#xff0c;* 时通配符&#xff0c;优先级最低&#xff1b; 转载于:ht…

现货黄金入门知识普及一:图形分析之K线理论

&#xff2b;线又称阴阳线、棒线、红黑线或蜡烛线&#xff0c;最早起源于日本德川幕府时代的米市交易&#xff0c;经过二百多年的演进&#xff0c;现已广泛应用于证券市场的技术分析中&#xff0c;成为技术分析中的最基本的方法之一&#xff0c;从而形成了现在具有完整形式和分…

openssl工具的使用以及创建私有CA

openssl软件包在安装之后&#xff0c;主要会生成三段重要内容&#xff1a;加密库ssl相关库文件openssl命令行工具下面就来介绍一下openssl命令行工具的使用&#xff1a;openssl和yum类似&#xff0c;有着许许多多的子命令&#xff1a;如果要获得这些子命令的相关man文档&#x…

Servlet体系结构

一、使用HttpServlet 其中&#xff0c;HttpServlet在重写的service()方法中对http请求的共7中提交方式进行了判断&#xff0c;所以只要我们只要重写对应的请求方式处理逻辑方法 doGet()和doPost()方法就可以&#xff1b; 浏览器直接访问servelet是get请求&#xff0c;参数列表会…

HTTP快速入门

一、tomcat端口号设置为80&#xff0c;访问时候可以不加&#xff1b;http协议1.1版本可以复用连接&#xff0c;请求结束后会稍微等会&#xff1b; 二、 表单&#xff0c;get方式提交&#xff1a; 三、user-agent告诉服务器是哪个浏览器&#xff0c;代码中解决兼容性问题&#x…

分类(二):基于向量空间模型的文本分类

2019独角兽企业重金招聘Python工程师标准>>> 利用向量空间模型进行文本分类的思路主要基于邻近假设&#xff08;contiguity hypothesis&#xff09;。 邻近假设&#xff1a; 同一类的文档会构成一个邻近区域&#xff0c;而不同类的邻近区域之间是互不重叠的。 1、Ro…

Request请求对象

一、Request对象由服务器创建&#xff0c;我们使用 浏览器访问服务器资源原理&#xff1a; 二、Request体系结构 其中&#xff0c;servlet 的service()方法参数列表是 servletRequest对象&#xff0c; HttpServlet 的 doGet()和 doPost() 方法参数列表是用的 HttpServletReques…