一、 扩展表格适配器

  当你设计业务层和数据存取层时,你基本上都是把实体和关系分别 映射到数据库表格及表格列中。典型地,我们期望每个实体的行为是:通过一系列语句来执行数据库操作。那么,你该如何描述一个实体的行为呢?数据存取设计模 式在这方面提供了一定的指导作用。其实,一个表格适配器的CommandCollection属性是一个内部数据结构,它提供的语句用于硬编码在一个表格 上期望的行为。

  因此,如果你想在Customers表格上添加一种行为,那么,你需要把一个新的T-SQL语句添加到 CommandCollection属性,然后经由CustomersTableAdapter类中的一个新的方法使其成为public型属性。 Visual Studio 2005提供一个简单的向导来完成所有这些工作。只要简单地选择在表格适配器设计器中定义的一个任务,你就可以触发这个向导,如图4所示。

VS2005数据存取层深入剖析高级篇_存取层
图4.选择表格适配器设计器定义的一个任务触发特定的向导。

  当你在Visual Studio 2005中选择一个表格适配器组件时,它将显示一个灵敏标签。点击它之后,Visual Studio就会显示一个菜单。如果你选择编辑查询,那么,你会得到当前数据集组件中所有表格的一个图形表示(见图5)。

VS2005数据存取层深入剖析高级篇_休闲_02
图5.Visual Studio 2005数据设计器提供的表格关系和表格适配器。

   要编辑一个现有查询,你只要选择表格适配器任务列表中的任务并编辑Properties窗口中的属性即可。为了添加一个新任务,你可以选择“Add Query”,然后遵循随后向导的提示即可。该向导最终引导你定义一个新的命令,无论它是一个查询或是一个更新语句。完成之后,你会看到一个额外的入口出 现在任务列表内;另外,对基本代码也进行了一定程度的修改。

  现在,假定你定义了一个新的查询语句—用于根据不同国家加载相应的顾客信息,那么,最后的语句看起来类似如下:

SELECT ... FROM customers WHERE country=@country

  向导会要求你为使用该语句检索数据(GetDataXXX)并填充传递的数据表格(FillXXX)的两个方法进行命名。当然,你可以选择仅生成其中一个方法。

  让我们如图6所示来命名方法GetDataByCountry和FillByCountry。

VS2005数据存取层深入剖析高级篇_存取层_03
图6.添加到表格适配器的新任务。

  这两个新的方法都要使用从命令集合中提取的命令来建立适配器的SelectCommand语句并执行它。列表4展示了该FillByCountry方法的源代码。

  列表4.FillByCountry方法的源代码。

The new FillByCountry method
Public Overridable Overloads Function FillByCountry( _
ByVal dataTable As CustomersDataTable, _
ByVal country As String) As Integer
 Me.Adapter.SelectCommand = Me.CommandCollection(1)
 If (country Is Nothing) Then
  Me.Adapter.SelectCommand.Parameters(0).Value = System.DBNull.Value
 Else
  Me.Adapter.SelectCommand.Parameters(0).Value = CType(country, String)
 End If
 If (Me.ClearBeforeFill = True) Then
  dataTable.Clear()
 End If
 Dim returnValue As Integer = Me.Adapter.Fill(dataTable)
 Return returnValue
End Function
  二、 生成代码评价

  值得注意的是,围绕DataSet和BindingSource组件生成的代码使用了中断连接模型和普通的T-SQL语句。这并不是说,不管你是通过Visual Studio 2005向导编辑代码或是手工编辑,都可以毫无顾忌地使用存储过程和事务。

   这里所使用的中断连接模型要求:在表单启动时,所有的数据都必须已经被加载到内存中。但是,在这种情况下,你仍然能够自由地编辑自动生成的代码,例如使 应用程序仅加载它在运行时刻需要的数据。默认情况下,一个使用Visual Studio 2005数据设计器生成代码的数据绑定表单的Load事件看起来如下所示:

Sub NorthwindForm_Load(ByVal sender As Object, _
ByVal e As EventArgs) Handles MyBase.Load

