ADO.NET:通向未来之桥
在Web跨入编程时代之前,对于大多数IT管理者和顾问来说,数据访问只是一个相对而言的问题;所有要用到的数据都必须自己准备好。人们主要关心的问题是选择性能/价格比最好的数据库服务器,系统涉及的所有模块必须和服务器兼容。客户机/服务器应用是这种两层模型最典型的范例。
随着Web交互性的日益提高和应用的日益广泛,对于第三层——中间层的需求也越来越突出。中间层是一个逻辑层,数据访问组件通常就在这一层上。数据访问组件是唯一有必要了解数据库细节的代码,同时,准备更换或者升级数据库服务器时,数据访问组件也是第一个需要修改的地方。
接下来,三层系统模型发展成了Windows DNA——现在称为Microsoft .NET服务器系统。Microsoft利用一个统一的模型进行数据访问。这个模型注重的是内容,而不是数据格式和存储媒介。它的具体表达方式建立在UDA(Universal Data Access,通用数据访问)的基础之上,而UDA正是激励OLE DB体系发展的深层理念。为了提供一种通过VB和ASP COM组件方便地、无缝地访问OLE DB功能的途径,Microsoft设计了ADO。ADO 2.0是用来支持OLE DB的第一个版本。在几年之内,ADO发展到了2.6版本,增加和扩展了XML支持,使得经过扩展的ADO对象模型能够匹配所有OLE DB改进功能(例如,对于OLE DB新增的Row和Stream对象,ADO 2.5提供了类似的ADO配套功能)。
ADO 2.0的核心功能超越了OLE DB。在多层系统中,随着中间层组件的出现,如何为表现层提供最新数据这一问题也随之出现。表现层怎样访问数据?连接怎样打开?或者,我们是否应该维护一份脱机记录(即,一些断开连接之后仍旧能够在表现层使用的数据库记录)?ADO 2.0以及它的更高版本同时提供了对服务器端游标和脱机记录集的支持(脱机记录集是一种COM对象,它可以跨越网络串行化,客户可以下载它然后脱机使用)。
服务器端游标代表着一个紧密结合的、保持连接的环境,在这个环境中我们总是维持着各个层之间的有效通道,只有结束时才拆除连接。与此相反,脱机记录集是一个有状态的对象,我们可以把它作为一个整体处理,且不必维持连接。脱机记录集使用客户端的静态游标,能够提供一个数据源的快照。对于那些只有读取要求、且需要在各个系统层之间移动数据的应用程序来说,脱机记录集是很理想的。今天,大多数Web应用程序要求在各个层之间传输数据。为了获取这些数据,我们经常使用快速的“只能向前”游标,用XML编码数据,然后通过网络发送数据,避免了在Web服务器上维持会话状态信息。在分布式环境中,数据库连接是一种关键性的资源,以脱机方式工作保证了高可伸缩性。
然而,Web是一把双刃剑。Web让我们连接到任何类型的联机服务,而且能够与它们进行交互。但是,Web也要求一定程度的互操作性,因为操作所涉及的各个服务可能运行在不同的软件和硬件平台上。我们可以通过利用开放的标准,以及那些不注重私有权的技术——甚至是象COM+这类广泛应用的私有技术,跨越不同的平台。
今天,基于Windows的Web数据访问应用程序利用了ADO丰富的、方便的编程接口。然而,ADO对象天生地定位在Windows平台上。ADO基于COM的本性使得记录集很难在一个分布式、异种平台构成的环境中使用。另外,即使目标平台可能允许我们使用ADO记录集,它也不具备最有效的机制。ADO.NET的DataSet和DataReader更有效;而且,如果没有ADO.NET,有些时候我们还可以借助XML或纯文本获得高效率。
为了在Web环境下传输数据,Microsoft对ADO记录集进行了优化。但COM类型转换仍旧是一个必不可少的步骤,因为COM的数据类型不可能总是匹配ADO记录集的数据类型(例如,String类型必须转换成BSTR类型)。由此,许多人把XML当成了粘合各个层的“万能胶水”——不管涉及到了哪些平台。通常的做法是:先提取一个记录集,把它保存为XML格式,然后传输结果数据流,让接收者从这个XML数据流重新构造出记录集供以后使用。随着对协同工作能力和可伸缩性要求的提高,ADO不再是最理想的答案,因为它不是建立在XML的基础上——但ADO.NET是。
二、ADO在Web环境中的不足
.NET框架创立了一种取代COM和COM+的新组件模型。.NET的提出是Microsoft成熟的组件技术的新战略。虽然几个关键性的COM特色不再在.NET中出现,但在某些方面,.NET与COM编程仍旧很相似。因此,COM程序员将能够方便地转向.NET开发。ADO.NET是Microsoft特别为.NET框架设计的数据访问层,它在很大程度上利用了.NET的优势。
为什么.NET必须用一个新的数据访问层来替代现有的、广泛应用的数据访问接口,比如ADO?现代Web应用系统必须具备客户机/服务器应用和桌面应用的交互能力,Microsoft设计.NET的目标正是为了迎接设计现代Web应用系统的挑战。同时,.NET也利用了各种Web协议广泛的、强大的连接能力和协同操作能力。
在非Windows平台上,ADO记录集不能直接使用,从而使得协同操作能力受到了限制。为了突破这个限制,我们要把记录集转换成XML格式,然后传输转换得到的XML记录集。在ADO.NET中,把数据转换成XML以及通过网络传输的操作得到了简化和优化。另外,ADO对象模型中的每一个地方都体现了以数据库为中心的思想。ADO把数据看成是一组来自数据源的记录,而不是把数据看成一些独立的信息。在ADO中,如果脱离了数据提供者用来保存和描述数据的结构,数据将不能独立存在。
三、ADO.NET数据集和ADO记录集
ADO.NET是从Web的角度对ADO进行检讨和改进。Microsoft对ADO.NET的设计严格地体现了其名字的含义:ADO,再加上.NET。ADO.NET自动连接网络,致力于让Web数据访问变得更加简单和高效。两个功能使得这方面的增强成为可能:脱机记录集,以及与生俱来的对XML的支持。由于采用了脱机记录集方案,ADO.NET自然也就不再支持服务器端游标。ADO.NET天生就把记录数据保存为XML文档,把模式(Schema)和数据视为分离的、可替换的元素。
如果你认为ADO早就提供了这些功能,它们并没有什么创新意义,那么,ADO.NET还提供了其他许多新的功能。ADO.NET能够使用连接的或者非连接的(脱机的)记录集,具体由用户选择的游标类型和游标位置决定。ADO记录集的本地存储格式是ADTG文件格式(Advanced Data TableGram,高级数据表图)。ADTG是一种Microsoft私有的二进制存储模式,代表着记录集在内存中的映像。XML是可替换使用的、确定的、详细输出格式。在ADO.NET中,我们可以断开一个记录集集合的连接,通过一个默认(但允许更改)的XML模式再现记录集集合。
在ADO.NET对象模型中,DataSet(数据集)是最重要的对象。一般地,一个DataSet对象就是一个记录集的集合。ADO.NET框架提供了记录集的所有数据库功能:排序,分页,过滤视图,关系,索引,和主键。
DataSet对象代表了一个在内存中的、有着丰富功能的数据缓冲区。DataSet对象也通过表组织数据,这些表与原始的数据源之间不存在连接。我们可以添加表,表可以通过读取本地或远程XML文件获得,或者也可以从任何可访问的系统资源(包括内存和其他附属设备在内)读取。我们可以排序、索引、过滤数据表,象处理ADO的Recordset一样导航数据表。
我们可以通过命令用数据集合填充DataSet对象。如果用.NET集合的形式为DataSet对象提供数据表(具有集合功能的.NET数据类型是ICollection),同一个DataSet对象能够服务来自多个连接的多个请求。ADO.NET的DataSet对象比ADO的Recordset更一般化;与ADO的Recordset不同,它是对数据源的一种抽象。然而,DataSet对象保留了一个在内存中工作的数据存储器;它没有完全淘汰记录集功能。如果我们只需要一次性地滚动记录集,然后生成某种输出,那么,我们应该使用DataReader对象。.NET的DataReader对象类似于“只能向前、只读”的记录集,但它是一个高度专用化的对象,所以无论在体积和开销上它都要比记录集小。事实上,记录集能够执行许多不同的任务,是一个相当臃肿的对象。与ADO的Recordset相比,DataReader不包含进行“家务管理”的代码,除了实现功能所必需的代码之外,它不包含任何其他代码。
把多个表作为一个整体管理以及允许建立这些表之间的关系,这是ADO.NET的新功能。我们可以用XML形式持久化或传输任何DataSet对象,而且无需付出任何额外的代价,因为DataSet对象本身就是按照XML格式构造。因此,除非要修改底层模式,否则,我们无需为了获得一个XML流而去转换DataSet对象的任意一个部分。
四、ADO.NET对象详解
ADO和ADO.NET有着两个截然不同的对象模型:ADO定位在基于Windows 2000和NT的服务器平台;ADO.NET定位在支持.NET的平台。Microsoft预期在2001年末推出第一个.NET OS——Windows XP(原来的代码名字为Whistler)。不过,它的后继产品(代码名字为Blackcomb)更有可能提供一个全功能的.NET OS。
如果要迁移代码,我们可以把现有的ADO代码导入到.NET应用之中,从而节省在编写代码方面的投入。然而,如果不做重大的设计调整,同样的代码几乎不可能移植到ADO.NET。ADO和ADO.NET的对象模型不一样,两者在不同的设计指导思想下完成。
ADO.NET只用来构造基于.NET服务器的Web应用。ADO.NET是.NET应用程序的数据访问API。因此,只有把服务器升级到.NET之后,你才可以考虑ADO.NET。在同一个应用程序中,让ADO和ADO.NET协同运作是没有什么意义的。虽然你可以同时使用这两者(至少从设计的角度来看),但这并不是一种好的选择。
ADO.NET的对象主要包括:DataSet,DataTable,DataColumn,DataRow,和DataRelation。这些对象的主要特点说明如下。
▲ DataSet:这个对象是一个集合对象,它可以包含任意数量的数据表,以及所有表的约束、索引和关系。所有这些信息都以XML的形式存在,我们可以处理、遍历、搜索任意或者全部的数据。图1显示了一个典型的DataSet对象的模式。在这个例子中,DataSet对象包含两个表,其中一个表来自SQL Server,另一个表来自Oracle。两个表通过一个关系连接到一起,关系把源表的一组记录和目的表的一组记录关联到一起(例如,主-细关系)。另外,一个XML表通过一对一(1:1)的关系关联到了Oracle表。
▲ DataTable:这个对象代表着可以在DataSet对象内找到的所有表,如图2所示。我们通过Tables属性访问DataTable的集合。类似地,通过DataSet的Relations属性可以访问到所有已经建立的数据集关系。Xml属性包含了对象原始的XML描述,.NET应用程序可以从这个XML字符串重新构造出数据集。
在ADO.NET对象模型的层次结构中,DataTable对象与ADO的Recordset对象最接近。根据具体目的的不同,我们可以在数据集之内或之外创建和使用表。另外,我们可以手工执行命令——但首先必须定义表的模式——或者,依靠受管理数据提供者创建和填充表。(受管理提供者即Managed Provider,它是一种新的数据提供者类型,是ADO.NET中唯一直接访问数据源的途径。这种提供者封装了一个数据源,通过Microsoft .NET类返回数据。受管理提供者共用OLE DB的观念,但它是经过专用化的、优化的,而且很容易使用。)
▲ DataColumn:表包含与列有关的信息,包括列的名称、类型和属性。我们可以按照下面的方式创建DataColumn对象,指定数据类型,然后把列加入到表:
Dim dc As DataColumn
dc = New DataColumn()
dc.DataType = System.Type.GetType("System.String")
dc.ColumnName = "NameOfTheColumn"
任何时候,列的清单都可以通过DataTable的Columns集合得到。
▲ DataRow:要填充一个表,我们可以使用命令的自动数据绑定功能,或者也可以手工添加行,方法是:创建DataRow对象,把DataRow对象插入到表,然后为该记录的各个字段填写数据。我们可以通过Rows集合导航DataTable的元素,利用Rows集合实现一个顺序导航器,或者,通过搜索或直接定位跳转到特定的记录。
▲ DataRelation:这个对象代表着两个表之间的父-子关系。关系建立在具有同样数据类型的列上,但列不必有相同的精确度。关系可以是1:1、一对多(1:M)或多-对-多(M:N)关系。关系可以方便地把对父记录的改动传播到子记录,但这不是默认行为。
要启用DataRelation对象,我们必须把一个ForeignKeyConstraint加入到待修改数据表的ConstraintsCollection成员。DataTable对象的ConstraintsCollection成员决定了当一个父表的值被删除或改动时,要执行一些什么动作。
设置了关系之后,ADO.NET将拒绝所有破坏该关系的数据集改动操作,并产生一个运行时异常。在遍历表的记录时,调用GetChildRows方法可以从已连接的表访问所有相关的行。GetChildRows方法返回一个DataRow对象的数组,这是一种新的分层式记录导航方式,而传统的记录导航方式属于顺序模式或随机模式。
关系是不可传递的。假设表A关联到表B,表B又关联到表C的一些行。另外,假设在遍历表A记录的时候,对于每一个表A的记录,我们要访问现有关系中的子记录。如果我们想要访问对应表B中特定记录的表C记录,那么,我们不能使用由A-B的关系得到的表B的DataRow对象;相反,我们必须从表B打开一个新的表视图,找到我们想要的特定记录,然后通过表B对表C的关系调用GetChildRows方法。
【Listing 1:创建和填充一个DataSet对象】
' 执行命令
Dim strConn, strCmd As String
strConn = "DATABASE=Northwind;SERVER=localhost;UID=sa;PWD=;"
strCmd = "SELECT * FROM Employees"
Dim oCMD As New SQLDataSetCommand(strCmd, strConn)
' 向数据集加入一个命名的表,得到一个静态快照
Dim oDS As New DataSet
oCMD.FillDataSet(oDS, "EmployeesList")
' 遍历各个记录
Dim oRow As DataRow
For Each oRow In oDS.Tables(0).Rows
Response.Write(oRow(0).ToString() + " ")
Response.Write(oRow(1).ToString() + "<BR>")
Next
Listing 1的代码显示了如何访问SQL Server标准的Northwind数据库,创建并填充一个DataSet对象。关于DataSet及其下属对象的层次结构简图,请参考图2。
创建DataSet对象之后,我们打开了一个访问指定数据库的连接,并执行一个返回记录的命令。这个命令可以是一个SQL命令,或者也可以是一个存储过程。下面的代码用到了FillDataSet方法,FillDataSet方法允许我们把命令返回的表关联到指定的数据集。
Dim oDS As New DataSet
oCMD.FillDataSet(oDS, "EmployeesList")
数据集中的每一个表都有一个名字,我们通过这个名字来提取内容。ADO.NET真正的进步源自数据集这一概念的提出,在ADO中不存在与ADO.NET DataSet类似的对象。虽然我们无法从这个例子看出DataSet对象的重要性,但从体系结构上说,DataSet对象处于一个不同的层次。它允许我们在同一个逻辑对象的组织之下管理多个表,这个功能为我们实现主-细关系模式和数据绑定机制提供了前所未有的强大工具。另外,我们还可以通过索引引用一个表。
五、修改数据
以脱机方式工作时,我们对数据的添加、修改、删除等更新操作在内存缓冲中进行。因此,如果你利用NewRow添加了新的记录,或者利用某个简单的工具编辑现有的行,这些更新只在内存中进行,不影响底层的数据源。更新操作在我们从Command对象调用Update方法提交更改的时候发生。Update方法为数据集中每个被插入、修改、删除的行分别调用Insert、Update和Delete命令。
我们可以利用ADO.NET命令的InsertCommand、UpdateCommand和DeleteCommand属性,指定面向特定数据源的命令,插入、修改或删除记录。这些属性的内容和我们所要操作的数据提供者有关,但是,如果数据提供者是一个数据库管理系统(DBMS),属性的内容很可能就是SQL命令字符串。当Update方法执行时,如果这些属性还没有设置好,但DataSet对象包含了主键信息,那么,ADO.NET将自动生成命令文本。
我建议,只有当应用程序要求读取一定数量的记录并进行修改,而且修改的次序和数量不可预测时,才使用这种插入、更改、删除记录的方法。如果应用程序的用户界面只是要求填写几个字段,然后插入或更新记录,那么,执行SQL语句或存储过程是更理想的方法。
六、其他需要说明的问题
虽然ADO.NET不是一个特别庞大的主题,但本文仍旧只是粗略地勾勒了它的表面。如果不理解隐藏在ADO.NET背后的深层原理,我们不能有效地利用ADO.NET。ADO.NET不仅仅是一种新的数据访问API,更重要的是,当我们利用.NET的强大优势时,ADO.NET使得我们能够跨越Web访问数据。如果你选择.NET作为服务器平台,你就应该用ADO.NET访问数据。
在.NET平台的设计过程中,Microsoft必须对如何进行数据访问做出决策。它可以直接移植ADO,但ADO建立在COM的基础上,而且大多数时候它被用作一种OLE DB上的脚本接口,一个紧密结合的、有连接的环境。Microsoft修改了ADO,淡化ADO以数据库为中心的色彩,使得它更适合在一个宽松结合的环境中运行。在ADO.NET中,Microsoft实现了ADO与.NET平台的一致性。不论我们编写的是Web表单、Windows窗体还是Web服务,如果系统中存在一个数据源,ADO.NET总是提供一组相同的类。ADO.NET重新审视了Recordset对象,把它的功能分割成了两个对象:DataSet和DataReader。如果你坚持使用ADO,那么,你仍旧可以利用.NET导入ADO类,然而,ADO不包含任何ADO.NET不能提供的功能。
最后一个需要认识的重要问题涉及到Microsoft设计ADO.NET的方法。无论是在ASP.NET中,还是在ADO.NET中,大多数改进体现了最优秀的开发实践经验。Microsoft简单地把各种改动加入到一个一致的对象模型和一个“完全包容”的框架,单独的编程技巧在这里变成了整体功能的有机组成部分。ADO.NET是数据访问的通用编程技巧和用户共同需求的结晶,如果你要把Web系统升级到.NET,ADO.NET绝对是你必不可少的工具。