我将讨论几种数据访问方案,演示如何利用 ADO 来实现这些方案,同时演示如何用C#在 ASP.NET 中借助 ADO.NET 来解决同样的问题。我 将从二者与数据源连接的相似之处开始,继而我将细致地探讨 ADO 的 Recordset 对象如何演变发展为 ADO.NET 中许多与众不同的面向对象和方法 的特性。最后,我将揭示 firehose 游标,如何从行集中返回一个单值,以及如何处理 XML 等。
![](http://www.vckbase.com/document/image/paragraph.gif)
有些传统的 ADO 特性,如建立与数据源的连接在两个 ADO 版本之间改变很小。其它一些功能则变化很大,譬如表示一个非连接的行集(rowset),将行集保存为XML,将行集转化为一个层次行集(hierarchical rowset)。促成这些重大改变的一个原因是在 ADO 后期引入 XML 和数据整形(data-shaping)特性,而在 ADO.NET 里,这些特性在当初设计时就将这些特性内建在其中了。
与更早的数据访问工具如 DAO 和 RDO 相比,传统的 ADO 是很轻量级的,而使 ADO 能在n层应用程序开发中和 Visual Basic 6.0 一起得以流行的原因之一 是其简单性、易于导航的对象模式。ADO 的 Connection、Command 对象以相对比较直白的方式转变为 ADO.NET 的 Connection 、Command 对象,但是 ADO 的 Recordset 对象的特性转换到 ADO.NET 中后变成了几个不同的对象和方法。
ADO 之所以成为一个强大而通用的数据访问工具,部分原因是其对 XML 的支持以及管理非连接行集的能力。ADO 的 Recordset 可以 是与其数据源断开连接的,只要将其 CursorLocation 属性设置为 adUseClient,将 CursorType 属性设置为 adOpenStatic, 将 LockType 属性设置为 adLockBatchOptimistic 即可。一旦记录集(recordset)被打开和加载后,通过将其 ActiveConnection 属性 设置为 Nothing,使记录集保持为非连接状态。
''''---Disconnecting an ADO Recordset oRs.CursorLocation = adUseClient oRs.CursorType = adOpenStatic oRs.LockType = adLockBatchOptimistic ''''— Or use adLockReadOnly oRS.OpenSet oRS.ActiveConnection = Nothing起初 XML 功能并未集成在 ADO 中,随着 XML 的流行,在 ADO 后期版本中增加了对 XML 的支持。ADO Recordset 对象中的 Save 方法可以将记录集的行和列保存为一个预定义的 XML 大纲(schema),并将其保存为一个文件或流。使用 XML 大纲并不灵活,但是这是试图将一个 ADO 行集(rowset)保存为 XML 的第一次尝试,并且让开发者对未来的开发方向有个较清晰的认识。Recordset 也可以从一个 XML 文件中加载,前提是其使用同样的XML大纲。下列代码演示 ADO 如何将一个记录集保存为一个 XML 文件:
.Save "c:/MyRowSet.xml", adPersistXML类似于 ASP 页面上的 Response 对象输出,行集可以保存为一个流。例如, 某些 ASP 代码可以接受一个请求,检索一个行集,将行集以流的形式发送给 Response 对象,后者将结果返给客户端。下列代码演示如何将一个记录集的内容保存为一个 Stream 对象。也可以将 Save 方法的第一个参数置为 Response 对象,这样就可以将 XML 行集流输出给浏览器:
Dim oStm As ADODB.StreamSet oStm = New ADODB.Stream oRs.Save oStm, adPersistXML
传统的 ADO 和 ASP 均支持流处理。当然,在.NET中,Web services 同样支持流,并且 Web Services 提供了比传统的 ADO 和 ASP 多得多的功能。实际上,Web Services 是一项处理数据请求并将数据以 XML 形式通过 HTTP 传输的技术。ADO 可以以流形式保存 XML,不过这是 ADO 后来的完善措施(afterthought)。而 ADO.NET 的 DataSet 对象通过 WriteXml 和 WriteXmlSchema 方法将其内容输出为一个 XML 文件或流。因此,ADO 的 Recordset 的 Save 方法是经过改进以适于将其内容输出为一个 XML,而 ADO.NET 的 DataSet 一开始就具有此特性。
ADO.NET 中不仅可以将 DataSet 保存为 XMl,并从 XML 加载 DataSet,并且它使用其类 XML 结构到别的用途中去。譬如,因为 DataSet 可以表示为 XML 形式,其可以很方便地在物理层和逻辑层间传递。这意味着,XML 可以在安全的网络上通过 HTTP 传送,同样 还可以以基于文本的 XML 形式传送。
由于构建在 XML 上,ADO.NET 可以工作在非连接状态。传统的 ADO Recordset 既可以工作在连接状态也工作在非连接状态, 取决于以下属性(譬如:CursorType = adOpenStatic 和CursorLocation = adUseClient),而在 ADO.NET 中,RowSet 对象分为连接态的(DataReader)和非连接态(DataSet)。 连接
创建连接的过程在 ADO 和 ADO.NET 中非常相似。首先,声明你的连接对象,初始化,设置其连接字符串,打开它。如 Figure 1 所示。第一个例子演示如何使用 ASP 和 ADO 来打开一个连接,第二个例子则在 ASP.NET 和 ADO.NET 做同样的工作。
ADO 和 ADO.NET 建立连接的主要区别在于 ADO 使用一个 Connection 对象来完成所有的与各种数据源的连接,而 ADO.NET 用不同的连接对象来表示与不同数据源的连接。例如,ADO.NET 含有一个System.Data.SqlClient 命名空间,其中包含所有 SQL Server 专用的 ADO.NET 对象(包括SqlConnection 对象)。SqlConnection 对象专门用于与 SQL Server 数据库进行通讯,因而是与 SQL Server 交互最快,功能最丰富的对象。还有一个更通用的命名空间 System.Data.OleDb,它可以与所有兼容 OLE DB 数据源通讯。因此,在 ADO.NET 中,可以创建多个数据提供程序命名空间(data provider namespaces)以连接特定的数据源,使数据访问速度更快,效率更高,允许每个命名空间充分利用目标数据提供程序 的功能。如果某个程序在运行时必须改变数据提供程序的类型,或者依赖一个数据提供程序而该数据提供程序不含有特定的 ADO.NET 连接对象时,该程序最好使用 OleDbConnection。
![](http://www.vckbase.com/document/image/paragraph.gif)
ADO 中的 Recordset,其行为(功能)依赖其属性设置的不同而不同,而在 ADO.NET 中 Recordset 被分解成许多不同的对象和方法。 从而避免了 Recordset 大而全的弊端,着眼于使各个对象各司其职,因此 ADO.NET 中的这种分解可以显著提高效率。ADO 中的 Recordset 可以是个连接的行集也可以是个非连接的行集。它可以充当一个前 向(forward-only)只读的游标,或者允许行集中位置的前移、后移和向中间移动。ADO 的 Recordset 允许对数据修改直接在数据库中完成,允许保存数据修改并以批处理形式发送给数据库。问题在于 ADO 的 Recordset 承担了太多的功能。ADO.NET 中将这些功能分解为多个对象去 分别完成特定的任务,因而 ADO Recordset 的功能被分为 DataSet 对象、DataReader 对象、DataAdapter 和 Command 对象。
前向、只读游标要求与数据源一直处于连接状态。ADO.NET 使用 DataReader 对象确保当其打开时,就一直处于连接状态。DataReader 专为 某一数据提供程序如 SQL Server、Oracle 或更通用的 OLE DB 数据提供程序而编写。因此,SqlDataReader 对象可以连接到 SQL Server 数据库,并且充当一个 firehose 游标的角色,在大量的记录间循环。SqlDataReader 也具有对查询结果的快速前向(fast-forward-only)访问功能。它从数据库的查询结果中检索到一条记录, 并保持连接为打开状态,以便成功检索下一条记录。ADO.NET 的 DataReader 由于不支持 ADO Recordset 的所有功能,因而效率 极其高。Figure 2 中的例子演示了如何在传统的 ADO 和 ADO.NET 中实现一个只前向移动的游标的例子。值得注意的是在 ADO.NET 中,DataReader 对象的 Read 方法自动移动位置到下一条记录。这就避免了开发者在使用传统的 ADO 时,由于疏忽了调用ADO Recordset 的 MoveNext 方法而导致死循环的情况的出现。
ADO 和 ADO.NET 的另外一个区别是填充前向游标的方式。ADO 中所有的行集,无论前向的还是其它的,均包含在 Recordset 对象中。而 Recordset 对象通过 Recordset.Open 方法或者 Connection 或 Command 对象的 Execute 方法而打开。ADO.NET 中, 有一个专门的方法将获取前向数据提供给 DataReader:即 Command 对象的 ExecuteReader 方法, 该方法告诉命令对象为该数据提供程序明确提出 DataReader 对象以便以优化的前向方式处理结果。如前面例子所示。该命令对象还有一个方法叫 ExecuteXmlReader, 其作用是告诉命令对象生成查询结果供 XmlReader 对象处理。XmlReader 对象可被用于转换和处理 XML 查询结果,见下面的 ASP.NET 例子:
oCn.Open(); SqlCommand oCmd = new SqlCommand("SELECT * FROM Orders FOR XML AUTO",oCn); oCmd.CommandType = CommandType.Text; XmlReader oXR = oCmd.ExecuteXmlReader(); while(oXR.Read()) { Response.Write(oXR.ReadOuterXml()); }该例子演示了 XML 如何被发送到调用浏览器。不过,XML也可以整个被聚集和流化到 Response 对象。
![](http://www.vckbase.com/document/image/paragraph.gif)
有时,大多数应用都需要从数据库查询中获取单个值的信息。在传统的 ADO 中标准的做法是创建一个 SQL 语句,并打开包含着查询结果的记录集。因为结果中只有一行一列,看起来似乎不必要。下列代码从查询中获取 单值:该例子获取 Orders 表中的行的数目:
''''-- ASP and ADO Set oRs = Server.CreateObject("ADODB.Recordset") oRs.ActiveConnection = oCn oRs.Open "SELECT COUNT(*) As iRowCount FROM Orders" iCount = oRs.Fields("iRowCount").Value如果你只想取得仅仅一行和一列的信息,ADO.NET 中引入了从查询中获取单个值的新的方法。使用 ADO.NET 命令对象中的 ExecuteScalar 方法, 它可以返回其相关查询的第一行和列的信息。由于不必经过创建行集,查找该值,关闭行集等过程,因而开销非常之小。ExecuteScalar 方法在检索单值时是最佳的方法。下列代码和前例完成同样的任务, 但所使用的是 ASP.NET 和 ADO.NET 中 ExecuteScalar 方法:
string sSql = "SELECT COUNT(*) As iRowCount FROM Orders"; SqlCommand oCmd = new SqlCommand(sSql, oCn); oCmd.CommandType = CommandType.Text; int iCount = (int)oCmd.ExecuteScalar();另外一种获取单值的方法是使用存储过程的输出参数。该技术也需要检索大量的值。在 ADO 和 ADO.NET 中均可采用,不同的是 ADO.NET 扩充了输出参数的功能。为了在 ADO.NET 中从一个命令对象中得到输出参数的值,使用 ExecuteNonQuery 方法执行查询语句。该方法告诉 ADO.NET,该查询不返回行集,因而可以避免产生 DataSet 或 DataReader 的 开销:
oCmd.ExecuteNonQuery(); oCmd.UpdatedRowSource = UpdateRowSource.OutputParameters; int iOrderID = (int)oCmd.Parameters["@OrderID"].Value;
上述代码设置 UpdatedRowSource 属性以指向输出参数,前提是输出参数已被设置。然后就可以检索到输出的值。在传统的 ADO 中 ,Connection 对象的 Execute 方法使用一个模糊的参数来完成以上任务,而 ADO.NET 是以一种直白的方式来实现的。当然,在 ADO.NET 中还有一种最佳的方法来返回一个标准的行集——即命令对象的 Execute 方法。
传统的 ADO 的 Recordset 对象能运用 UPDATE、INSERT、DELETE 等语句,这些语句对于用 Recordset 变化后的值修改底层数据库来说是必须的。尽管该功能很方便,但也还是带来许多开销,因为它必须返回到数据库去了解如何进行该操作。
ADO.NET 中通过 CommandBuilder 对象做到这一点;虽然它同样带来系统开销。在大多数开发情况下,具体的 SELECT、INSERT、UPDATE、DELETE 操作在设计时间就可以确定下来。在传统的 ADO 中,没有 一种方便的方法将查询行为与 Recordset 关联起来以便 Recordset 可以利用这些它们的优势。而在 ADO.NET 中,一个 DataAdapter 有四个不同的命令对象与之相关联,分别代表每个查询 动作和 SELECT 语句。这使得 DataAdapter 可以使用一个查询结果去填充一个 DataSet,并告诉DataAdapter 事先在数据库上采取哪些 查询动作。当然在设计阶段这需要更多的代码,不过这些额外的代码对于性能的提高是值得的,更不用说代码的易维护性了,因为这些代码是自解释的。 在我五月份的“Data Point”专栏里详细讨论了 DataAdapter 的使用方法。
本文中我讨论了从 ADO 到 ADO.NET 一些关键特性的演变,包括 firehost 游标和持续化 XML。在后续专栏里,我将探讨更多有关游标的演变并示范如何处理并发问题以及在传统 ADO 和 ADO.NET 中如何进行批量更新。
发送问题和建议给 John 的邮件地址:mmdata@microsoft.com