CustomersTableAdapter.Fill(nwDataSet.Customers)
//…………
End Sub

  Visual Studio 2005在绑定的DataSet组件内相应于每个表格适配器放入了一个对Fill方法的调用。

   须知,Visual Studio 2005生成的代码将同工程的其它部分一起编译。在无法完全满足你的需要和要求时,决不应该相当然地使用这些代码。你可以自由地修改它;但是,在这样做之 前,你首先应该深入理解它的逻辑与结构。不要相当然认为,一个向导能为你实现一切或者干脆放弃使用之。你应该接受它,然后修改之以适合你的需要。大多数时 候,这是你必须做的,当然也是最明智的选择。

  三、 DAL通用模式

  任何相当 复杂的系统都要求实现许多不同层来存取和操作数据。比如,你可以使用业务逻辑层(BLL)来与用户接口进行通讯并且提供安全检查、数据校验以及其它服务, 例如数据的预处理和事后处理等;另一方面,你可以使用数据存取层(DAL)来存取和检索数据。其实,该DAL仅是一个合并了API功能实现数据库存取的组 件。CRUD部分的任务相应于一个DAL部分的目标,也即是把数据暴露给业务层以便实现数据交换。这些层按特定顺序“迭放”在一起;DAL层由BLL层使 用,并且不应该被用户接口所利用以免打破各层之间的分离原则。

  当你想创建一个库以便合并数据存取功能时,你该怎么办呢?是否应该使用 Visual Studio 2005数据设计器所提出的定制的基于表格的方法?或是具有较好的条件而使用一个商业性的O/R映射器工具来生成大多数代码?还是打算自己开发DAL?幸 好,这些选择并非互斥的,完全可以根据需要进行混合使用。于是,问题出现了:你应该怎么办?现在,让我们首先来理解通用模式。在这样做时,你还是应该首先 搞清楚Visual Studio 2005所提供的方法和逻辑。

  一般地,在设计一个实现数据处理系统的后端时,通常使用两种主要模型:Domain模型和Table模型。
Domain模型构画了一个对象模型,其中,每一个实体都是通过合并了特定行为和数据的ad hoc类进行描述的。一个类的任何实例都相应于该模型中一个特定的实体;关系是通过跨越所涉及的类的属性进行表达的。

  【译者注】 类似于图书馆里的书籍检索,即书籍库(数据库)相对稳定不变,不同用户的查询要求是千变万化的,这种检索就称为“ad hoc”。基于Web的搜索引擎也属于这一类。

   在Table模型中,你要为目标数据库中的每个表格定义一个类。任何要求处理数据的代码都是在单个类的实例上定义的。总结来说,如果你的数据模型中包括 一个Order实体,那么,你应该使用域模型为每一个订单创建一个OrderEntity对象,还有一个OrderManager对象用于使用表格模型来 处理所有的订单。很容易看出,该“订单管理器”对象十分类似Visual Studio 2005设计器中的表格适配器对象。
 
  在Domain模型和Table模型抽象模型中,你可以发现各种具体的设计模式。在大多数流行的设计模式中,主要使用的是数据映射器和表格数据网关(TDG)。
  四、 应用数据映射模式

  一个数据映射(DM)对象负责把从表格中提取的原始数据加载到 内存对象(这些对象被更为紧密地映射到更高一级:实体-关系数据模型)。该DM模式尤其适合于复杂的域逻辑领域—此时数据的物理结构必须被加以提取以便直 接编码、简化阅读和确保恰当的维护费用。典型地,一个高度复杂的逻辑一般都有一些额外要求,例如多数据库支持和严格的单元测试等。在两种情况下,不同的系 统发布,或开发周期中的不同步骤,可能要求替换整个数据映射层以便应用一种不同的逻辑,针对不同的目标数据库,或是仅为了实现测试目的。你进行越多的抽象 并且在系统中进行越多的分层,那么以后对于你以及你的客户将越发有益。

  毋庸置疑,该DM模式有时看起来有些过于复杂,但对于丰富的而复杂的系统来说一定是一种可行的选择。一种更为简单的变种,特别适合于简单的逻辑实现,就是活动记录模式。

  在一种DM情况下,你需要为抽象数据模型中的每一个实体创建许多类。你将创建一个实体类(也许一个类型安全的集合类)和一个管理器类来为DAL提供公共入口点。列表5展示了一些具体的示例代码。

  列表5.DM模式(Employee实体)的示例代码。

Public Class Employee
 Public ID As Integer
 Public FirstName As String
 Public LastName As String
 //……
End Class
Public Class EmployeeCollection : Inherits Collection (Of Employee)
End Class
Public Class EmployeeManager
 Public Function FindAll() As EmployeeCollection
 Public Function Find(ByVal empID As Integer) As Employee
 Public Sub Update(ByVal emp As Employee)
 Public Sub Insert(ByVal emp As Employee)
 Public Sub Delete(ByVal emp As Employee)
 Public Sub Delete(ByVal empID As Integer)
 Public Function FindOrders(ByVal empID As Integer) _
 As OrderCollection
 //……
