Jocelyn Garner
比以往更灵活
自 MFC 数据访问对象 (DAO) 数据库类在 1995 年 10 月发布以来,MFC 数据库开发人员就已开始考虑该使用哪个类集。本白皮书并不力图用常规术语回答此问题。相反,这里要强调的是对各种选择进行检查,以便您(即开发人员)可以确定每种情况最适合使用哪个 MFC 数据库类集。
大多数 MFC 开发人员都熟悉开放式数据库连接 (ODBC) 数据库类 - 它们早在三年半以前就出现了。在 MFC 4.2 中,对这些类有一些重要的改进。
身为 C++ 开发人员,您可能不熟悉 DAO,因为到目前为止它还只可用于 Microsoft Aclearcase/" target="_blank" >ccess 和 Visual Basic® 程序设计系统。如果您熟悉 DAO,就必须了解:DAO 在 MFC 中的实现颇有不同,但包罗广泛。
了解 MFC ODBC 类的开发人员必须知道:虽然 MFC DAO 类比 ODBC 类功能性更强,但 DAO 类不能代替它们。
MFC 数据库策略
对于那些不怎么熟悉 MFC 的人,我想提醒一句:MFC 有一个适用于各种数据库类的设计原则。
MFC 像一张薄薄的包装纸那样,封装 Windows® 操作系统 API,提供您想要的 C++ 性能,同时提供您需要的抽象概念。当基础 API 具有实际意义时,向其添加值。可是多数时候 MFC 力图保持不为人触及。
就如 MFC 封装更复杂的 API 一样,数据库类也封装更复杂的技术。因为 MFC 可以在 Intel、Unix 和 Alpha 之间进行移值,所以数据库应用程序解决方案也是可移值的。
我们与 Microsoft Access、Visual Basic 使用相同的记录集模型,因此,已经使用那些产品的开发人员不必学习新的范例。另外,两个类集的体系结构基本相同,使用其中一个类集的开发人员可以轻松地切换到另一个类集并使用它。
MFC 数据库类
自从 1.5 版之后,MFC 中就已经包含 ODBC 数据库类。Visual C++ 2.0 版提供了含有相同类的 32 位版本。这些类基于一个工业认可标准,并已得到广泛应用,且因其 ODBC 具有可移植性而受到其它数据库开发选择方案的青睐。这种可移植性是指能够将许多 ODBC 数据源与用这些类创建的应用程序一起使用。近来的性能改进使得 ODBC 数据库类成为一个吸引人的选择方案。
在多数情况下,MFC 4.0 中的 DAO 数据库类允许您直接访问桌面数据源而无须使用 ODBC。DAO 数据库类所具有的同时打开多个数据库类型的能力、使用多数据源的能力以及数据定义语言能力,使其成为重要的开发选择方案。
现在,您已经准备好提出这个问题,自己应使用哪个数据库类集合。如果您所提供的有关自己项目的信息不多,就很难确定这个问题的答案。但是,第一件要考虑的事情是您使用什么数据源。如果您通常使用桌面数据,我们鼓励您考虑 MFC DAO 数据库类,因为您将会发现它们极为有效且功能强大。如果您主要使用 ODBC(基于服务器)的数据,则使用基于 ODBC 的类会使您项目的进展更富成效。
其它考虑事项包括网络种类、可伸缩性要求,以及速度是否是最重要的因素。最好是用您认为工作得最好的数据库类集让应用程序保持原型。可以进行一些基准测试,以确定您的最佳性能选择。
真正的决定权在您。
一系列选项
您可用来创建数据库应用程序的选项集合很大。它实际是完整的一系列选项,一端是桌面数据库应用程序,另一端是严格的客户/服务器数据库应用程序。仅有的两种看似明显的选择可能是:在 Microsoft Access 97 MDB 数据中使用 MFC DAO 数据库类,而在 Microsoft SQL Server ™ 6.5 中使用 MFC ODBC 数据库类。这两种组合主要是互相配合使用的,均非常有效。但您可能已对这些选择有所了解,其它情况又该如何呢?
简要地说,以下是您赖以决策的过程:
确定数据源需求
您需要的数据库有多大? 一次会有多人需要访问数据吗? 有时,其余的大多数步骤均取决于您选择的数据源。
确定接口需求
如果您需要的接口有大量的用户输入(如用户可以设计自己的查询),那么在调整和分发数据库(一个或多个)时就必须要慎重。例如,如果需要用变化不大的数据填充列表框,并且已选择基于服务器的数据源,则将通常不变化的数据存储在本地而非服务器上,是一种有意义的做法。
确定连接性需求
目前使用的网络协议有很多种,每种协议都对通过网络传送的数据各有不同影响。关于网络的讨论不是本白皮书份内之事,但您需要了解如何优化自己的数据库应用程序,以避免遇到网络的数据陷阱。
选择适当的工具(一个或多个)
Microsoft 为数据库开发人员提供了多种可选择的工具(Microsoft Access、Visual Basic、Visual Basic Enterprise、Visual C++/MFC 以及 SQL Server),在某些情况下它们的功能略有重叠。本白皮书旨在讨论 Visual C++/MFC 选项。但如果您还未考虑其它选项,应该也对它们稍加了解。
在实施前保持原型
在本白皮书中您将数次看到此陈述,因为它很重要。您可能已做了很好的选择,但除非您的解决方案真的起了作用,否则工作就不算完成!
此外,关于本白皮书,我们假设您想了解的是 MFC 的数据库类。我们不妨尽可能多地检查一些选择方案,以便您认可您想为自己的第一个原型采用的指导。
MFC ODBC 数据库类使用 Microsoft Access 和 Visual Basic 中的记录集模型。您可以用内置于类中的成员函数对记录进行筛选、分类、滚动以及其它处理。基础 ODBC 驱动程序会影响特殊应用程序的功能。因此,若要使可移植性更强,您的应用程序就必须更具有通用性,或者必须依赖于更低级别的 ODBC 功能。如有必要,可以直接调用 ODBC,以完成特殊任务。现在,让我们检查该集合中的单个类。
MFC 的 ODBC 数据库类
就象使用 MFC 那样,必要时可以调用基础 API(在此情况下为 ODBC)。
一个 CDatabase 对象代表一个到数据源的连接,通过此连接您可以在数据源上进行操作。数据源是指以某些数据库管理系统 (DBMS) 作为宿主的指定数据实例。具体示例包括 Microsoft SQL Server、Microsoft Access、Borland dBASE 和 xBASE。您可以在自己的应用程序中同时让一个或多个 CDatabase 对象处于活动状态,并且可以保持到一个数据库对象的多个连接。
一个 CRecordset 对象代表从数据源中选择的一个记录集合。称为“记录集”的 CRecordset 对象有三种可用的形式:动态集、快照、动态记录集。动态集是与其他用户的更新保持同步的记录集。快照是静态记录集,它反映的是抓拍快照时数据库的状态。动态记录集类似于动态集,但通常只用于 Microsoft SQL Server。每种形式都代表在打开记录集时确定的记录集合,但当您在动态集内或动态记录集内滚动到一条记录时,它反映后来由其他用户或您应用程序中的其它记录集对记录所做的更改。
MFC 4.2 以书签的形式向 CRecordset 类中添加新的导航功能,并添加标识及导航到记录的 AbsolutePosition 的能力。书签是唯一的标识符,通过调用该标识符可返回指定的记录。
类 CRecordView 是一种 MFC 构造 - 一种显示数据的形式。因为 CRecordView 是基于 CFormView 的,所以它具有该基础类的所有遗传功能。从本质上说,窗体视图在窗口的客户端区域伸展对话框模板。这使得添加控件与显示字段数据极为容易。
当使用 AppWizard 和 ClassWizard 创建基于 ODBC 的数据库应用程序时,记录集的列会自动绑定到(静态地)成员变量,这些变量可以随后添加到对话框模板中。
一个 CDBException 对象代表一种由数据库类引起的异常错误条件。该类中含有两个公共数据成员,可使用这两个成员确定导致异常错误的原因,或显示对异常错误进行说明的文本消息。CDBException 对象由数据库类的成员函数构造与抛出。
CFieldExchange 类支持由数据库类使用的记录字段交换 (RFX) 例程。如果您正在编写自定义数据类型的数据交换例程,则使用该类;否则,您不会直接使用该类。RFX 在您记录集对象的字段数据成员和数据源上当前记录的相应字段之间交换数据。RFX 管理两个方向中的交换,即来自数据源的交换与到数据源的交换。
数据访问对象
在 DAO 的本机格式中,包含 21 个对象和 20 个集合。而且,DAO 不仅提供单个的对象,如表和字段,还提供对象所属的集合。对象的这种清晰的层次结构使得能够轻松地将面向对象的原理应用于数据库开发。
DAO 的存在已有一段时间。DAO 1.0 版出现在 Microsoft Access 1.0 版中,它仅提供到表与查询结构的接口,以及代表具有数量有限的属性的表、动态集、快照的对象。Visual Basic 3.0 版中的 Data Access Objects 1.0 添加了 TableDef、QueryDef 和 Field 对象,以提供可编程的结构。
Microsoft Access 2.0 版中的 DAO 2.0 版有 OLE 自动化的雏形,以及对几乎所有 Microsoft Jet 功能的全部编程访问权限。它拥有具有可靠的对象与属性集合的全部对象模型。
DAO 2.5 版中含有 ODBC 桌面数据库驱动程序 (ODBC Desktop Database Driver),这些驱动程序是为 16 位平台能与 ODBC Desktop Database Drivers 2.0 版一起使用而创建的。该 16 位版本是为用于 Visual Basic 4.0 版的 16 位版本而安装的。
DAO 3.0 版随带在 Microsoft Access for Windows 95、Visual Basic 4.0 版(32 位)、Microsoft Excel 7.0 版 和 Visual C ++ 4.0 版中。DAO 得到了增强,以支持任何兼容主机的单机接口。
DAO 3.5 版随带在 Microsoft Access for Windows 97 中,并且含有新的 ODBCDirect COM 对象。MFC DAO 数据库类不包括这些对象的类。
最为重要的是,DAO 接口是基于 OLE COM 的,它很好地安置 DAO 以适应不断发展的技术和操作系统。
此处是 DAO 层次结构图表。在顶端,将看到 DBEngine 对象,该对象中含有所有其它对象。这是唯一没有集合的对象,因为您只能有一个引擎。但可以有多个工作区 (Workspace)、数据库 (Database) 等等,这正是余下的对象都有自己所属集合的原因。
在工作区中,可以有多个数据库,一个基本表 (.MDB) 或一个附加/链接的表。在每个数据库中将有一个或多个表、查询、记录集,而其中的每个表、查询、记录集不仅包含字段和(或)索引,还有其它类型的对象。
另外,与工作区连接的是用户对象和组对象,它们形成了 DAO 的安全模型。
独立但与引擎对象连接的是 Errors 对象。
Errors 集合中对象的附加方式不同于其它 DAO 集合。最详细的错误放在集合的末尾,最常规的错误放在开头。
MFC 和 DAO
现在要谈的是 MFC 如何实现 DAO。因为我们不是分别包装每个 DAO 对象,所以实际上 MFC 会平展 DAO 的层次结构。我们为您提供 8 个对象而不是 21 个对象。
我们封装所有的 DAO 功能,除 Security 对象外 - Users 对象、Groups 对象以及新的 ODBCDirect 对象。这是我们有意而为。例如,我们觉得在查看安全性对象时,围绕它们创建类将不会给 DAO 的使用添加任何值,因此还是让您直接调用 DAO,来处理那些对象。这也是与 MFC 原则一致的:应该在对添加值的立场而言有意义的地方创建类。但我们仍会在 MFC Technical Note 54 中提供关于如何实现安全性模型的指导。
另外,我们还管理 DAO 要将对象追加到集合的要求。在 DAO 中,您创建对象,然后将它追加到集合中。除一种例外情况外,这种添加是自动完成的。对于这种例外情况,可作为一个单独的步骤来设置开发人员能否追加对象,这是很用的。
虽然可能用 ODBC 数据库类进行动态绑定,但实现这一目的的功能并非 MFC 类的内置功能。它被置入 DAO 数据库类,您可以相当轻松地进行动态绑定。现在我们的第二个演示将更多地谈及这一点。
DAO 提供来自 SQL 的数据定义语言 (DDL),以便您创建数据库、表、记录集等。在 ODBC 中没有 DDL。
最后,当您需要时,总是可以对基础 DAO OLE 对象进行直接调用。
MFC DAO 数据库类的层次结构
五个由 CObject 派生的 MFC DAO 类(CDaoWorkspace、CDaoDatabase、CDaoTableDef、CDaoQueryDef 和 CDaoRecordset)具有该基础类的所有功能。
CDaoException 由 CException 派生得到,具备该类的优点,包括显示来自基础 DAO Errors 对象的错误消息的能力。
如前面提到的那样,CDaoRecordView 由 CFormView 派生得到,而 CFormView 又由 CScrollView 派生得到,依次类推。可以看到 CDaoRecordView 类在快速而轻松地实现基于窗体的数据显示方面所具有的全部优点。此外,还有对于 CDaoRecordView 的向导支持。该类中的功能实质上是与 CRecordView 类一样的。
CDaoFieldExchange 类支持由 DAO 数据库类使用的 DAO 记录字段交换 (DFX) 例程。如果要创建自定义的 DFX 例程,只要直接调用该对象即可。
MFC DAO 数据库类
CDaoWorkspace 封装 DBEngine 对象和 Workspace 对象。MFC DAO 类提供工作区的事实非常重要。ODBC 数据库类不同时支持多个数据库连接。
事务处理是在 DAO 数据库类中的 Workspace(工作区)级别完成的,而不是在 ODBC 类中的 Recordse(记录集)级别完成的。一个事务可能会影响所有打开的数据库和记录集,或者您可以隔离事务,使其只会影响指定的数据库,等等。
多数时候,您不必担心工作区对象的创建。如果您未创建,MFC 就将为您打开一个工作区对象。如果您需要的话,DAO 数据库类可支持多个工作区。
最后,您不必担心工作区对象超出范围或是在数据库会话完成前关闭。可以使用工作区指针访问工作区集合,访问数据库集合,以及访问数据库引擎的属性等。
CDaoDatabase
CDaoDatabase 在体系结构上类似于基于 ODBC 的 CDatabase 类。CDaoDatabase 也封装数据库连接。因为您不必总是使用 ODBC,所以对于大多数桌面数据源,数据源的位置就表达为路径。CDaoDatabase 可以存储 tabledef 和 querydef 对象,为您的开发提供了极大方便。CDaoDatabase 可使用本地及远程数据源。在本白皮书的稍后部分,有一个可供您使用的数据源的列表。
该数据库对象在会话期间也一直继续存在。必要时可以明确地关闭数据库连接。仅就比较而言,CDatabase 类有 21 个成员函数,而 CDaoDatabase 有 26 个成员函数。这些成员函数中彼此相应的都非常相似,在 CDaoDatabase 中还有几个没有对应成员函数。
在 ODBC 数据库类中没有与 CDaoTableDef 相应的类。TableDef 对象让您检查数据库的架构(结构),不论表是本机 Microsoft Access 表(基本表)还是链接的表。如果用 DAO 直接打开外部数据源,就可向其中添加字段和索引。如果您链接了表,就可以检查结构,但不能更改它。可以将表作为记录集的基础。这样做会使您获得几个好处,包括使用名为 Seek 的快速搜索成员函数。
使用 CDaoTableDef 可以确定是否可通过调用 CDaoTableDef::CanUpdate 编辑表中的数据。MFC 负责为您管理 DAO Field 和 Index 集合。使用 CDaoTableDef 时,您可以选择是否向 TableDefs 集合中追加表;而使用所有其它对象时,会自动进行追加。
您用来检索记录的 SQL 存储在 CDaoQueryDef 对象中。您可用该对象存储您提出的有关数据的“问题”,如“How many customers did X dollars of business last month?”。可以检索或重新使用存储的查询,可按下列三种方法之一使用它们:
- 通过将指针传递到 CDaoQueryDef 对象而创建记录集。
- 直接执行操作查询,即移动或更改数据的查询。操作查询包括追加、删除、生成表以及更新查询。删除查询和更新查询会更改现有数据;附加查询和生成表查询会移动现有数据。
- 执行 SQLPASSTHROUGH 查询:SQL 直接传递查询是直接发送到数据库服务器而不会被 Microsoft Jet 数据库引擎中断的 SQL 语句。SQL 直接传递查询为您的应用程序提供直接使用数据库服务器的功能的能力。
另外,CDaoRecordset 还很类似于基于 ODBC 的 CRecordset 类。记录集不仅可以基于表,也可以基于动态集和快照。请记住,记录集代表您已检索到的记录和穿过数据的方法。移动并滚动数据的方法包括 Seek(只用于表类型的记录集)、Find 和 Move 操作,以及 AbsolutePosition 和(如果您的数据源支持的话)书签。书签是唯一的标识符,可通过调用该标识符来返回指定的记录。
MFC DAO 数据库类中的大部分功能是存在于 CDaoRecordset 中的。CRecordset 只有 44 个成员函数,与之相比 CDaoRecordset 有 91 个成员函数。这种额外的功能表现为字段值的导航、高速缓存、设置和检索中,以及记录集属性的设置和检索。
CDaoRecordView 和 CRecordView 类有几乎相同的功能。另外,它们都还具有因基于 CFormView 而获得的优点。请记住,窗体视图就好象在窗口的客户端区域伸展的对话框模板一样,有了它,添加控件与显示字段数据就很容易。AppWizard 和 ClassWizard 支持基于窗体的数据显示。如果使用 AppWizard 创建初始应用程序,您数据库中的列就会自动绑定到成员变量。
对于 DAO 数据库类,异常错误处理略有不同。类 CDaoException 将返回基础 DAO OLE 对象的错误消息。多数时候,您可以检索的错误信息要比通常采用基于 ODBC 的类所获得的错误信息多。在 MFC 中,所有 DAO 错误都表达为 CDaoException 类型的异常错误。
当捕获到这种类型的异常错误时,可以使用 CDaoException 成员函数从任何存储在数据库引擎 Errors 集合中的 DAO 错误对象中检索信息。每个错误发生时,都会有一个或多个错误对象放入 Errors 集合中。当另一个 DAO 操作生成错误时,Errors 集合被清除,新的错误对象被放入 Errors 集合。
CDaoFieldExchange 类支持由 DAO 数据库类使用的 DAO 记录字段交换 (DFX) 例程。如果您正在编写自定义数据类型的数据交换例程,则使用该类;否则,您不会直接使用该类。DFX 在您 CDaoRecordset 对象的字段数据成员和数据源上当前记录的相应字段之间交换数据。DFX 管理两个方向中的交换,即来自数据源的交换与到数据源的交换。若需关于编写自定义 DFX 例程的信息,请参见 Technical Note 53(可在 Books Online 中的 MFC 下找到)。
CDaoFieldExchange 对象提供发生 DAO 记录字段交换所需要的上下文信息。CDaoFieldExchange 对象支持许多操作,包括绑定参数和字段数据成员,以及在当前记录的字段上设置各种标志。DFX 操作是在类型的记录集类数据成员上执行的,这些类型由 CDaoFieldExchange 中的 enum FieldType 定义。可能的 FieldType 值有:
- 用于字段数据成员的 CDaoFieldExchange::outputColumn。
- 用于参数数据成员的CDaoFieldExchange::param。
图 1 MFC 的 ODBC 数据库类
这里是基于 ODBC 的数据库类的图片,与您所了解的相同。顶端的栏代表基于 ODBC、与 ODBC 交谈的 MFC 类。每个数据库的 ODBC 驱动程序都解释 SQL 调用,并针对每个数据源对其进行转换。许多数据源都与相应的驱动程序一起显示在关系图的底部,以提醒您 ODBC 的灵活性。
图 2 是 DAO 数据库类的图片。它们通过含有 DAO 的 OLE 进行通信,DAO 与 Jet 数据库引擎交谈。Jet 数据库引擎有一些独立的 DLL,用于与各种桌面数据源进行通信。
图 2 MFC DAO 数据库类 - 桌面数据源
Microsoft Jet 数据库引擎可以直接打开诸如 FoxPro® 数据库和 Paradox 等的数据源。但只要您不需要更改这些数据源的架构,就可以将这些表链接到 Microsoft Access 数据库中,实际上这会更有效。这就是 FoxPro 和 Paradox 表(它们仅是示例)显示在两个位置的原因。虚线用以暗示当可能直接打开数据源时,它的效率会更低。
被链接表的外观及工作方式与 Microsoft Jet 数据库中的任何其它表一样(虽然在连接到远程数据和检索远程数据方面略微有些性能差异)。建立和维护与远程数据源连接所需要的信息存储在表定义中。
相反,当您直接打开表时,必须在每个会话开始时提供连接信息,以便建立与数据源的连接。建立与远程数据源的连接所需要的信息都不存储在 Microsoft Jet 数据库中。若要直接打开表,就必须使用 CDaoTableDef::Create,并且必须提供连接信息(如数据源、用户名、密码和数据库名称)。
图 3 MFC DAO 数据库类,包括 SQL 数据库
如果使用 DAO 数据库类,就可以经由 ODBC 访问服务器类型的数据库(如 Microsoft SQL Server 和 ORACLE)。图 3 不是完整的图片,因为它并没有包括对所有类型数据源的访问。
最后,图 4 是关于 MFC 数据库类的完整图片。请注意,MFC 栏和 ODBC 栏上的垂直线表示指派两个数据库类集用作“非此即彼”决策。您可以选择访问所有类型的数据源,但不能混合两个集合中的 MFC 数据库类。
图 4 MFC 数据库类
ODBC 数据库类的数据源选择
当您用 MFC ODBC 类编写应用程序时,可以连接到任何数据源(只要您有它的 ODBC 驱动程序)。ODBC 驱动程序管理器和 ODBC 驱动程序的操作在您用这些类编写的应用程序中是透明的,但个别驱动程序性能会影响应用程序的功能。
通常,MFC 动态集(但并非只向前滚动的记录集)要求 ODBC 驱动程序具有 2 级 API 一致性。如果数据源的驱动程序符合 1 级 API 设置,您就仍可以使用可更新且只读的快照与只向前滚动的记录集,但不能使用动态集。然而,如果 1 级驱动程序支持扩展的提取和键集驱动游标,它就可以支持动态集。
ODBC Desktop Driver Pack 3.0 版支持 2 级 ODBC API 调用 SQLExtendedFetch。
AppWizard 和 ClassWizard 会自动将数据源的列静态地绑定到您应用程序的成员变量中。这是在您应用程序与数据源之间建立连接的最简单的方法,但不是最灵活的方法。可以用类 CFieldExchange 将自定义的记录字段交换 (RFX) 调用添加到您的应用程序中。请参见“Technical Note 43:RFX Routines”,以获取更多信息。
另外,还可以考虑动态地绑定数据库的列。在最普通的级别中可执行下列步骤:
- 构造您的主要记录集对象。然后,可以将指针传递给一个打开的 CDatabase 对象,或者也可以用其它方法向列记录集提供连接信息。
- 执行一些步骤,动态地添加列。
- 打开您的主要记录集。
记录集会选择记录,并使用记录字段交换 (RFX) 绑定“静态”列(映射到记录集字段数据成员的列)和动态列(映射到您分配的额外存储的列)。
DAO 数据库类的数据源选项
现在我们要谈的是您可以用 DAO 数据库类连接的数据源、列的静态绑定、列的动态绑定,以及记录的双缓冲。
既然 Microsoft Access 数据库是 Jet 的本机数据库,您自然就可以以最快速度访问它们。Microsoft Access 97 具有的数据库格式是 DAO 3.5 版的本机数据库格式。如果使用 Microsoft Access 97 数据库,将获得最为快速的性能。
Jet 使用单独的 DLL 提供对 Microsoft Jet 1.x 和 2.0 版数据库的访问权限。存储引擎和格式完全是用 Microsoft Jet 3.0 版修订的。假如更改之处很多,Microsoft Jet 3.0 版就会将 2.0 版数据库视为外部 ISAM,这会影响性能。因此,这也是促使您应该考虑将 Microsoft Access 数据库升级的合理原因。
另外,您还可以访问可安装的 ISAM 数据库和 ODBC 数据源。ISAM(基于索引的连续访问方法)数据库,如 FoxPro 和 dBASE,可以直接打开,也可以链接到 Access 数据库以实现最佳性能。下面是 DAO 可以访问的数据源的列表:
- Microsoft FoxPro 的 2.0、2.5 和 2.6 版。在 3.0 版中可以导入与导出数据,但不能创建对象。
- dBASE III、dBASE IV 和 dBASE 5.0
- Paradox 的 3.x、4.x 和 5.x 版
- Btrieve 的 5.1x 和 6.0 版
- Microsoft Excel 的 3.0、4.0、5.0、7.0 和 8.0 版工作表
- Lotus WKS、WK1、WK3、WK4 电子表格
- 文本文件
请记住,Microsoft Access 的 1.x、2.0 和 7.0 版数据库都属于此类别。
可以通过 ODBC 访问 ODBC 数据源,如 SQL Server 和 Oracle,因此您可以选择针对这些数据源使用 DAO。一个 ODBC 数据源可以来自任何 DBMS,只要您拥有该 DBMS 的适当的 ODBC 驱动程序。对于 Visual C++ 2.0 版或更高版本,您需要 32 位的 ODBC 驱动程序(但 Win32 除外,在 Win32 中需要 16 位的 ODBC 驱动程序)。下面是此版本 Visual C++ 所包含的 ODBC 驱动程序列表。
- SQL Server
- Microsoft Access
- Microsoft FoxPro
- Microsoft Excel
- dBASE
- Paradox
- 文本文件
Microsoft Desktop Database Drivers 3.0 版(它包含列表中的最后六项)为这些数据源提供最佳性能。这些只限 32 位驱动程序。
将外部数据源(如 SQL Server)链接到 Microsoft Access 表是最有效的处理数据访问的方法。在将应用程序连接到远程数据源以前,必须先确保应用程序的用户可以访问远程数据,并且确保正确地设计您的应用程序,以解决远程数据源安全性难题。另外,您还必须确保应用程序能够正确地与区分大小写的数据源进行交流,并且确保正确地初始化可安装的 ISAM,以用于您想访问的数据源。最后,您必须检查您的代码,以确保在访问非 Jet 数据源时,代码没有使用 Microsoft Jet 数据源专用的对象或调用。
设置链接最简捷的方法是:进入 Microsoft Access。如果用的是 Microsoft Access 2.0 版,则使用“文件”/“附加表”命令;如果用的是 Microsoft Access 95 和 Microsoft Access 97,则使用“文件”/“获取外部数据”/“链接表”命令。
连接信息存储在您所使用的基本表 (.MDB) 中。如果移动了外部数据的位置,就必须在 Microsoft Access 中或从代码中调用 CDaoTableDef::RefreshLink,重新建立链接。
DAO 记录字段交换机制与在基于 ODBC 的数据库类中的 RFX 有着相同的工作方式。记录集对象的字段数据成员如果联系在一起,就形成一个“编辑缓冲区”,以保存一个记录的选定列。当记录集第一次打开并要读取第一条记录时,DFX 就会将每个选定列与适当字段数据成员的地址绑定(关联)在一起。当记录集更新一条记录时,DFX 调用 DAO 以向数据库引擎发送适当的命令。DFX 利用它对字段数据成员所了解的内容,指定要写入的数据源中的列(字段)。
向导支持列的静态绑定。您可以添加自己的 DFX 调用,就象使用基于 ODBC 的类时那样。首先,对于每个绑定字段和参数,都必须将成员添加到 CDaoRecordset 派生的类中。接下来,CDaoRecordset::DoFieldExchange 应被替代。请注意,成员的数据类型很重要。它应与来自数据库字段中的数据匹配,或者至少可转换成那种类型。MFC Technical #53 更详细地讲述了此过程。
CDaoFieldExchange 类支持由 DAO 数据库类使用的 DAO 记录字段交换 (DFX) 例程。如果您正在编写自定义数据类型的数据交换例程,则使用该类。CDaoFieldExchange 对象提供发生 DAO 记录字段交换所需要的上下文信息。CDaoFieldExchange 对象支持许多操作,包括绑定参数和字段数据成员,以及在当前记录的字段上设置各种标志。
DAO 数据库类中的动态绑定
如果可以用基于 ODBC 的类动态绑定列,对这种行为的支持就不会内置于 MFC 类中。动态绑定会内置在 DAO 数据库类中,而且执行起来相当容易。
还可以做一些其它的事情以优化性能,如检索记录的一部分而不是整个记录。在本陈述的稍后部分中,我们将提供一些可以优化应用程序的方法。
DFX 和动态绑定不是互相排斥的选择方案。通过 DAO 数据库类,可以将静态和动态绑定调用混合,以实现最大效率。
DAO 数据库类中的双缓冲
在 MFC 的 CDaoRecordset 类中,双缓冲是一种当记录集内的当前记录发生更改时简化检测的机制。当添加新记录和编辑现有记录时,对您的 DAO 记录集使用双缓冲会减少必需的工作量。默认情况下,您的 MFC DAO 记录集保留编辑缓冲区的第二份副本(记录集类的字段数据成员,全体复制;DAO“帮助”中称相应的缓冲区为“复制缓冲区”)。当您对数据成员进行更改时,MFC 会拿它们与副本(“双缓冲区”)进行比较以检测更改。
双缓冲的代用方法 - 不保留数据的副本 - 当编辑当前记录的字段时,要求您进行其它函数调用。
双缓冲一直是基于 ODBC 的数据库类的一部分。对于 DAO 数据库类,如果需要,您可以停用该机制以提高效率。
该机制的主开关名为 m_bCheckCacheForDirtyFields,Dirty 意思是“已更改”。如果将此开关置于 ON(开),就可以对全部或部分字段启用双缓冲。如果该主开关是 OFF(关),就会禁用整个双缓冲机制。
您最想关闭其双缓冲的字段包括备注字段、图片字段,以及其它 BLOB(大型二进制对象)。
DAO SDK 类
DAO SDK 中含有一些 C++ 数据库类,这些类彼此独立,而且不同于 MFC DAO 数据库类。这些 C++ 类将单个对象封装到 DAO 层次结构中。虽然您可以将 DAO SDK C++ 类与 MFC DAO 数据库类混合,但 DAO SDK C++ 类不遵循操作符超载的 MFC 准则,并且在混合使用这些类时您必须当心。有关的详细信息,请参阅 Microsoft Systems Journal(1999 年 6 月)中的文章“The DAO of Databases:Using Data Access Objects and the Jet Engine in C++”。
下表比较了 DAO SDK 类的功能与 MFC DAO 数据库类的功能。
DAO SDK 数据库类
|
MFC DAO 数据库类
|
从 Visual Basic 进行简单的迁移
|
从 MFC ODBC 数据库类进行简单的迁移
|
直接映射到 DAO 的 OLE 自动化对象
|
符合 MFC 标准两相结构
|
更多的 Jet/DAO 功能
|
支持 AppWizard 和 ClassWizard
|
不符合 MFC 标准两相结构
|
隐藏更困难的 DAO 功能
|
对那些用惯了 Visual Basic 的人而言,使用 DAO SDK 类是更容易实现的转换,但它不符合 MFC。那些已经使用 MFC ODBC 数据库类的开发人员则会发现,MFC DAO 类的体系结构和用法对他们来说很熟悉。
比较 MFC 数据库类
我们不妨首先重点讨论两个类集内共同的数据库功能。
二者都支持对记录集进行滚动
- ODBC 类依赖于基础驱动程序。
- DAO 类能更好地支持 MDB,对可安装的 ISAM 有益;并且对基于服务器的数据,DAO 类与 ODBC 数据库类的支持程度相同。
二者都支持事务
- 在 Database(数据库)级别的 ODBC 类。
- 在 Workspace(工作区)级别的 DAO 类。
- 记录集更新功能几乎相同。
- 二者都支持在更新期间锁定记录。
- 两个类集都支持字段数据更改的检测。
- 二者都有 Move 操作。
- CDatabase 和 CDaoDatabase 都可检测数据源是否接受事务。自从 MFC 4.2 开始,ODBC 数据库类中的事务要求就相当宽松了。
- 两种数据库对象都允许您设置预定义的查询超时时期。
- 二者都可以执行直接的 SQL 语句。
ODBC 数据库类的其它功能
DAO 数据库类的其它功能
优化您的 MFC 数据库应用程序
有一些明显但常被忽略的方法可以改进应用程序的性能。例如,查明您的网络怎样才能增强或妨碍数据检索(如异步查询)。
您所拥有的 ODBC 驱动程序的类型可能也会影响网络的性能。
- 单级驱动程序用于那些不基于 SQL 的数据库。其数据库文件直接由该驱动程序处理。该驱动程序处理 SQL 语句并从数据库中检索信息。SQL 语句一经分析与转换后,就作为基本文件操作传给数据库。处理 xBASE 文件的驱动程序是执行单级驱动程序的一个示例。
- 单级驱动程序可能会限制可提交的 SQL 语句集合。单级驱动程序必须支持的最小 SQL 语句集合是在附录 C“SQL 语法”中的 ODBC SDK《程序员指南》中定义的。
- 单级驱动程序通常要比使用本机 DBMS 工具(如 Microsoft FoxPro)慢一些,因为它们要分析 SQL 语句分析并将其转换成基本文件操作。它们慢到什么程度取决于该进程的优化程度。两个不同单级驱动程序之间的速度差异通常要归因于优化的方法。
- 在多级配置中,驱动程序将请求发送到处理这些请求的服务器。请求采用的格式可能是 SQL 或 DBMS 专用的格式。虽然整个安装可以在一个系统上完成,但更常见的则是分布到各个平台中。通常,应用程序、驱动程序和驱动程序管理器均驻留在一个系统上,称为客户端。而数据库和控制对数据库访问的软件则驻留在另一个系统上,称为服务器。有两种类型的多级驱动程序:两级和三级(或网关)。有关的详细信息,请参见 Colleen Lambert 的文章“ODBC:Architecture,Performance,and Tuning”(位于 Microsoft Developer Network 光盘)。
优化 ODBC 数据库和记录集对象
当您打开 CDatabase 对象时,就可以提供数据源名称或 NULL 以向用户显示选择对话框。MFC ODBC 数据库类不支持对数据库的排它性访问,因此连接总是共享的。但是,您可以以只读方式打开数据库。请记住,如果这样做,则所有从该 CDatabase 对象派生的记录集将也是只读的。下面的选项可使得控制台应用程序的编写更为容易。这些新选项是用户最常请求的。
- CDatabase::noOdbcDialog 无论提供的连接信息是否足够,都不显示 ODBC 连接对话框。
- CDatabase::forceOdbcDialog 总是显示 ODBC 连接对话框。
您可以选择将 ODBC 游标库与您的应用程序装载到一起。根据基础驱动程序的性能,您也可能不需要它。游标库可掩藏基础 ODBC 驱动程序的某些功能,从而有效地防止使用动态集(如果驱动程序支持它们)。如果装载了游标库,将只支持静态快照和“forwardOnly”游标。
所打开的 CRecordset 对象的类型将对应用程序性能有很大影响。如果您想让动态记录集实现双向滚动,请选择 CRecordset::dynaset 类型。若想让静态记录集实现双向滚动,请选择 CRecordset::snapshot 类型。如果不需要对数据进行来回滚动,请选择 CRecordset::forwardOnly 类型,这样会创建一个只能向前滚动的只读记录集。新的 CRecordset::dynamic 类型记录集是一个可双向滚动的记录集。在进行提取操作后,其他用户对成员身份、排序以及数据值所做的更改都是可见的。但是请注意,许多 ODBC 驱动程序不支持这种类型的记录集。
CRecordset 的其它选项有:
- CRecordset::none 未设置任何选项。默认情况下,可以用 Edit 或 Delete 对记录集进行更新,而且允许用 AddNew 追加新记录。可更新性不仅取决于数据源,也取决于您指定的选项。
- CRecordset::appendOnly 不允许在记录集上进行 Edit 或 Delete 操作。只允许使用 AddNew。
- CRecordset::readOnly 以只读方式打开记录集。
- CRecordset::optimizeBulkAdd 使用已准备好的 SQL 语句,一次优化添加多个记录的操作。该选项与 CRecordset::useMultiRowFetch 是互斥的。
- CRecordset::useMultiRowFetch 执行大量行提取,以允许在一个提取操作中检索多行。该选项与 CRecordset::optimizeBulkAdd 是互斥的。请注意,如果指定 CRecordset::useMultiRowFetch,则会自动开启选项 CRecordset::noDirtyFieldCheck(双缓冲将不可用);在只向前滚动记录集上,将自动开启选项 CRecordset::useExtendedFetch。
- CRecordset::skipDeletedRecords 在记录集内导航时跳过已删除的记录。这会降低某些相关提取的性能。该选项在只向前滚动记录集上无效。请注意,CRecordset::skipDeletedRecords 与“驱动程序包装”类似,这意味着删除过的行已从记录集内删掉了。
- CRecordset::useBookmarks 如果支持的话,可以在记录集上使用书签。书签会降低数据检索的速度,但会改进数据导航的性能。在只向前滚动记录集上书签无效。
- CRecordset::noDirtyFieldCheck 关闭自动的 Dirty 字段检查(双缓冲)。这将改进性能;但您必须通过调用SetFieldDirty 和 SetFieldNull 成员函数,手工将字段标志为 Dirty。
- CRecordset::executeDirect 不使用已准备好的 SQL 语句。在需改进性能时,如果不调用 Requery 成员函数,就请指定该选项。
- CRecordset::useExtendedFetch 执行 SQLExtendedFetch 而不是 SQLFetch。这是用于在只向前滚动记录集上执行大量行提取而设计的。如果在只向前滚动记录集上指定选项 CRecordset::useMultiRowFetch,则会自动开启 CRecordset::useExtendedFetch。
- CRecordset::userAllocMultiRowBuffers 用户将为数据分配存储缓冲区。如果想自己分配存储量,应将该选项与 CRecordset::useMultiRowFetch 一起使用;否则,框架将自动分配必要的存储量。
如果您愿意的话,可以使用大量行提取改进性能。在打开记录集对象前,可以用 SetRowsetSize 成员函数定义行集的大小。行集的大小指定了一次提取应检索多少条记录。执行大量行提取时,默认行集大小为 25。请注意。如果不执行大量行提取,行集的大小保持固定值 1。
在将行集大小初始化后,应调用 Open 成员函数。在此处您必须指定 dwOptions 参数的 CRecordset::useMultiRowFetch 选项,以执行大量行提取。另外,还可以设置 CRecordset::userAllocMultiRowBuffers 选项。大部分记录字段交换机制在提取期间均用数组来存储检索到的多行数据。这些存储缓冲区可由框架自动分配,或者您也可以手工分配它们。指定 CRecordset::userAllocMultiRowBuffers 选项就意味着要由您进行分配。
优化 DAO 数据库和记录集对象
如果使用 CDaoDatabase 对象,您应使用一个字符串表达式,该表达式是现有 Microsoft Jet (.MDB) 数据库文件的名称。如果文件名有扩展名,则要求带其扩展名。如果您的网络支持统一命名约定 (UNC),则还可以指定网络路径,如“\\\\MYSERVER\\MYSHARE\\MYDIR\\MYDB.MDB”。(双反斜线是字符串文字中所要求的,因为“\”是 C++ 的转义符。) 当以这种方式使用数据库时,有一些需要注意的事项。
如果某个数据库已由另一个用户以独占访问形式打开,MFC 就会抛出一个异常错误。使用该异常错误可以让您的用户知道该数据库是不可访问的。
如果您用空字符串 ("") 打开数据库,并正在连接到 ODBC 数据源,就会显示一个对话框,框中列出所有已注册的 ODBC 数据源名称,以便用户选择数据库。应避免与 ODBC 数据源直接连接;而是使用链接的表。
CDaoDatabase 对象可以以独占访问的方式打开。默认情况下,它是以共享访问的方式打开的。可以将它作为只读数据源打开,也可以以读/写访问方式打开。所有由 CDaoDatabase 对象派生的记录集都继承该数据库对象的读取能力。
与 CRecordset 对象一样,CDaoRecordset 有多种类型。如果选择 dbOpenDynaset,您就得到可以双向滚动的动态集类型的记录集。这是默认选项。如果选择 dbOpenSnapshot,您得到的就将是可以双向滚动的快照类型的记录集。最后,使用 dbOpenTable 可以打开能双向滚动的表类型的记录集。无法用 MFC ODBC 数据库类打开基于表的记录集。
CDaoRecordset 对象所具有的选项与 CRecordset 对象的选项类似。
- dbAppendOnly 您只可追加新记录(只限动态集类型的记录集)。该选项的字面意思是记录只可以被追加。MFC ODBC 数据库类有只追加选项,该选项允许对记录进行检索和追加。
- dbForwardOnly 记录集是只向前滚动的快照。
- dbSeeChanges 如果另一个用户要更改您正在编辑的数据,就会生成一个意外错误。
- dbDenyWrite 其他用户无法修改或添加记录。
- dbDenyRead 其他用户无法查看记录(只限表类型记录集)。
- dbReadOnly 您只可查看记录;而其他用户可修改它们。
- dbInconsistent 允许不一致的更新(只限动态集类型记录集)。
- dbConsistent 只允许一致的更新(只限动态集类型记录集)。备注 常量 dbConsistent 和 dbInconsistent 是互斥的。在给定的 Open 实例中只可使用两者中的某一个,而不能同时使用。
优化基于 ODBC 的数据库交互
下面的建议适用于任何涉及 ODBC 的交互。这不仅适用于 ODBC 数据库类,而且对那些与基于服务器的数据一起使用的 DAO 数据库类(DAO 使用 ODBC 与数据源进行通信)也适用。这些建议中有很多都是来自于 Colleen Lambert 的文章“ODBC:Architecture,Performance,and Tuning”(位于 Microsoft Developer Network 光盘)。
- 通过查询驱动程序一致性级别而尽可能多地利用驱动程序的能力。如果不需要将应用程序移植到另一个平台,就可以通过着重关注某个特定驱动程序的能力,使您应用程序的性能达到最佳状态。
- 避免调用数据源目录例程。这些例程可能会很耗时。试着调用例程一次,并将信息在本地高速缓存。
- 牢记可伸缩性,并适当地在本地和远程存放数据。设法避免在二进位组合码框中填入来自远程数据源的数据。
- 将 SQLExtendedFetch 用于滚动。如果您的驱动程序支持它,这是对 ODBC 数据源进行来回滚动的最快方法。
- 已准备好的 SQL 语句运行得更快。花费一些时间准备 SQL 调用,以便它们附加到 HSTMT 上。只有该句柄释放后,它们才会被释放。
- 使用 SQLBindCol 而不是 SQLGetData。SQLBindCol 负责列的存储和数据类型,这样您就不必为结果集而重新获得它。
- 将 SQLTypeInfo 在本地高速缓存。某些数据源将该调用作为存储过程进行处理,这执行起来可能会很耗费成本。
- 尽量少使用连接,并避免反复地断开连接和重新连接。请记住,除 ODBC 驱动程序进行的调用外,它的管理器也进行调用以准备连接。所有这种开销加起来就可能大幅度降低应用程序的速度。
- 将块提取用于多级驱动程序。多级驱动程序经常将网络性能当作自己工作的一部分使用,并且,依赖于 SQLFetch 而不是 SQLExtendedFetch 会使您的应用程序遭受网络操作奇特行为的影响。
- 通过全面了解您的 SQL 来优化查询。这可能看似显而易见,但许多程序员实际上只了解有限数量的 SQL 命令。而对 SQL 语句的进一步学习与精通,可能会使得应用程序有快与更快之分。
- 使用高性能的 DBMS。我们在本白皮书的开头就谈到了这个问题,好的数据源是应用程序的能力与操作的基础。在您的客户端负担得起的前提下,使用最好的数据源。
下面的建议是专用于 DAO 数据库类的。该列表中带星号的项主要是指与 ODBC(基于服务器)数据源的交互。
- 尽可能使用链接的表而不是直接打开的表。请参见 DAO“帮助”中的文章“DAO External:Working with External Data Sources and the topic Accessing External Databases with DAO”。在此列表的所有建议中,这项建议对性能的影响最为显著。*
- 如果不更新数据,就不要使用动态集类型的记录集。如果只向前滚动,就使用向前滚动的快照类型的记录集。不必要的话不要从头至尾滚动记录,还要避免跳到大型表的最后一条记录。
- 只检索与查看需要的数据。用受限查询来限制要检索的记录数量,并且只选择您需要的列。这样会使得要求通过网络传输的数据更少。
- 使用高速缓冲。在类 CDaoRecordset 中,MFC 支持对指定数量的记录进行高速缓存。一开始在将数据检索到高速缓存中时,这样做花费的时间会较长;但在高速缓存中移动并滚动(已检索的)记录要比滚动检索每条记录快一些。*
- 关闭 MFC CDaoRecordset 对象中的双缓冲选项。这是改进性能的常规方法,也适用于使用外部数据源的情况。
- 对于量比较大的操作,如大量添加记录,请使用 SQL 直接传递查询。当使用 SQL 直接传递查询时,只要您始终通过同一连接执行 SQL 直接传递查询,就只需一次性地设置连接信息即可。*
避免使用会导致本地处理的查询。* 不要使用含有远程列参数的用户定义函数。只在索引列上使用异类联接(在两个数据库中的表上联接),并且,如果您这样做的话,请注意到某些处理是在本地完成的。当访问外部数据时,只有在外部数据库无法执行操作时,Microsoft Jet 数据库引擎才会在本地处理数据。在本地执行的查询操作有:
- 查询顶部含有 DISTINCT 谓词的 WHERE 子句限制。通常,您可以重新安排查询,以计算合计或在所有其它操作后的 DISTINCT 查询数量。
- 含有无法远程处理的操作的 WHERE 子句,如涉及到远程列的用户定义函数。(请注意,这种情况下,只有那些无法远程处理的 WHERE 子句部分将在本地处理。)
- 来自不同数据源的表之间的联接。仅靠来自不同数据源的表之间存在联接这一个条件,并不能断定所有处理都会在本地发生。如果将限制发送给服务器,则只有相关的行会在本地处理。
- 在聚合或 DISTINCT 谓词上的联接。无论如何,正确地联接都是一门艺术,添加聚合函数(如 AVG、MAX 和 MIN)或含有 DISTINCT 的谓词只会减慢查询。
- 含有不为 ODBC 驱动程序所支持的语法的外联接。桌面数据库驱动程序不仅支持左向外联接和右向外联接,还支持内联接 左向外联接中的右表和右向外联接中的左表都可用于内联接中。完全的和嵌套的外联接都不受支持。
接下来的三项专用于您想要从远程数据源访问的数据。
- 含有无法远程处理的操作的 DISTINCT 谓词。
- ORDER BY 参数(如果远程数据源不支持它们)。
- 含有无法远程处理的操作的 ORDER BY 或 GROUP BY 参数。
- “不可远程处理的”表达式是无法由您的服务器求值的。不可远程处理的输出表达式(SELECT 子句中的那些表达式)不强制对您的查询进行本地求值,除非这些表达式出现在合计查询、DISTINCT 查询或 UNION 查询中。其它子句(如 WHERE、ORDER BY、GROUP BY、HAVING 等)中的不可远程处理的表达式至少强制您的部分查询在本地求值。
- 查询顶部含有 DISTINCT 选项的 GROUP BY 参数。使用 SELECT DISTINCTROW,后跟 GROUP BY 子句。
下面的功能在所有已知的 ODBC 可访问的服务器上都是不被支持的 - 通常是一些这样的操作:它们是 Microsoft Jet 特有的对 SQL 的扩展,如:
- 多级别 GROUP BY 参数,如在含有多个分组级别的报告中使用的参数。
- TOP 或 TOP PERCENT 谓词。
- 有多个聚合的交叉表查询或含有与 GROUP BY 子句匹配的 ORDER BY 子句的交叉表查询。简单的交叉表查询可以发送到服务器。
其它用于优化应用程序的资源
Visual C++ 随带的文档应是关于 MFC 实现 DAO 的第一个信息来源。不仅应该查阅 MFC DAO 示例和 MFC 技术说明,还应查阅 Class Library Reference 和 Programming with MFC。
有关 DAO 工作方式的其它信息,请查阅 Microsoft Jet Database Engine Programmer's Guide(Microsoft Press 出版)。
另外还有两篇推荐文章,均位于 Microsoft Developer Network 光盘上,它们是:
结论
MFC 的基本概念(即为更复杂的技术提供高级别抽象)在这些数据库类中是可以实现的。您可以选择 ODBC 或是 DAO,以获得可使客户端成功的最佳路径。
我们花了相当多的时间检查改进性能的方法,看到了一些与在许多数据源上类的执行方式有关的方法,并向您提出了许多要考虑的问题。不管您做什么,在决定以前都请保持原型。我们希望今天的会议能帮助您缩小选择范围,并使您的决定更容易实现。 |