一. 引言
Visual Studio 2005提出了“用更少的代价实现更高的性能”的口号。Visual Studio 2005中提供了大量的向导—特别是在生成数据存取代码方面;这些向导大大减少了代码的编写量。请注意,自动添加到你的工程中的任何代码都遵循了某种严格 的逻辑和良好的设计模式。因此,全面理解Visual Studio 2005在这方面的工作原理有助于你以后进一步修改和扩展这些代码从而构建你自己的定制的数据存取层。本文将对表格适配器和绑定源组件相应的代码展开深入的分析。
自从微软发行.NET框架2.0、Visual Studio 2005和ASP.NET 2.0以来,其关键卖点之一在于,同样的工作如今你只需要编写更少的代码,并且常常不需要任何编码。
简言之,“no-code-at-all”的口号其实是大量存在的向导、插件与设计器—你可以通过鼠标拖动、复选框和按钮交互方式在其中进行编程。只要 对你想实现的内容加以“声明”,那么微软的工具会针对你提出的要求生成相应的代码。你可能还记得那名老口号“所见即所得”—正是在这种口号倡导下产生了如 Visual Basic这样的快速原型开发工具。我想,现在我们可以把这个口号再改一下,那就是“所得即工具所想”(what-you-get-is-what- the-tool-thinks-you-want)。
事实上,在Visual Studio 2005中,你会发现存在大量的Windows表单和ASP.NET数据控件用于连接到中间层对象以便迅速而高效地创建数据驱动的应用程序。
基于最新的Visual Studio 2005数据设计器,你可以通过用户驱动的向导来创建代码。然而,在你点击“Finish”按钮后得到的代码中往往包含了比以前版本的Visual Studio中更多的抽象。在此基础上,虽然你只需使用例如表格适配器和类型化的数据集等顶层对象,但是你最终得到的却是一个“瘦”的更好的中间层。而 且,更重要的是,你可以选择把这个顶层API连接到你自己的数据存取层,从而完成一个正规化多层系统。
我的建议是:在任何情况下,你 都应该避免把大量的ADO.NET相关的表单代码插入到ASP.NET页面和Windows表单事件的code-behind中。你应该总是坚持使用分层 设计,并使用数据传输模式和有效的数据描述方式。但是,Visual Studio能够提供数据集并自动生成ADO.NET代码。另外,如今的Visual Studio 2005还能够为你提供数据集和定制对象并能自动生成一个能够进行大幅度定制的抽象层。
在本文中,我将从面向对象设计的角度来分析和讨论Visual Studio 2005数据设计器生成的代码。
二. 构建一个数据驱动的Windows表单
让我们一步步来创建一个数据驱动的Windows表单应用程序。我们的总体想法是构建一个基于表单的应用程序,它能够显示并编辑来自于Northwind数据库多个表格的数据。图1显示了最终的应用程序运行情况。
图1.本文示例程序的最终显示结果。 |
这个表单上有一些数据绑定控件,还有几个数据连接器控件。典型情况下,你首先要添加BindingNavigator控件以便让用户在一个绑定数据源记 录中进行导航。这个BindingNavigator控件有一个类似于VCR的用户接口,它仅负责从数据源选择一个特定的记录并使之可应用于一个通用的编 程接口。这个BindingNavigator控件提供的用户接口很容易使人联想到Microsoft Access中的工具栏。
在Windows Form 2.0中,大多数控件不直接绑定到一个集合对象,而是使用一个中间对象—绑定源—该绑定源被绑定到一个典型的可枚举的数据源对象上。注意,这个BindingNavigator控件不会抛出任何异常。
绑定源组件的设计目的主要是为了简化表单上的控件和绑定数据之间的绑定。另外,它还提供了许多其它类型的服务(如当前状态管理,改变通知,过滤等)。如前面所提及,一个绑定源组件其实是在用户接口元素和后端数据之间添加了一个间接层,如图2所示。
图2.绑定源组件介于用户接口和数据源之间。 |
你需要把该绑定源组件连接到一个物理数据源,然后把表单上的控件绑定到该绑定源。从现在开始,任何与数据源相关的数据交互都是通过这个绑定源实现的。典型的操作包括导航、检索、排序、过滤和更新。
其实,.NET框架2.0中的一个绑定源组件是一个派生自类BindingSource的类的实例。注意,尽管这个术语比较新一些并且特定于.NET框架2.0;但是,绑定源组件“背后”的核心概念对于一位.NET Windows开发老手来说应该不陌生。
是的,这种绑定源组件其实就是一个没有用户界面的组件,专门设计用于让开发者从Visual Studio 2005内部管理绑定对象,并且多数是以声明方式实现的。
三. BindingSource类
BindingSource类负责包装一个数据源并通过它自己的对象模型来暴露该数据源。表格1列出了BindingSource基类的主要属性。
表格1.BindingSource类的编程接口。
属性 | 描述 |
AllowEdit | 指示是否能够编辑在底层数据源中的项。 |
AllowNew | 指示是否该新项能够被添加到底层数据源。 |
AllowRemove | 指示是否能够从底层数据源中删除这些项。 |
Count | 从底层数据源中取得的项的数目。 |
CurrencyManager | 取得一个对相关联的当前状态管理器的引用。 |
Current | 取得底层数据源中的当前项。 |
DataMember | 指示数据源中的一个特定的列表。 |
DataSource | 指示连接器绑定的数据源。 |
Filter | 用于过滤数据源的表达式。 |
IsReadOnly | 指示是否底层数据源是只读的。 |
IsSorted | 指示是否底层数据源中的该项已经被排序。 |
Item | 检索相应于指定索引的数据源项。 |
List | 取得连接器被绑定到的列表。 |
Position | 指示底层数据源中当前项的索引。 |
Sort | 指示用于排序的列名以及排序的顺序。 |
SortDirection | 指示在数据源中排序项的方法。 |
SortProperty | 取得用于排序数据源的PropertyDescriptor对象。 |
SupportsAdvancedSorting | 指示是否数据源支持多栏排序。 |
SupportsChangeNotification | 指示是否数据源支持改变通知。 |
SupportsFiltering | 指示是否数据源支持过滤。 |
SupportsSearching | 指示是否数据源支持搜索。 |
SupportsSorting | 指示是否数据源支持排序。 |
值得注意的是,BindingSource对象的设 计目的是既用来管理简单的数据绑定也应用于复杂的数据绑定场所—这意味着,它合并了.NET框架1.x中CurrencyManager和 PropertyManager的所有功能。基于此,我们应该注意到,表格1中的基本数据源经常指一个集合(例如,一个类型化的数据集),但也可以是单个 的对象(例如,一个独立的DataRow)。
从表格1中的属性可见,绑定源组件拥有一个Position成员,它用于指示当前选择的 数据项的索引。该BindingSource类并没有提供任何用户接口,因此这里所谓的“选择”纯粹是从逻辑上讲的。由绑定控件负责把逻辑选择转换成对用 户可见而且有意义的一些内容。Current属性指向在当前选择位置检索到的数据。该BindingSource类还暴露一些方法用于实现前后移动选择内容或跳转到一个特定的位置;还有一个事件,用于指示当前选择的元素已经发生改变。
为了实现这些功能并且使它们快速而容易地出现在用户接口级,你可以使用BindingNavigator控件并且把它关联到一个绑定源上。每当用户点击 图1中类似于VCR的按钮,绑定源上的Position和Current属性被更新并且激发CurrentChanged事件。就象在 WindowsForms 1.x时期的数据绑定一样,监听这些事件的控件接收通知并且能够适当更新各自的用户接口。下面,让我们继续讨论Visual Studio 2005中数据源的定义问题。
四. 把数据源导入Windows表单应用程序
为了把数据添加到你的Windows应用程 序,你首先要把一个BindingSource组件拖动到你的表单。然后,你要设置该组件的DataSource属性。存在许多可用的数据源,包括数组、 集合和定制类型列表。典型情况下,列表都是在集合的基础上扩展而成的,它们要实现下列任何接口:IBindingList,ITypedList或 IListSource。注意,流行的ADO.NET容器类,例如DataSet和DataTable,都属于最后一种类型,因为它们都实现了 IListSource接口。
在最开始,你的应用程序没有数据源—你必须要为之创建一个。当你选择BindingSource组件的DataSource属性时,你会遇到一个类似于图3所示的窗口。点击弹出窗口底部的链接,从而启动一个向导以便把一个新的数据源添加到当前工程。
图3.在工程上添加一个新的数据源。 |
此向导中,Visual Studio 2005会非常礼貌地询问应用程序想从哪里得到数据。存在三个可能的场所:数据库,外部Web服务或定制对象。如果你选择了Web服务,那么, Visual Studio 2005将打开“Add Web Reference”对话框以便让你选择使用一个本地的还是远程的Web服务并且创建相应的代理类。然后,将由你来检索数据并且把它绑定到控件。同样,你 可以从工程引用的任何程序集内选择一个定制对象。
定制对象集合或Web服务的创作者负责设计并且使用他们喜欢的任何方法来实现对象模 型。当你选择该数据库选项时,Visual Studio 2005会为你生成大量的代码。典型地,你需要添加一个DataSet组件,也即是通过一个XSD文件描述的一组相互关联的表格。这个DataSet组件 描述了一个具有一个或多个数据表格的内存DataSet对象。注意,到目前为此,Visual Studio 2005的行为与Visual Studio2003没有什么很大的不同—类型化的数据集类都是由声明性XSD文件创建的。
五. 表格适配器(TableAdapter)
一个表格适配器为一个应用程序与它的数据库之间的通信提 供支持。例如,一个表格适配器连接到一个数据库并且执行命令;任何返回的数据被存储到一个DataTable对象中以备进一步处理。你还能够使用一个表格 适配器来把更新内容发送回数据库。其实,一个表格适配器是一个工具生成的类的实例。一个表格适配器只是在.NET托管的提供程序内定义的适配器类的一个特 例。简言之,它是一个担当针对特定表格的适配器的包装器对象。注意,表格适配器没有基类。
下面的代码片断展示了一个表格适配器类典型的定义形式。
Partial Public Class CustomersTableAdapter Inherits System.ComponentModel.Component ... End Class |
从内部实现来看,一个表格适配器类合并了一个SqlCommand,SqlConnection和SqlDataAdapter对象的功能。在 Visual Basic.NET中,数据适配器使用了WithEvents修饰词以便捕获事件。表格2列举了一个表格适配器的内部属性。
表格2.一个表格适配器类的内部成员。
成员 | 修饰词 | 描述 |
Adapter Private | ReadOnly | 用于与相应的数据库表格进行通讯的数据适配器。 |
Connection | Friend | 用于与相应的数据库表格进行通讯的连接对象。 |
CommandCollection Protected | ReadOnly | 定义了一组描述表格适配器行为的命令对象。为了增强可以通过表格适配器实现的任务,你可以把一个新的命令添加到这个集合。 |
ClearBeforeFill | Public | 指示是否表格在填充之前应该为空。默认情况下为True。 |
内部数据适配器是在InitAdapter方法中初始化的。该方法是从Adapter属性的get存取器内进行调用的。
Private ReadOnly Property Adapter() _ As System.Data.SqlClient.SqlDataAdapter Get If (Me._adapter Is Nothing) Then Me.InitAdapter End If Return Me._adapter End Get End Property |
在此,适配器是一个助理对象,用于驱动在底层表格上的标准的CRUD(创建,读取,更新,删除)操作。这个适配器负责定义相应于Insert, Delete和Update命令的缺省T-SQL语句;它并没有包括一个Select命令。由于主要是为了便于使用数据填充数据表格,所以,适配器通过在 表格适配器中的一对public类型方法(分别为Fill和GetData)实现了它的Select功能。
任何数据操作都要求建立到 数据源的一个物理连接。相应源码中的列表1展示了Connection属性的内部实现。这个Connection字符串存储在应用程序的配置文件内,可以 在任何时候预以编辑而无需修改和重新编译基本代码。当你建立一个新的连接时,该信息被自动地传递到表格适配器中所有的命令对象。
后面你会看到,表格适配器类是用于实现数据存取的流行设计模式中的非常重要的元素之一。借助于CommandCollection属性,表格适配器类能够列出在表格上执行的所有行为。简言之,它定义了表格的“行为”。
Protected ReadOnly Property CommandCollection() _ As System.Data.SqlClient.SqlCommand() Get If (Me._commandCollection Is Nothing) Then Me.InitCommandCollection End If Return Me._commandCollection End Get End Property |
默认情况下,该CommandCollection仅包含一个命令—实现Select操作的T-SQL命令。下列代码展示了对这个集合的初始化。
Private Sub InitCommandCollection() _commandCollection = New SqlCommand(0) {} _commandCollection(0) = New SqlCommand _commandCollection(0).Connection = Me.Connection _commandCollection(0).CommandText = "SELECT * FROM Customers" _commandCollection(0).CommandType = CommandType.Text End Sub |
这个集合内的缺省命令对象被包装在Fill和GetData方法内(见源码中的列表2);其中,Fill负责使用命令结果填充数据表格,而GetData能够使用相同的数据返回一个新的DataTable对象。表格3列出了该表格适配器类的所有public方法。
表格3.一个表格适配器类提供的方法。
方法 |
描述 |
Fill | 使用执行命令集合中的缺省命令所返回的结果来填充与这个表格适配器相关联的数据表格。 |
GetData | 返回一个新创建的DataTable对象;其中填充有执行命令集合中的缺省命令取得的数据。 |
Delete | 执行与内部数据适配器相关联的DELETE命令。 |
Insert | 执行与内部数据适配器相关联的INSERT命令。 |
Update | 执行与内部数据适配器相关联的UPDATE命令。这个方法提供若干重载形式。 |