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