End Class

   这个EmployeeManager类中提供的方法实现怎么样?不管在你的设计中引入了多少的抽象,在某种程度上你都需要手工实现连接字符串, ADO.NET命令和事务等内容。当然,你能够把这部分代码直接插入到EmployeeManager的方法体内。然而,还有一种更好的方法就是,使用一 组基于TDG模式的中间集合类来实现这一点。图7展示了整个实现框架。

VS2005数据存取层深入剖析高级篇_VS2005_04
图7.一个使用了模式的多层应用程序架构。

   其基本思想是,由描述层创建一个给定实体(比如说,employee表格)的管理器类的实例。一方面,该管理器类要对描述层暴露高级对象;另一方面,它 还负责连接到底层DAL以便进行物理数据存取。在该管理器和DAL可能存在各种层。为了实现更多的灵活性,你可能想使用一种可替换的工厂机制并且是针对不 同的数据库的。

  反过来,从描述层对管理器类的调用将会实例化一个数据存取提供者工厂类。这个类将从配置文件信息(类和程序集)中读取关于该工厂类的信息以便用来创建其支持的实体(如雇员,顾客,产品,订单等)的数据提供者。

  每个工厂类都要实现一个合同接口—该接口将依赖于一个方法来返回针对各种实体的数据提供者。最后,这些数据提供者要实现一个接口以便与管理器类中的方法相匹,并使用TDG模式来得到/设置一特定数据库中的物理数据。

  基于这一模式,通过简单地改变一下配置文件内的某个入口,你就可以使整个系统工作在一个完全不同的数据库之上。现在,你有了一些类—数据提供者—你可以在其中编写优化的数据库代码—并且由于使用了基于合同的接口,所以你不必去打破与上一层的关系。
 
  五、 应用表格数据网关模式

  在复杂的系统中,你常常是把TDG模式与DM以及 Factory相结合以便创建一种极其灵活的而可扩展的方案。在此,我必须对两个结构化方面加以解释。首先,你可以直接在管理器类的方法中编写 ADO.NET代码,舍弃工厂,从而获得一种纯DM方案。同样,你可以编写一个纯TDG方案(稍后讨论)并且获得一个类似Visual Studio 2005的方案。

  用TDG术语描述,网关是一个包装了对一个数据库表格存取的对象,而你只需要使用一个处理所有表格行的对象即可。典型地,该网关类中的方法将返回ADO.NET对象。下面是一个例子:

Public Class EmployeeGateway
Public Function Find(ByVal id As Integer) As IDataReader
 //……
End Function
Public Function FindAll() As DataTable
 //……
End Function
Public Sub Save(ByVal id As Integer, ...)
 //……
End Sub
Public Sub Insert(...)
 //……
End Sub
Public Sub Delete(ByVal id As Integer)
 //……
End Sub
:
End Class

  在此,你添加越多的方法,该TDG模型越发进一步发展成为一个DM模型。如果你把这个网关的抽象定义与前面讨论的表格适配器类作一下比较,那么,你会发现在这两者之间存在一种明显的匹配。

  六、 总结

  在基于模式进行设计时,首先给人的感觉往往是有点过于抽象,因为你,作为一名开发人员,的最终目的是要编写出能够实际工作的具体代码。因此,模式应该成为你开发的指南,而你未必在一切开发中都基于模式进行编程。

  根本上说,应该基于你的爱好和对于一种既定方法的习惯程度来作出决定。然而同时,不要惧怕在复杂的问题上尝试一种新的方式和更结构化的方法。你可能很快发现你已经喜欢上它了。

   Visual Studio 2005提供的数据设计器向导生成的代码基于经典的TDG方法进行工作。其实,并没有什么糟糕的内容,当然也没有新的或秘密的东西。使用向导的关键在于真 正了解它相对你的需要实现了什么。因此,为了全面地理解Visual Studio 2005数据设计器的功能,全面地归纳一下企业数据存取设计模式是非常必要的。

  本文首先解释了Visual Studio 2005代码的内在机制,然后,从一种模式设计的角度来分析你完全有希望对这些代码作进一步的改进。Visual Studio 2005数据设计器的确创建了一种结构化的有效的DAL,但是你必须理解它的代码并且着手编辑它才行—不过,这种模式未必适合于复杂的应用程序开发中。现 在,既然你已经对它有了全面的理解;那么,接下来你就应该开心地试用Visual Studio 2005了!