导 读:.NET 在数据存取方面做了很大的调整。在.NET 框架下,数据存取是由ADO.NET来完成的,这是一个ADO的改进和完善版本。它最显著的变化是其完全基于XML。而对于从事ADO开发的人员来说,Recordset对象的消失也令他们感到惊奇。
翻译整理:.net技术网(www.51dotnet.com)slash
原文出处:http://www.dnjonline.com/articles/essentials/iss22_essentials.html
表4显示了DataSet 中现有的数据。可以发现现在有两种数据类型,同时SCHEMA 也做了相应的调整来描述这两种数据类型。
请注意我们现在并没有做创建一个内连接或外连接,而是类似于用SHAPE 语言用两个单独的表创建了一个分级的Recordset对象。当然你也可以创建连接,这在DataSet中表现为一个表。
ADO开发者在操作Recordset 对象的时候,需要知道他们到底需要一个客户端还是服务器端的光标。MoveFirst 或是 AbsolutePosition之类的操作,在服务器端光标的情况下将消耗很大的服务器资源,但在客户端光标的情况下,确是高效和有力的工具。两类光标存在着巨大的差异。一个客户端光标的Recordset 对象事实上更类似于一个高性能的数组,而不是一一种序列化的存取结构。
与之相对,DataSet 始终是'客户端的',并且可以发挥'高性能数组'存取模型的最高效率。在Recordset 中有字段集合,但对于一个Recordset 对象,却没有相应的集合。而DataSet中的所有表都有一个列和一个行的集合,你可以使用简单的随机存取技术来操作它们,表5显示了在ADO.NET 中操作DataSet的对象模型。
通过循环的方式你可以对DataTable中的每一行进行操作,然而我在下一个例子中采用了另一种方法:DataTable 的 Select 方法。这是一个重载方法,从本质上来说,它相当于结合了Recordset的FILTER 和 SORT 属性。SELECT 方法返回一个以DataRow对象为元素的数组--能够利用标准的数组的方法对它处理。需要注意的是所有这些过程是在你程序的缓存中进行的,DataSet 已经和数据源完全的断开了。
下面的示例代码向DataSet中填充两个数据表(第二个表没有使用 WHERE 子句)然后用SELECT 方法从'Authors'返回一个数组,并利用该结果创建一个动态的下拉列表。
Dim dc As New ADODataSetCommand( _
"select au_id, au_fname," & _
" au_lname from authors", strConnect)
Dim ds As New DataSet()
' Declare an array of DataRows
Dim dr() As DataRow
Dim i As Integer
dc.FillDataSet(ds, "Authors")
dc = New ADODataSetCommand( _
"select * from titleauthor", strConnect)
dc.FillDataSet(ds, "Titles")
dr = ds.Tables("Authors").Select( _
"au_lname >= 'R'", "au_lname ASC")
For i = 0 To UBound(dr)
listbox1.Items.Add( _
CStr(dr(i)("au_fname")) & " " _
& CStr(dr(i)("au_lname")))
Next
在这里,SELECT 语句返回所有的行,其中的 last name 的打头字母在 'R'之前,并且对这些行进行了分类,'Titles'表在这里被忽略了
表之间的联系
如果你没有利用SHAPE LANGUAGE 进行过工作,你很可能只是创建一个拥有一个数据表的 DataSet 并对其进行操作,就象Recordset 对象一样。当你一旦向DataSet 中加入了多个表,你会希望在它们之间建立关联以便于操作。在下面的代码中,假定以与上例完全相同的方法创建了一个名为DS的DataSet 对象:
Dim dr() As DataRow
Dim drChildren() As DataRow
Dim dl As DataRelation
Dim i, j As Integer
dl = New DataRelation("AuthorTitles", _
ds.Tables("Authors").Columns("au_id"), _
ds.Tables("Titles").Columns("au_id"))
ds.Relations.Add(dl)
dr = ds.Tables("Authors").Select( _
"au_lname >= 'R'", "au_lname ASC")
For i = 0 To UBound(dr)
listbox1.Items.Add( _
CStr(dr(i)("au_fname")) & " " _
& CStr(dr(i)("au_lname")))
drChildren = dr(i).GetChildRows(dl)
For j = 0 To UBound(drChildren)
listbox1.Items.Add(" " & _
CStr(drChildren(j)("title_id")))
Next
Next
DataSetCommands
表 6:
使用list box显示两个表的一多关系
这段代码在'Authors' 表和 'Titles'表之间建立了一个父子关系的关联,这是通过创建一个DataRelation对象(命名为dl)并将它加入DataSet实现的。关联指定 au_id 为关键字段,通过对子表('Titles')中 au_id 的匹配来得到父表中每一行的子行。在ADO 数据筛选的SHAPE 语言中是,这是通过 RELATE 语句来实现的。
当你指定了父表中的行时,你可以利用这种关联。你可以通过GetChildRows方法得到所有子表中所有相关的行,当然这里的关联关系是由你来决定的。DataRelations 使得创建一个master-detail程序变的非常简单。上面的代码的显示结果见表6。
下面我们来了解一下ADODataSetCommand对象以及与它功能相似的SQLDataSetCommand对象。我们已经了解了它们三个主要功能中的一个,就是通过使用命令字符串和一个连接号向DataSet 对象中加入数据。下面对另外两个主要功能进行讨论,首先是更新(updating)。
在传统的ADO中,一个客户端的 Recordset 对象通过SQL 语句来进行更新。在这里SQL 模拟开放式锁定,因此更新得以被返回到数据库。这是一个灵活的机制,但有两个缺点:一、自动生成的SQL语句不易更改,因此假如你写一些高效率的存储过程,将会比直接使用SQL 语句迅速的多。二、这是第一个问题的延续。当需要更新的数据源无法理解ANSI-SQL的时候,你就无法使用客户端的Recordset了。象Active Directory, Exchange 2000以及 Indexing Services这些兼容ADO 的数据源,它们不兼容ANSI 的标准。因此你如果想通过ADO对它们进行更新,你就只能使用服务器端的光标了。
在ADO.NET 中这些问题被解决了。第一种方法,DataSet 与数据源完全断开,ADODataSetCommand作为一个独立的实体与数据源进行交互。更新完全由ADODataSetCommand进行,而DataSet则被完全隔离。
第二中方法,ADODataSetCommand将更新的SQL语句作为一种公开的属性,这样你可以轻松的替换SQL 语句,或者是存储过程。更为出色的是,如果你想使用存储过程,Visual Studio.NET 将为你生成,在下一部分我们可以看到具体的应用。
最后是关于数据表映射功能。数据的使用者不需要得到这样一个数据表:列以'au_fname' 和 'au_lname'命名。不仅是不美观的问题,更重要的是这样会把数据库的结构暴露出来了,数据映射允许你在DataSet 中替换列的名字,如果需要,可以为不同的用户提供不同的数据表映射,下面我将介绍如何利用Visual Studio.NET ,在图形化的界面下创建数据更新的代码。
简单的可视化设计
Visual Studio.NET 为 Windows Forms, Web Forms, Web Services, Components and XML Schemas的设计提供了图形化的设计工具。设计者只需要从工具条上的控件拖动到工作区域 就可以了。在这里,工作区域将与最终用户看到的界面有很大区别。
当你将一个非可视的对象如ADODataSetCommand拖动进来时,它将被显示在设计视图中,但用户将无法看到这个对象。其他的数据控件也是这样。
表7显示了一个VB.NET的项目,这个项目有一个窗体,上面有一个DataGrid控件、一个CommandButton以及ADODataSetCommand控件,在这里你可以象在Visual Studio 6中一样来处理ADODataSetCommand:在可视化的界面中你可以利用向导来建立ADODataSetCommand的连接字符串,命令字符串;在与数据库的接口上,你可以选择自动生成SQL语句,选择已有的存储过程,或是创建一个新的存储过程。
表8显示了向导的最终结果,你可以给你创建的存储过程命名,或者只是预览一下,然后将之存为文件以便以后修改。
如果你不想利用存储过程,你也可以直接使用SQL语句,你还可以在属性面板上修改这些语句。你还可以做的工作包括给对列进行简单化的命名,以便你今后能够方便的使用。具体的操作可以参看表9的对话框。
在完成了以上的工作后,应用程序的编写变的相对很轻松了,下面是DATAGRID的绑定的代码:
Me.ADODataSetCommand1.FillDataSet(dsAuthors)
Me.DataGrid1.DataSource = dsauthors
ADODataSetCommand1成为窗体Me的一个属性,它将dsAuthors表装入一个DataSet 对象,接着设置DataGrid的DataSource属性为dsAuthors。最后是编写CommandButton的CLICK 事件:
Me.ADODataSetCommand1.Update(dsAuthors)
UPDATE 事件将根据对dsAuthors的修改对数据源进行更新(具体的UPDATE 方法在存储过程中已经被设定)。这与ADO 中断开连接的RecordsetS对象的批量更新很相似,但效率更高。可以在表10中看到最终完成的应用程序,它的列名已经被替换为新的列名。
表10: 运行结果
类型化的DataSet
对于许多开发者来说,他们已经习惯了使用ADO 的 Recordset 对象的,象使用字段(fields)而不是使用属性(properties),这样做有它的优势,但也有很多缺陷。首先,与属性不同,字段并不是强类型的,它不为IntelliSense技术(自动提醒语法、参数和对象属性)所支持。另外,因为不是强类型,所以你无法为它添加自定义的方法或是属性,这意味着当你需要把一个Recordset 对象的功能完全封装起来的时候,你会遇到许多限制。
与之相对,因为.NET 平台支持继承,所以你可以创建DataSet 对象的子类,并向其中添加新的功能。这就是具有类型的 DataSet,它基本上没有什么使用范围的限制,所有DataSet 在.NET 中内建的特性都将被支持,包括绑定,和XML DOM 的内部操作。为了创建这样一个具有类型的 DataSet 对象,你所需要的只是一个XSD 格式的 XML SCHEMA。
当你基于一个DataSetCommand 上创建具有类型的 DataSet 的时候,你甚至可以让Visual Studio.NET 来创建这样一个 XML SCHEMA,所有的工作只是鼠标在'Generate DataSet'菜单上的轻轻一点(见表7)。在这里使用的XML SCHEMA与表2中的是一样的(除非你对数据表的结构做了改动)
在后面的例子中我将使用我创建的具有类型的AuthorsDataSet 对象来代替DataSet 对象。AuthorsDataSet中的所有表和列都将是它的属性,因此所写出的代码将更易于查看,而强类型将更不易出错,同时还可以利用IntelliSense技术。表11显示了类型化的DataSet 的编程界面,注意IntelliSense菜单也被显示出来了。
表11中我们可以看到在AuthorsDataSet被创建之后,同时还创建了authorsSelectTable(继承于DataTable),authorsSelectRow(继承于DataRow),以及所有的列的类(继承于DataColumn),由此,我们可以看到继承对.NET 的重要意义。
FindByAuthor_ID方法自动被添加到authorsSelectTable类中,列属性被自动添加到authorsSelectRow类中,所有的类的代码都是非隐藏的而且易于扩展。如果你已经习惯了使用断开连接的(disconnected)或是虚拟的(fabricated)Recordset 对象,那么转向ADO.NET 使用具有类型的 DataSet 对象是一个很好的选择。
ADO 到 ADO.NET 是一个革命性的发展,在所有的.NET Framework领域中,许多基本的组件可以被重写,因为不用受到二进制兼容性的强制性约束所以可以对所有类型的组件的接口进行重写和改善,而ADO.NET 只是其中一例。
在经过了几个星期的使用后,我认为 ADO.NET 与ADO 相比是一个更完善的模型,我非常满意它对ADO所做的改进。虽然某种程度上讲,对于开发者来说,需要学习一种新的对象模型,但我仍建议开发者向.NET转移。ADO.NET 继承了ADO的优良特性,并且更易于使用。