你我并不相识。不过,面对一个实实在在的问题“软件到底是什么?”,我却没有合适的答案。
设想一下这样一个场景:在一个旅游纪念品商店,你正专注于购买一些没用的东西(主要是纪念品),以便让到机场接你的朋友和亲戚感到开心。这时,往往会有人问你:“第一次来吗?出差还是度假?”
所以,如果你是在做和软件有关的事,而不是在度假,那你就不得不面对这个现实的问题。
那么,软件到底是什么?
回答这种关于存在的问题是很困难的,尤其是如果此时你正在闲逛,背着一背包明信片、考拉图片和袋鼠玩具,包上还印着防鳄鱼的黄色警告标志。
我努力使自己的思维自由而又尽量简单。首先,软件是跟计算机相关的。软件也和演变有关。当然,软件还与数据(特别是数据存储和操作)有关。
回到旅馆后,我仍在思考下面的问题——关于数据的存储和使用,我近年来观察到了怎样的演变?于是,我开始思考 OLE DB 及其在 .NET 方面的演变。
从历史角度来说,ODBC 进行了第一次严肃尝试:它试图创建一种统一的应用程序访问数据库的途径。像软件中的其他东西一样,ODBC 的设计目的是满足某种特定的需要。在信息技术永无止境的进化进程中,它开创了一个新阶段。
ODBC 必须提供一个公用的(最好是抽象的)API,用来访问数据库,而不用考虑数据库的内部细节、语言和表的组织。但是,随着时间的推移,人们发现,面对新的数据驱动应用程序的设计与构造方法,ODBC 越来越无法成功地满足需要。
软件也有自己的进化论。ODBC 以不同的名称、不同的编程模型和新的功能适应了变化,生存了下来,同时又保持了它的真正使命。ODBC 继续以 OLE DB 的名称和功能提供(或多或少地)开放式数据库连接的功能。
OLE DB 作为一种编程接口,将 Microsoft 通用数据访问 (UDA) 策略的理论概念应用于实践。UDA 能够通过基于 COM 的单一编程接口来访问各种类型的数据,包括关系型、非关系型和层次结构型数据。
OLE DB 是作为一种组件技术而设计的,其特点是采用了多层模型。在 COM 桥的一侧是用于保留数据的服务器组件,另一侧则是了解如何连接和请求数据的客户端组件。前者称作 OLE DB 数据提供者;而后者则称作 OLE DB 使用者。
使用者和提供者都是 COM 对象,并能够通过一套 COM 接口互相通信。这种基于 COM 的通信可被概括为在抽象对象(如 DataSource、Session、Command 和 Rowset)上执行的操作。因此,当使用者连接到 DataSource,打开 Session,发出 Command,并返回数据 Rowset 时,便会出现这种情况。
ODBC 的这一进化使 UDA 和 OLE DB 添加了一种功能,这种功能就像一个简单的关系表一样,将所有的企业数据粘合在一起,不论它们是关系型、非关系型还是层次结构型。
说到数据访问,我们有两种基本选择。一种是像 UDA 允许的那样,采用通用数据访问策略。另一种则倾向于使用通用数据结构。它强行将现有的所有信息从当前的数据存储区移动到一个能包容所有数据类型的数据库服务器。
使用 OLE DB,需要将客户所有的信息粘合在一起。另一种方式是,强行将客户端升级至新的、更强大的、唯一的 DBMS,而这个 DBMS 能够处理任何格式的信息。
与 ODBC 相比,OLE DB 对数据物理结构的依赖更少。此外,它不必严格基于 SQL。OLE DB 命令可以是 SQL 语句,也可以是其他的一些东西。总的说来,可以将它们看作以任何能够为目标提供者理解的语法写成的文本字符串。
像 ODBC 一样, OLE DB 采用 C++ 的概念进行设计,以尽可能提高中间层模块数据访问的性能。基于同样的原因,OLE DB 不能直接在 Visual Basic® 或 ASP 中使用。
而不计其数的分布式系统却是使用 Visual Basic 来生成组件的。这就是 Microsoft 引入 ActiveX® 数据对象 (ADO) 库的主要原因。
ADO 的编程接口比原始的 OLE DB SDK 更加丰富。虽然在 C++ 应用程序中使用 ADO 是完全可行的,但是 OLE DB 调用经过的代码层次较少,与相应的 ADO 代码相比,能更直接地到达数据。
虽然 ADO 很明显是在 OLE DB 上生成的,但是调用原始 OLE DB 接口和通过 ADO 运行时发出的调用具有不同的相对速度。这一事实导致了语言之间的差异。哪一种更好、更值得推荐呢?是 OLE DB 的 C++ 高性能层次还是 Visual Basic 组件中更简单、更友好的 ADO 模型?
除了提供者和使用者,OLE DB 模型还包括第三个元素——OLE DB 服务。服务是一种 COM 组件,用于处理返回给使用者的“行集”。它就像挂钩一样工作,监督使用者和提供者之间的所有通信。ADO 在很大程度上依赖 OLE DB 服务来添加其扩展功能,如数据塑型、持久性和断开的记录集。
因此,自从人们开始重视构建基于 COM 的分布式应用程序以来,就开发了各种针对某些特定领域的最佳实例。为改进 Web 应用程序的可伸缩性,人们转而使用数据访问断开模型。
简单说来,数据使用者和数据提供者并不总是连接的。一旦建立了连接,便可以发出指定的查询,获取记录并将其放至内存中的存储库,然后从数据源断开连接。然后您再在脱机状态下处理这些记录,并在需要时重新连接或提交更改。这一模型不是在所有情况下都可以使用,不过,一旦它发生作用,您就会发现它在可伸缩性和总体性能方面非常有价值。
许多系统已经进行了转换(或再转换),通过客户端游标服务来部署 ADO 记录集,从而启用数据断开。OLE DB 还不是专用于此类交互的模型,所以 ADO 是通过中间 OLE DB 服务进行扩展的。
由于其结构所固有的灵活性,OLE DB 可以成功地应用于断开连接方案,但是,这当然不代表最佳的工作方式。这一实现方案的另一小小的限制是:方案较多地依赖 ADO 记录集进行工作,以至于人们怀疑它不可能总是把每件事都做好。这样的对象如何才能在各种情况下成为最快的工作工具,不论是连接还是断开,有没有 XML,是创建的还是从磁盘加载的?
此外,考虑到 ADO 的功能包与原始 OLE DB SDK 显著不同,使用 OLE DB 将导致明显的不一致现象。
因此,ADO.NET 成为数据访问技术进化中的下一步骤。不过,从名称上看来,ADO.NET 似乎只是 ADO 的继承者。.NET 中的 OLE DB 又是怎样的?
永恒的进化论规律现在将 OLE DB 技术向前推进了一步,以满足新用户的要求。在 .NET 中,Web 应用程序首先是一个断开的应用程序,它利用新设计的特殊工具来管理数据。
.NET 框架使得类能够处理数据。这些类——特别是 ADO.NET 和 XML 名称空间——可供收集、读取和写入。ADO.NET 和 XML 子系统最终取代了 ADO 和 OLE DB SDK。现在,您拥有了一个唯一的、以语言为中心的方法来获取和设置数据。
ADO.NET 类对数据源的抽象能力甚至比 ADO 还要好,因为它明确设计为以数据为中心,而 ADO 中仍然使用以数据库为中心的设计。
.NET 中对应于 OLE DB 数据提供者的部分称为“托管提供者”。它们的作用如下图所示。
图 1:托管提供者层次结构图
在 OLE DB 中可以按照相似性来识别两个交互的层,即我上面提到的托管使用者层和托管提供者层。在处理数据时,.NET 应用程序不必将特殊的类或组件作为使用者模块使用。
如果 .NET 应用程序只使用本机框架中的 DataSet 或 DataReader 对象,则其将立即成为“托管”数据使用者。要真正地获取数据,应使用从 DataSetCommand 和 DBCommand 中继承的特殊类的实例。这些类代表了到数据源的链接。
你只需使用了解如何处理给定提供者的导出类,而不用指导通用对象来处理给定的提供者。所以出现的情况是,SQLDataSetCommand 将处理 SQL Server 数据库,而 ADODataSetCommand 将包装所有现有 OLE DB 提供者。
托管提供者将隐藏在这样的 DataSetCommand 类中。你永远不会意识到它们的存在,也无需特意了解它们。只要使用类和设置属性就可以了,这让人感到愉快。
在这种情况下,上图中的托管提供者层使用的交互模块与 OLE DB 甚至更早的 ODBC 中使用的模块没有太大不同。使用者命令类针对的是包装数据源的特定组件。它了解用于在源中读写数据行的协议。它还以一种 .NET 类能够很好处理的格式返回结果。
为便于理解,让我们回顾一下 OLE DB 和 .NET 数据检索的共性。
OLE DB 提供者 | .NET 托管提供者 | |
标识 | COM progID | 包装在命令类中 |
返回结果 | Rowset 或 ADO Recordset | DataSet 或 DataReader 类 |
更新方式 | 提供者的特殊命令 | 提供者的特殊命令 |
传送格式 | 二进制 | XML |
表 1:比较 OLE DB 和 .NET 数据提供者
目标提供者是通过其位于 OLE DB 中的 COM progID 来识别的,而在 .NET 中,这些细节隐藏在访问器类中。
OLE DB 提供者总是返回行集——COM 对象主要提供 IRowset 接口。如果通过 ADO 访问数据,行集会转换为更丰富和更脚本化的对象,称作记录集。
.NET 应用程序只使用具有不同功能的各种类。DataReader 类是一个简单、快捷、只能前进的游标,在连接状态下工作并按记录来提供访问。结束后必须显式断开连接。相反,DataSet 对象是内存中的断开连接集合表。它是 DataSetCommand 类的填充的实例。DataSet 对象的内容建立在 DataSetCommand 从数据源取回的 XML 流的基础上。
我将在以后的专栏中讲述有关 DataReader 和 DataSet 的内容。
数据以二进制格式从提供者到达使用者,如果部署了 OLE DB,则还会经过 COM 配置。而在 .NET 中,托管提供者将返回一个 XML 流。
两种提供者都支持查询语言(通常是 SQL 和各个供应商特有的扩展)。通过该语言可以执行更新和询问数据源。
那么,OLE DB 数据提供者和 .NET 数据提供者之间的区别是什么呢?抽象地说,它们使用相同的数据访问策略。但是托管提供者更加简单和专业。两个主要的原因导致了其性能的优越性。首先,托管提供者不使用 COM 互操作桥来获取和设置数据。作为 COM 组件,OLE DB 提供者在这一点上别无选择。其次,托管提供者通常利用来自供应商的内部数据源知识来更快地获取和设置行。OLE DB 提供者也是这样做的,但是当在 .NET 内部使用时,OLE DB 提供者必须为其基于 COM 的特性付出代价,并且需要额外的代码将数据转换为 .NET 特有的类。
像在 Beta 1 中一样,NET 框架的特色是具有两个托管提供者:一个用于 SQL Server(7.0 版本或更高),一个用于所有能够通过 OLE DB 提供者获得的数据源。
SQL Server 托管提供者隐藏在特定的类(如 SQL DataReader、SQL DataSetCommand 和 SQLCommand)后。这些类直接访问低层的 SQL Server 文件系统。下图是提供者的类图。该图将以前的通用架构映射至 SQL Server 托管提供者。
图 2:SQL Server 托管提供者的类图
OLE DB 托管提供者在 .NET 中的作用与 ODBC OLE DB 提供者在 Windows DNA 系统中的作用是相同的。简单说,它体现了后向兼容性,也反映了这样一个事实,即所有 .NET 应用程序均可以面向任何以 OLE DB 为基础的现有数据源。OLE DB 托管提供者的类图如下所示。
图 3:OLE DB 托管提供者的类图
请注意在 Beta 2 中,ADOxxx 类需要重命名为 OleDbxxx。
OLE DB 托管提供者将 .NET 类提供给调用方,但利用指定的 OLE DB 提供者来获取行。.NET 应用程序与底层 OLE DB 提供者(COM 对象)之间的通信通过 COM 互操作桥发生。
总的来说,在 .NET 中通过上述两个提供者均可以访问 SQL Server 7.0(及更高版本)的表。SQL Server 的托管提供者直接从 DBMS 文件系统请求数据,而 OLE DB 托管提供者依赖 SQLOLEDB OLE DB 提供者的服务,从而导致需要经过额外层次的代码。
现在,如果你面对的是 SQL Server 以外的任何数据源,那么 OLE DB 托管提供者是唯一的通道。通过同一通道,你还可以到达任何 ODBC 数据源。
OLE DB 托管提供者是在 COM 互操作桥上生成的瘦包装,可以调入本地 OLE DB 提供者。除了设置和终止调用,这一模块还负责将返回的行集包装至 DataSet 或 ADO DataReader 对象,以便进行后续 .NET 处理。
在 .NET 代码层,通过本地托管提供者或 OLE DB 提供者访问 SQL Server 表实际是对涉及到的类的前缀进行更改。以下是用于 SQL Server 的代码:
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")
以下是用于 OLE DB 提供者的代码(不同之处以粗体表示):
Dim strConn, strCmd As String strConn = "Provider=SQLOLEDB;" strConn += "DATABASE=Northwind;SERVER=localhost;UID=sa;PWD=;" strCmd = "SELECT * FROM Employees" Dim oCMD As New ADODataSetCommand(strCmd, strConn) Dim oDS As New DataSet oCMD.FillDataSet(oDS, "EmployeesList")
由此可见,表面上的不同是非常小的;只有连接字符串和命令类不同。而使用一种类还是另一种类,差别却是非常大的。
.NET 托管提供者代表了数据访问技术演化的下一步发展方向,但是,在 Beta 1 中还没有文档化的 SDK 涉及数据源特定的托管提供者。几个关于 OLE DB 和 .NET 的基本问题是无法忽略的。它们正在等待着 Beta 2。
难道为 OLE DB 开发的所有代码只是过时的代码吗?公司已经投入(而且经常是仍然在投入)的为自己的数据编写提供者的一切努力将会如何?
坚守你的信念——OLE DB 不是一项消亡的技术。尤其是对于功能丰富、通用并且独立于 .NET 的编程接口,它仍然是基本的规范。它不专门针对 .NET,但它获得了很好的支持。
这就是说,如果要公开自定义数据,就不能忽略 .NET 和托管提供者的出现。那么,什么是包装数据提供者的最佳接口呢?你应该怎样计划尽快公开你的数据呢?例如,从下个星期一上午 8 点开始?
.NET 使用开放的标准并广泛地基于 XML。在这种情况下,如果你需要公开拥有所有权而又基于文本的数据,则只需要考虑使用 XML(可能是自定义方案)来发布。在 .NET 中有这么多的工具可与 XML 数据配合工作,包装类的生成应该完全没有问题。
对于更复杂的数据存储,OLE DB 提供者仍是有意义的,因为你的用户群更大,而且可能不仅仅局限于 .NET。对于 .NET 专有的应用程序,托管提供者当然可以提供巨大的性能优势,但我对此持非常谨慎的态度——尤其是要在这样短的时间内做出决定!不要忘记,目前为止还没有发布有关托管提供者的任何 SDK,尽管 Microsoft 已经做出了这样的承诺。
总之,这个星期一早晨我将要开始编写的下一个数据提供者将会包括一对 OLE DB 数据提供者和一个使用 XML 的 .NET 包装类。我的首要选择不会是用 .NET 类通过 COM Interop 来包装 OLE DB 提供者。我宁可使用同样的、经过一定调整的源代码。在这种情况下,托管 C++ 很可能是便于重用“物理”代码的最佳语言。
让我们将此作为一种预言,留待从现在开始的未来几年验证。我要冒昧地说,OLE DB 将与 SGML(标准通用标记语言,XML 的前身)一样,其最终结局不会很理想。
作为数据交换世界的救世主而引进的 SGML 从来没有成为事实上的标准,也许是因为对于日常使用来说它太过强大和复杂了。事实是,它令人激动的原理经过了适当的缩减和特殊化,并生成 XML,然后才被广泛地接受。
我的预言是,一旦 .NET 稳固了其基础,OLE DB 将逐渐丧失其重要性,直至最终消失。我不能确定这一过程将会持续多长时间,但我确信这一点。
论据就在后面 <g>。不要走开。
你很可能生活在另一个时空!你该如何将现有的 ADO 代码定义为陈旧代码(在大多数情况下,这些代码是在六个月内写成的。)?以 .NET 技术/平台的名义?它甚至还没有进入 Beta 程序的第二阶段。
没有任何重点的生活是什么样子的?无论如何,这个问题问得好。
面对近来许多 DNA 系统中的 ADO 代码,我们能够将它们定义为陈旧的代码吗?我的答案仍是:“是的,我们会。”但是我确实理解,这听起来实在令人迷惑。
我认为,“陈旧代码”就是不再适合宿主平台核心的代码。相信我,这的确是 .NET“即将”面对的状况。当然,还是有方法在 .NET 中将现有代码、组件和应用程序结合起来的。
.NET 是一场非暴力革命,在未来的几年中,它将吸取 Windows 软件中的任何有生命力的实例。反抗是徒劳的——你注定会被同化。无论代码的年龄如何,在我对“陈旧”的定义中,真正重要的是源代码与运行时的一致性。
.NET 改变了 Windows 运行时,使其被托管。虽然 COM 和 Windows SDK 还没有完全丧失生命力,但你必须根据另一个模型来编写代码。不管这一新的运行时的基础是什么,一种崭新的模型将会与旧模型展开竞争。这一新模型将是未来 Windows 的模型。
Windows 不会灭亡,但是它将改变。COM 不会灭亡,但它将不得不面对 .NET 类。ADO 不会灭亡并将继续发挥作用,但是以 ADO.NET 为特点的 .NET 才是 ADO 的未来。
.NET 并不简单的就是 Windows 6.0,ADO.NET 也不是以前称作 ADO 3.0 的奇妙新名字。它是不同的,又是普遍适用的。它是新的平台。其他的内容或者是另一个平台,或者是陈旧的代码(当集成在一起时)。
陈旧代码是不论存在时间的。我知道人们还是会编写 DNA 系统,在这一周,在六个月内,甚至在 .NET 发布后。我并不是说这是完全错误或是应该绝对避免的。只是要提醒你,你正在逆潮流而动。