c#

位置:IT落伍者 >> c# >> 浏览文章

VS2005数据存取层深入剖析高级篇


发布日期:2018年11月20日
 
VS2005数据存取层深入剖析高级篇
扩展表格适配器

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

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

选择表格适配器设计器定义的一个任务触发特定的向导

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

Visual Studio 数据设计器提供的表格关系和表格适配器

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

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

SELECT FROM customers WHERE country=@country

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

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

添加到表格适配器的新任务

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

列表FillByCountry方法的源代码

The new FillByCountry method

Public Overridable Overloads Function FillByCountry( _

ByVal dataTable As CustomersDataTable _

ByVal country As String) As Integer

MeAdapterSelectCommand = MeCommandCollection()

If (country Is Nothing) Then

MeAdapterSelectCommandParameters()Value = SystemDBNullValue

Else

MeAdapterSelectCommandParameters()Value = CType(country String)

End If

If (MeClearBeforeFill = True) Then

dataTableClear()

End If

Dim returnValue As Integer = MeAdapterFill(dataTable)

Return returnValue

End Function

生成代码评价

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

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

Sub NorthwindForm_Load(ByVal sender As Object _

ByVal e As EventArgs) Handles MyBaseLoad

CustomersTableAdapterFill(nwDataSetCustomers)

//…………

End Sub

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

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

DAL通用模式

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

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

一般地在设计一个实现数据处理系统的后端时通常使用两种主要模型Domain模型和Table模型

Domain模型构画了一个对象模型其中每一个实体都是通过合并了特定行为和数据的ad hoc类进行描述的一个类的任何实例都相应于该模型中一个特定的实体关系是通过跨越所涉及的类的属性进行表达的

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

在Table模型中你要为目标数据库中的每个表格定义一个类任何要求处理数据的代码都是在单个类的实例上定义的总结来说如果你的数据模型中包括一个Order实体那么你应该使用域模型为每一个订单创建一个OrderEntity对象还有一个OrderManager对象用于使用表格模型来处理所有的订单很容易看出订单管理器对象十分类似Visual Studio 设计器中的表格适配器对象

在Domain模型和Table模型抽象模型中你可以发现各种具体的设计模式在大多数流行的设计模式中主要使用的是数据映射器和表格数据网关(TDG)

应用数据映射模式

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

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

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

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

一个使用了模式的多层应用程序架构

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

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

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

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

应用表格数据网关模式

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

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

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

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

上一篇:VS2005数据存取层深入剖析基础篇

下一篇:使用c#(datagrid控件)编辑xml文件