《清华版第14章数据库编程》由会员分享,可在线阅读,更多相关《清华版第14章数据库编程(95页珍藏版)》请在金锄头文库上搜索。
1、第第14章章 数据库编程数据库编程14.1 ODBC的基本概念的基本概念 14.2 MFC的的ODBC类类 14.3 创建一个创建一个ODBC数据源的数据库管理程序数据源的数据库管理程序实例实例 14.4 深入理解深入理解CRecordset类类 14.5 几个有用的函数几个有用的函数14.6 增加程序的功能增加程序的功能 14.7 DAO简介简介 14.8 小结小结 习题习题MFC提供了对数据库编程的强大支持。对于数据库提供了对数据库编程的强大支持。对于数据库的访问,的访问,MFC提供了两组类:提供了两组类: ODBC(open database connectivity)和和DAO(dat
2、abase access object)。利用这利用这两个功能强大的类,用户可以方便地开发出基于两个功能强大的类,用户可以方便地开发出基于ODBC或或DAO的数据库应用。的数据库应用。ODBC(open database connectivity,开放数据库互连开放数据库互连)是微软公司开放服务结构是微软公司开放服务结构(windows open services architecture,WOSA)中有关数据库的一个组成部分,中有关数据库的一个组成部分,它建立了一组规范,并提供了一组对数据库访问的标它建立了一组规范,并提供了一组对数据库访问的标准准API(应用程序编程接口)。这些应用程序编程
3、接口)。这些API利用利用SQL来来完成其大部分任务。完成其大部分任务。ODBC本身也提供了对本身也提供了对SQL语言语言的支持,用户可以直接将的支持,用户可以直接将SQL语句送给语句送给ODBC。14.1 ODBC的基本概念的基本概念一个基于一个基于ODBC的应用程序对数据库的操作不依赖任的应用程序对数据库的操作不依赖任何何DBMS(database manager system, 数据库管理系数据库管理系统),不直接与统),不直接与DBMS打交道,所有的数据库操作由打交道,所有的数据库操作由对应的对应的DBMS的的ODBC驱动程序完成。也就是说,不驱动程序完成。也就是说,不论是论是FoxP
4、ro、Access还是还是Oracle数据库,均可用数据库,均可用ODBC API进行访问。由此可见,进行访问。由此可见,ODBC的最大优点的最大优点是能以统一的方式处理所有的数据库。是能以统一的方式处理所有的数据库。ODBC管理器管理器(administrator)位于位于Windows控制面控制面板板(control panel)的的32位位ODBC内,其主要任务是管内,其主要任务是管理安装的理安装的ODBC驱动程序和管理数据源。驱动程序和管理数据源。驱动程序管理器驱动程序管理器(driver manager)包含在包含在ODBC32.DLL中,对用户是透明的。其任务是管理中,对用户是透明
5、的。其任务是管理ODBC驱动程序,是驱动程序,是ODBC中最重要的部件。中最重要的部件。ODBC 驱动程序是一些驱动程序是一些DLL,提供了提供了ODBC和数据和数据库之间的接口。库之间的接口。数据源包含了数据库位置和数据库类型等信息,实际数据源包含了数据库位置和数据库类型等信息,实际上是一种数据连接的抽象。上是一种数据连接的抽象。应用程序要访问一个数据库,首先必须用应用程序要访问一个数据库,首先必须用ODBC管理管理器注册一个数据源,管理器根据数据源提供的数据库器注册一个数据源,管理器根据数据源提供的数据库位置、数据库类型及位置、数据库类型及ODBC驱动程序等信息,建立起驱动程序等信息,建立
6、起ODBC与具体数据库的联系。这样,只要应用程序将与具体数据库的联系。这样,只要应用程序将数据源名提供给数据源名提供给ODBC,ODBC就能建立起与相应数就能建立起与相应数据库的连接。据库的连接。在在ODBC中,中,ODBC API不能直接访问数据库,必须不能直接访问数据库,必须通过驱动程序管理器与数据库交换信息。驱动程序管通过驱动程序管理器与数据库交换信息。驱动程序管理器负责将应用程序对理器负责将应用程序对ODBC API的调用传递给正确的调用传递给正确的驱动程序,而驱动程序在执行完相应的操作后,将的驱动程序,而驱动程序在执行完相应的操作后,将结果通过驱动程序管理器返回给应用程序。在访问结果
7、通过驱动程序管理器返回给应用程序。在访问ODBC数据源时需要数据源时需要ODBC驱动程序的支持。驱动程序的支持。MFC的的ODBC类对较复杂的类对较复杂的ODBC API进行了封装,进行了封装,提供了简化的调用接口,从而大大方便了数据库应用提供了简化的调用接口,从而大大方便了数据库应用程序的开发。程序员不必了解程序的开发。程序员不必了解ODBC API和和SQL的具的具体细节,利用体细节,利用ODBC类即可完成对数据库的大部分操类即可完成对数据库的大部分操作。作。14.2 MFC的的ODBC类类MFC的的ODBC类主要包括:类主要包括: (1) CDatabase类类, 主要功能是建立与数据源
8、的连接。主要功能是建立与数据源的连接。(2) CRecordset类类, 该类代表从数据源选择的一组记该类代表从数据源选择的一组记录(记录集),程序可以选择数据源中的某个表作为录(记录集),程序可以选择数据源中的某个表作为一个记录集,也可以通过对表的查询得到记录集,还一个记录集,也可以通过对表的查询得到记录集,还可以合并同一数据源中多个表的列到一个记录集中。可以合并同一数据源中多个表的列到一个记录集中。通过该类可对记录集中的记录进行滚动、修改、增加通过该类可对记录集中的记录进行滚动、修改、增加和删除等操作。和删除等操作。(3) CRecordView类类, 提供了一个表单视图与某个记提供了一个
9、表单视图与某个记录集直接相连,利用对话框数据交换机制录集直接相连,利用对话框数据交换机制(DDX)在记在记录集与表单视图的控件之间传输数据。该类支持对记录集与表单视图的控件之间传输数据。该类支持对记录的浏览和更新,在撤销时会自动关闭与之相联系的录的浏览和更新,在撤销时会自动关闭与之相联系的记录集。记录集。(4) CFieldExchange类类, 支持记录字段数据交换支持记录字段数据交换(DFX),),即记录集字段数据成员与相应的数据库的即记录集字段数据成员与相应的数据库的表的字段之间的数据交换。该类的功能与表的字段之间的数据交换。该类的功能与CDataExchange类的对话框数据交换功能类
10、似。类的对话框数据交换功能类似。(5) CDBException类类, 代表代表ODBC类产生的异常。类产生的异常。概括地讲,概括地讲,CDatabase针对某个数据库,它负责连接针对某个数据库,它负责连接数据源;数据源; CRecordset针对数据源中的记录集,它负针对数据源中的记录集,它负责对记录的操作;责对记录的操作; CRecordView负责界面,而负责界面,而CFieldExchange负责负责CRecordset与数据源的数据交换。与数据源的数据交换。利用利用AppWizard和和ClassWizard,用户可以方便地建用户可以方便地建立数据库应用程序,但这并不意味着可以对立数
11、据库应用程序,但这并不意味着可以对MFC的的ODBC类一无所知。应注意阅读后面几小节中的内容,类一无所知。应注意阅读后面几小节中的内容,为学习后面的例子打好基础。为学习后面的例子打好基础。要建立与数据源的连接,首先应构造一个要建立与数据源的连接,首先应构造一个CDatabase对象,然后再调用对象,然后再调用CDatabase的的Open成员函数。成员函数。Open函数负责建立连接,其声明为函数负责建立连接,其声明为:virtual BOOL Open( LPCTSTR lpszDSN, BOOL bExclusive = FALSE, BOOL bReadOnly = FALSE, LPCT
12、STR lpszConnect = ODBC;, BOOL bUseCursorLib = TRUE ); throw( CDBException, CMemoryException );14.2.1 CDatabase类类参数参数lpszDSN指定了数据源名,在指定了数据源名,在lpszConnect参数中参数中也可包括数据源名,此时也可包括数据源名,此时lpszDSN必须为必须为NULL,若若在函数中未提供数据源名且使在函数中未提供数据源名且使lpszDSN为为NULL,则则会显示一个数据源对话框,用户可以在该对话框中选会显示一个数据源对话框,用户可以在该对话框中选择一个数据源。参数择一个
13、数据源。参数bExclusive说明是否独占数据源,说明是否独占数据源,由于目前版本的类库还不支持独占方式,故该参数的由于目前版本的类库还不支持独占方式,故该参数的值应该是值应该是FALSE,这说明数据源是被共享的。这说明数据源是被共享的。参数参数bReadOnly若为若为TRUE则对数据源的连接是只读则对数据源的连接是只读的。参数的。参数lpszConnect指定了一个连接字符串,连接指定了一个连接字符串,连接字符串中可以包括数据源名、用户账号字符串中可以包括数据源名、用户账号(ID)和口令等和口令等信息,字符串中的信息,字符串中的“ODBC”表示要连接到一个表示要连接到一个ODBC数据源上
14、。参数数据源上。参数bUseCursorLib若为若为TRUE,则会装则会装载光标库,否则不装载,快照需要光标库,动态集不载光标库,否则不装载,快照需要光标库,动态集不需要光标库。若连接成功,函数返回需要光标库。若连接成功,函数返回TRUE,若返回若返回FALSE,则说明用户在数据源对话框中按了则说明用户在数据源对话框中按了Cancel按钮。若函数内部出现错误,则框架会产生一个异常。按钮。若函数内部出现错误,则框架会产生一个异常。下面是一些调用下面是一些调用Open函数的例子:函数的例子:CDatabase m_db; /在文档类中嵌入一个在文档类中嵌入一个CDatabase对象对象 /连接到
15、一个名为连接到一个名为Student Registration的数据源的数据源m_db.Open(Student Registration); /在连接数据源的同时指定了用户帐号和口令在连接数据源的同时指定了用户帐号和口令m_db.Open(NULL,FALSE,FALSE,ODBC;DSN=Student Registration;UID=ZYF;PWD=1234);m_db.Open(NULL); /将弹出一个数据源对话框将弹出一个数据源对话框要从一个数据源中脱离,可调用函数要从一个数据源中脱离,可调用函数Close。在脱离在脱离后,可以再次调用后,可以再次调用Open函数来建立一个新的连
16、接。函数来建立一个新的连接。调用调用IsOpen可判断当前是否有一个连接,调用可判断当前是否有一个连接,调用GetConnect可返回当前的连接字符串。函数的声明可返回当前的连接字符串。函数的声明为为:virtual void Close( );BOOL IsOpen( ) const; /返回返回TRUE则表明当前有一个连接则表明当前有一个连接const CString& GetConnect( ) const;CDatabase的析构函数会调用的析构函数会调用Close,所以只要删除了所以只要删除了CDatabase对象就可以与数据源脱离。对象就可以与数据源脱离。CRecordView(记
17、录视图)是记录视图)是CFormView的派生类,的派生类,它提供了一个表单视图来显示当前记录,用户可以通它提供了一个表单视图来显示当前记录,用户可以通过表单视图显示当前记录。通过记录视图,可以修改、过表单视图显示当前记录。通过记录视图,可以修改、添加和删除数据,用户一般需要创建一个添加和删除数据,用户一般需要创建一个CRecordView的派生类,并在其对应的对话框模板中的派生类,并在其对应的对话框模板中加入控件。加入控件。14.2.2 CRecordView类类记录视图使用记录视图使用DDX数据交换机制在表单中的控件和数据交换机制在表单中的控件和记录集之间交换数据。在前面介绍的记录集之间交
18、换数据。在前面介绍的DDX都是在控都是在控件和控件父窗口的数据成员之间交换数据,而记录视件和控件父窗口的数据成员之间交换数据,而记录视图则是在控件和一个外部对象(图则是在控件和一个外部对象(CRecordset的派生类的派生类对象)之间交换数据。清单显示了一个对象)之间交换数据。清单显示了一个CRecordView的派生类的的派生类的DoDataExchange函数,可以看出,该函函数,可以看出,该函数是与数是与m_pSet指针指向的记录集对象的域数据成员指针指向的记录集对象的域数据成员交换数据的。而且,交换数据的代码是交换数据的。而且,交换数据的代码是ClassWizard自动生成的。在后面
19、的例子中,将介绍用自动生成的。在后面的例子中,将介绍用ClassWizard连接记录视图与记录集对象的方法。连接记录视图与记录集对象的方法。在函数的开头先调用在函数的开头先调用CRecordset:Edit进入编辑模式,进入编辑模式,接着调用接着调用UpdateData将控件中的数据更新到记录集将控件中的数据更新到记录集对象的域数据成员中,然后调用对象的域数据成员中,然后调用CRecordset:Update将域数据成员的值写入数据源,这说明将域数据成员的值写入数据源,这说明OnMove在滚在滚动记录的同时会完成对原来记录的修改。动记录的同时会完成对原来记录的修改。在函数的中间有一个分支语句用
20、来处理在函数的中间有一个分支语句用来处理4个不同的命个不同的命令,在这个分支语句中调用了令,在这个分支语句中调用了CRecordset的各种用于的各种用于滚动记录的成员函数,这些函数在滚动到一个新的记滚动记录的成员函数,这些函数在滚动到一个新的记录时会把该记录的内容设置到域数据成员中。在函数录时会把该记录的内容设置到域数据成员中。在函数的末尾调用的末尾调用UpdateData(FALSE)把新的当前记录的内把新的当前记录的内容设置到表单的控件中。容设置到表单的控件中。由此可见,由此可见,OnMove一来一回完成了两次表单控件和一来一回完成了两次表单控件和数据源的数据交换过程。通过分析该函数,可
21、以学会数据源的数据交换过程。通过分析该函数,可以学会在浏览记录时如何控制在浏览记录时如何控制DDX和和DFX数据交换。数据交换。例例14.1 创建一个学生信息管理的数据库应用程序。创建一个学生信息管理的数据库应用程序。使用使用ODBC数据源,因此首先应该创建数据库,这里数据源,因此首先应该创建数据库,这里采用采用Access 来创建,创建的步骤如下:来创建,创建的步骤如下: (1) 运行运行Access,建立一个新数据库,建立方法可以建立一个新数据库,建立方法可以参考有关书籍。参考有关书籍。14.3 创建一个创建一个ODBC数据源的数据库管理数据源的数据库管理程序实例程序实例(2) 数据库创建
22、完毕,就可以来创建数据库创建完毕,就可以来创建ODBC数据源了,数据源了,单击计算机的单击计算机的“开始开始”按钮,选择按钮,选择“设置设置”“控制控制面板面板”命令,在弹出的控制面板对话框中,双击命令,在弹出的控制面板对话框中,双击ODBC数据源图标,如图数据源图标,如图14.1所示。所示。图图14.1 在控制面板上创建在控制面板上创建ODBC数据源数据源此时,弹出图此时,弹出图14.2所示的对话框。所示的对话框。图图14.2 创建创建ODBC数据源数据源 单击单击“用户用户DSN”选项卡中的选项卡中的“添加添加”按钮,来创建按钮,来创建一个一个Access类型的数据源,按照图类型的数据源,
23、按照图14.3所示对话框进所示对话框进行设置,选取上面建好的数据库,正如所看到的,将行设置,选取上面建好的数据库,正如所看到的,将数据源的名称取为数据源的名称取为“DSNSTUDENT”,这个名称在下面这个名称在下面进行程序设计时要用到。进行程序设计时要用到。图图14.3 设置数据源的名称设置数据源的名称(3) 做完这些准备工作后,创建应用程序的框架。遵做完这些准备工作后,创建应用程序的框架。遵循以下步骤:循以下步骤: 运行运行AppWizard来创建一个来创建一个MFC AppWizard的新项的新项目,将其命名为目,将其命名为“student”。第一步,选择基于第一步,选择基于Single
24、 document,语言支持中文。语言支持中文。第二步,选择第二步,选择Database view with file support,并单并单击击Data Source按钮,为程序指定图按钮,为程序指定图14.4所示的数据源,所示的数据源,集合类型选为集合类型选为Dynaset,单击单击OK按钮,在弹出的对话按钮,在弹出的对话框中为程序选中可用的表框中为程序选中可用的表“学生学生”(在本例中,它是(在本例中,它是惟一可用的表),单击惟一可用的表),单击OK按钮,完成后单击按钮,完成后单击Next按按钮。钮。图图14.4 在创建应用程序框架时指定数据源在创建应用程序框架时指定数据源第三步到第六
25、步,保持默认设置,完成应用程序的框第三步到第六步,保持默认设置,完成应用程序的框架设计。架设计。这时,可先停下来,认真看一下应用程序向导所做的这时,可先停下来,认真看一下应用程序向导所做的一切。生成的类中有两点与以前的不一样,一是类一切。生成的类中有两点与以前的不一样,一是类CStudentView,它由它由CRecordView类派生,而类派生,而CRecordView类是类是CFormView的子类,它在的子类,它在CFormView类的基础上添加了支持数据库的功能,类的基础上添加了支持数据库的功能,前面对它已有阐述;前面对它已有阐述; 二是二是CStudentSet类,它由类,它由CRe
26、cordSet类派生而来,关于类派生而来,关于CRecordSet类在前面类在前面已作过介绍。已作过介绍。(4) 进行窗体设计。在项目区中选择类视图区展开进行窗体设计。在项目区中选择类视图区展开Dialog,双击双击IDD_STUDENT_FORM编辑显示的窗编辑显示的窗体,按图体,按图14.5添加控件添加控件, 然后按表然后按表14.1来设置控件的来设置控件的属性。(见书上表属性。(见书上表14.1)图图14.5 设计显示数据库数据的表单设计显示数据库数据的表单将控件与变量进行关联,步骤如下:将控件与变量进行关联,步骤如下: 运行类向导,在运行类向导,在Member Variables选项卡
27、中,选择相选项卡中,选择相应的控件后单击应的控件后单击Add Variable按钮。所需关联的变量按钮。所需关联的变量见表见表14.2。(见书上表。(见书上表14.2)程序中应出现以下的代码:程序中应出现以下的代码:void CStudentView:DoDataExchange(CDataExchange* pDX)CRecordView:DoDataExchange(pDX);/AFX_DATA_MAP(CStudentView)/ NOTE: the ClassWizard will add DDX and DDV calls here/AFX_DATA_MAPDDX_FieldText
28、(pDX,IDC_EID,m_pSet-m_ID,m_pSet);DDX_FieldText(pDX,IDC_ENAME,m_pSet-m_name,m_pSet);DDX_FieldText(pDX,IDC_EZIP,m_pSet-m_zip,m_pSet);DDX_FieldText(pDX,IDC_ETELEPHONE,m_pSet-m_telephone,m_pSet);DDX_FieldText(pDX,IDC_ECITY,m_pSet-m_city,m_pSet);DDX_FieldText(pDX,IDC_EPROVINCE,m_pSet-m_province,m_pSet);D
29、DX_FieldText(pDX,IDC_EADDRESS,m_pSet-m_address,m_pSet);DDX_FieldText(pDX,IDC_EMAJOR,m_pSet-m_major,m_pSet);上面的代码体现了视图中控件与变量的数据交换的机上面的代码体现了视图中控件与变量的数据交换的机制。制。此时调试运行程序,如果顺利的话,应该看到图此时调试运行程序,如果顺利的话,应该看到图14.6所示的运行界面。在界面上,它与一般的单文档应用所示的运行界面。在界面上,它与一般的单文档应用程序显著的不同是在工具栏上多了一组数据库程序特程序显著的不同是在工具栏上多了一组数据库程序特有的按钮,
30、它们实现在数据库记录间的移动;有的按钮,它们实现在数据库记录间的移动; 在菜在菜单条中,也会多出单条中,也会多出“记录记录”菜单项,打开这个菜单可菜单项,打开这个菜单可以看到有以看到有4个子菜单,个子菜单,“第一个记录第一个记录”、“前一个记前一个记录录”、“下一个记录下一个记录”和和“最后一个记录最后一个记录”,它们分,它们分别对应着前面所说的工具栏中别对应着前面所说的工具栏中4个按钮。个按钮。图图14.6 程序的运行结果程序的运行结果此时,除了浏览记录外,还可以修改记录的内容。这此时,除了浏览记录外,还可以修改记录的内容。这里值得注意的是,尽管在程序中已修改了记录的内容,里值得注意的是,尽
31、管在程序中已修改了记录的内容,但数据库没有立即更新,直到用户移动了当前记录,但数据库没有立即更新,直到用户移动了当前记录,才能实现数据库的更新。才能实现数据库的更新。CRecordset类代表一个记录集,该类是类代表一个记录集,该类是MFC的的ODBC类中最重要、功能最强大的类之一。类中最重要、功能最强大的类之一。记录集主要分为快照记录集主要分为快照(Snapshot) 和动态集和动态集(Dynaset)两种,两种,CRecordset类对这两者都支持。这两种记录集类对这两者都支持。这两种记录集的不同,表现在它们对别的应用改变数据源记录采取的不同,表现在它们对别的应用改变数据源记录采取了不同的
32、处理方法。了不同的处理方法。14.4 深入理解深入理解CRecordset类类快照型记录集提供了对数据的静态视图。快照是个很快照型记录集提供了对数据的静态视图。快照是个很形象的术语,就好像对数据源的某些记录拍了一张照形象的术语,就好像对数据源的某些记录拍了一张照片一样。当别的用户改变了记录时(包括修改、添加片一样。当别的用户改变了记录时(包括修改、添加和删除),快照中的记录不受影响。需要指出的是,和删除),快照中的记录不受影响。需要指出的是,快照的这种静态特性是相对于别的用户而言的,它会快照的这种静态特性是相对于别的用户而言的,它会正确反映由本身用户对记录的修改和删除,但对于新正确反映由本身用
33、户对记录的修改和删除,但对于新添加的记录直到调用添加的记录直到调用Requery后才能反映到快照中。后才能反映到快照中。动态集提供了数据的动态视图,当别的用户修改或删动态集提供了数据的动态视图,当别的用户修改或删除了记录集中的记录时,会在动态集中反映出来。当除了记录集中的记录时,会在动态集中反映出来。当滚动到修改过的记录时,对其所作的修改会立即反映滚动到修改过的记录时,对其所作的修改会立即反映到动态集中,当记录被删除时,到动态集中,当记录被删除时,MFC代码会跳过记代码会跳过记录集中的删除部分,对于其他用户添加的记录,直到录集中的删除部分,对于其他用户添加的记录,直到调用调用Requery时,
34、才会在动态集中反映出来。本身应时,才会在动态集中反映出来。本身应用程序对记录的修改、添加和删除会反映在动态集中。用程序对记录的修改、添加和删除会反映在动态集中。当数据是动态的时候,使用动态集是最适合的。当数据是动态的时候,使用动态集是最适合的。快照和动态集有一个共同的特点,那就是在建立记录快照和动态集有一个共同的特点,那就是在建立记录集后,记录集中的成员就已经确定了,这就是为什么集后,记录集中的成员就已经确定了,这就是为什么两种记录集都不能反映别的用户添加记录的原因。两种记录集都不能反映别的用户添加记录的原因。本例中建立的是动态集类型的记录集。见下面的代码:本例中建立的是动态集类型的记录集。见
35、下面的代码:CStudentSet:CStudentSet(CDatabase* pdb): CRecordset(pdb)/AFX_FIELD_INIT(CStudentSet)m_ID = 0;m_name = _T();m_address = _T();m_city = _T();m_province = _T();m_zip = _T();m_telephone = _T();m_major = _T();m_numstu = 0;m_nFields = 9;/AFX_FIELD_INITm_nDefaultType = dynaset; /记录集的类型为动态记录集的类型为动态CStr
36、ing CStudentSet:GetDefaultConnect()return _T(ODBC;DSN=DSNSTUDENT); /设设置置数数据据源源CString CStudentSet:GetDefaultSQL()return _T(学生学生); /设置管理的数据表设置管理的数据表CRecordset类代表一个记录集,用户一般需要用类代表一个记录集,用户一般需要用ClassWizard创建一个创建一个CRecordset的派生类。的派生类。ClassWizard可以为派生的记录集类创建一批数据成可以为派生的记录集类创建一批数据成员,这些数据成员与记录的各字段相对应,被称为字员,这些
37、数据成员与记录的各字段相对应,被称为字段数据成员或域数据成员。域数据成员用来保存某条段数据成员或域数据成员。域数据成员用来保存某条记录的各个字段,它们是程序与记录之间的缓冲区。记录的各个字段,它们是程序与记录之间的缓冲区。域数据成员代表当前记录,当在记录集中滚动到某一域数据成员代表当前记录,当在记录集中滚动到某一记录时,框架自动地把记录的各个字段拷贝到记录集记录时,框架自动地把记录的各个字段拷贝到记录集对象的域数据成员中,当用户要修改当前记录或增加对象的域数据成员中,当用户要修改当前记录或增加新记录时,程序先将各字段的新值放入域数据成员中,新记录时,程序先将各字段的新值放入域数据成员中,然后调
38、用相应的然后调用相应的CRecordset成员函数把域数据成员设成员函数把域数据成员设置到数据源中。置到数据源中。因此,在记录集与数据源之间有一个数据交换问题。因此,在记录集与数据源之间有一个数据交换问题。CRecordset类使用类使用“记录域交换记录域交换”( Record Field Exchange,缩写为缩写为RFX)机制自动地在域数据成员和机制自动地在域数据成员和数据源之间交换数据。数据源之间交换数据。RFX机制与对话数据交换机制与对话数据交换(DDX)类似,类似,CRecordset的成员函数的成员函数DoFieldExchange负责数据交换任务,在该函数中调用了一系列负责数据
39、交换任务,在该函数中调用了一系列RFX函函数,一般由数,一般由ClassWizard自动建立。下面是本例中为自动建立。下面是本例中为数据交换建立的代码:数据交换建立的代码: void CStudentSet:DoFieldExchange(CFieldExchange* pFX) /AFX_FIELD_MAP(CStudentSet) pFX-SetFieldType(CFieldExchange:outputColumn); RFX_Long(pFX, _T(ID), m_ID); RFX_Text(pFX, _T(name), m_name);RFX_Text(pFX, _T(addres
40、s), m_address);RFX_Text(pFX, _T(city), m_city); RFX_Text(pFX, _T(province), m_province); RFX_Text(pFX, _T(zip), m_zip); RFX_Text(pFX, _T(telephone), m_telephone); RFX_Text(pFX, _T(major), m_major); RFX_Long(pFX, _T(numstu), m_numstu); /AFX_FIELD_MAP CRecordset提供了几个成员函数用来在记录集中滚动,提供了几个成员函数用来在记录集中滚动,当用
41、这些函数滚动到一个新记录时,框架会自动地把当用这些函数滚动到一个新记录时,框架会自动地把新记录的内容拷贝到域数据成员中。新记录的内容拷贝到域数据成员中。void MoveNext() 前进一个记录;前进一个记录; void MovePrev() 后退一个记录;后退一个记录; void MoveFirst() 滚动到记录集中的第一个记录;滚动到记录集中的第一个记录; void MoveLast() 滚动到记录集中的最后一个记录;滚动到记录集中的最后一个记录; void SetAbsolutePosition( long nRows ),该函数用于该函数用于滚动到由参数滚动到由参数nRows指定的
42、绝对位置处;指定的绝对位置处; 14.5 几个有用的函数几个有用的函数BOOL IsEOF() const如果记录集为空或滚动过了最如果记录集为空或滚动过了最后一个记录,那么函数返回后一个记录,那么函数返回TRUE,否则返回否则返回FALSE; BOOL IsBOF() const 如果记录集为空或滚动过了第如果记录集为空或滚动过了第一个记录,那么函数返回一个记录,那么函数返回TRUE,否则返回否则返回FALSE。上面的程序总还缺少点什么。下面,为程序添加两个上面的程序总还缺少点什么。下面,为程序添加两个功能,记录的添加和记录的删除。实现这两个功能,功能,记录的添加和记录的删除。实现这两个功能
43、,有必要接着上文讲一下记录添加和删除的实现。有必要接着上文讲一下记录添加和删除的实现。14.6 增加程序的功能增加程序的功能要向记录集中添加新的记录,应该按下列步骤进行:要向记录集中添加新的记录,应该按下列步骤进行: (1) 调用调用AddNew成员函数进入添加模式,该函数把成员函数进入添加模式,该函数把所有的域数据成员都设置成所有的域数据成员都设置成NULL(注意,在数据库注意,在数据库术语中,术语中,NULL是指没有值,这与是指没有值,这与C+的的NULL是不是不同的同的)。与。与Edit一样,一样,AddNew会把当前域数据成员的会把当前域数据成员的内容保存在一个缓冲区中,在必要的时候,
44、程序可以内容保存在一个缓冲区中,在必要的时候,程序可以再次调用再次调用AddNew取消添加操作并恢复域数据成员原取消添加操作并恢复域数据成员原来的值,调用后程序仍处于添加模式,调用来的值,调用后程序仍处于添加模式,调用Move(AFX_MOVE_REFRESH)可退出添加模式,同可退出添加模式,同时该函数会从缓冲区中恢复域数据成员。时该函数会从缓冲区中恢复域数据成员。(2) 调用调用Update把域数据成员中的内容作为新记录写把域数据成员中的内容作为新记录写入数据源,从而结束添加。入数据源,从而结束添加。如果记录集是快照,那么在添加一个新的记录后,需如果记录集是快照,那么在添加一个新的记录后,
45、需要调用要调用Requery重新查询,因为快照无法反映添加操重新查询,因为快照无法反映添加操作。作。要删除记录集的当前记录,应按下面两步进行:要删除记录集的当前记录,应按下面两步进行: (1) 调用调用Delete成员函数,该函数会同时给记录集和成员函数,该函数会同时给记录集和数据源中当前记录加上删除标记。注意不要在一个空数据源中当前记录加上删除标记。注意不要在一个空记录集中调用记录集中调用Delete,否则会产生一个异常。否则会产生一个异常。(2) 滚动到另一个记录上以跳过删除记录。滚动到另一个记录上以跳过删除记录。上面提到的函数声明为:上面提到的函数声明为: virtual void Ad
46、dNew( );throw( CDBException );virtual void Delete( );throw( CDBException );virtual BOOL Update( );throw( CDBException ); 若更新失败则函数返回若更新失败则函数返回FALSE,且会产生一个异常。且会产生一个异常。在对记录集进行更改以前,程序也许要调用下列函数在对记录集进行更改以前,程序也许要调用下列函数来判断记录集是否可以更改,因为如果在不能更改的来判断记录集是否可以更改,因为如果在不能更改的记录集中进行修改、添加或删除将导致异常的产生。记录集中进行修改、添加或删除将导致异常的
47、产生。BOOL CanUpdate( ) const; /返回返回TRUE表明记录是表明记录是可以修改、添加和删除的。可以修改、添加和删除的。BOOL CanAppend( ) const; /返回返回TRUE则表明可以则表明可以添加记录。添加记录。本例中,在进行记录的添加时,有一个需要注意的问本例中,在进行记录的添加时,有一个需要注意的问题,因为在数据表的设计时将编号作为记录的主关键题,因为在数据表的设计时将编号作为记录的主关键字,这要求在添加记录时,每两个记录的编号项不能字,这要求在添加记录时,每两个记录的编号项不能相同。可以通过设置编号所对应的文本框的属性来解相同。可以通过设置编号所对应
48、的文本框的属性来解决这个问题,冻结文本框的可编辑功能,使之只用于决这个问题,冻结文本框的可编辑功能,使之只用于显示,编号由程序自动添加。在窗体设计的环境下右显示,编号由程序自动添加。在窗体设计的环境下右击编号所对应的文本框,在弹出的属性框中核选击编号所对应的文本框,在弹出的属性框中核选Disabled选项。选项。添加和删除功能采用菜单驱动,为此需要编辑菜单资添加和删除功能采用菜单驱动,为此需要编辑菜单资源,方法与以前相同。增加两个菜单和一个分隔栏,源,方法与以前相同。增加两个菜单和一个分隔栏,如图如图14.7所示,菜单属性设计见表所示,菜单属性设计见表14.3。图图14.7 设计菜单设计菜单表
49、表14.3 设置菜单的属性设置菜单的属性菜单菜单ID属性属性设置设置ID_ADDCaption添加(添加(&a)ID_DELCaption删除(删除(&d)Separator核选核选为菜单映射函数,方法在前文已有阐述。编辑下面的为菜单映射函数,方法在前文已有阐述。编辑下面的代码:代码: void CStudentView:OnAdd() / TODO: Add your command handler code here CRecordset * pset = OnGetRecordset(); /保存当前记录的任何变化保存当前记录的任何变化 if (pset-CanUpdate() & !p
50、set-IsDeleted() pset-Edit(); if (!UpdateData() return; pset-Update(); /获取新记录的获取新记录的ID号号 long m_lnewID=m_pSet-GetMaxID()+1; /添加新记录添加新记录 m_pSet-AddNew(); m_pSet-m_ID=m_lnewID; /保存新记录保存新记录 m_pSet-Update(); /更新记录集更新记录集 m_pSet-Requery(); m_pSet-MoveLast(); /更新显示窗体更新显示窗体 UpdateData(FALSE); void CStudentVi
51、ew:OnDelrec() / TODO: Add your command handler code here if (MessageBox(Are you sure you want to delete this record?, Delete this record?,MB_YESNOCANCEL | MB_ICONQUESTION)=IDYES) /删除当前记录删除当前记录 m_pSet-Delete(); /数据记录指针前移数据记录指针前移 m_pSet-MovePrev(); /更新数据更新数据 UpdateData(FALSE); 除了除了ODBC 类,类,MFC也提供了一组也提
52、供了一组DAO(database access object)类,也可以用来创建数据库应用程序。类,也可以用来创建数据库应用程序。DAO在很大程度上是在很大程度上是ODBC 类的超集,它包含了类的超集,它包含了ODBC 类的大部分功能。与类的大部分功能。与ODBC一样,一样,DAO提供提供了一组了一组API供编程使用,从而大大简化了程序的开发。供编程使用,从而大大简化了程序的开发。利用利用MFC的的DAO类,用户可以编写独立于类,用户可以编写独立于DBMS的的应用程序。应用程序。14.7 DAO简介简介DAO类与类与ODBC类相比具有很多相似之处。首先,类相比具有很多相似之处。首先,二者都支持
53、对各种二者都支持对各种ODBC数据源的访问。虽然二者使数据源的访问。虽然二者使用的数据引擎不同,但都可以满足用户编写独立于用的数据引擎不同,但都可以满足用户编写独立于DBMS的应用程序的要求;的应用程序的要求; 其次,其次,DAO提供了与提供了与ODBC功能相似的功能相似的MFC类,它们的大部分成员函数类,它们的大部分成员函数都是相同的。都是相同的。由于由于DAO类使用了微软类使用了微软Jet数据库引擎,与数据库引擎,与ODBC类类十分相似,因此只要掌握了十分相似,因此只要掌握了ODBC,就很容易学会使就很容易学会使用用DAO。实际上,可以很轻松地把数据库应用程序实际上,可以很轻松地把数据库应
54、用程序从从ODBC移植到移植到DAO,只需要将程序中使用的类的只需要将程序中使用的类的名字对应到名字对应到DAO中。即中。即CDatabase 改为改为CDaoDatabase, CRecordset 改为改为CDaoRecordset, CRecordView 改为改为CDaoRecordView。在在ODBC 和和DAO之间还有许多不同的地方。其中,之间还有许多不同的地方。其中,一个较大的不同是系统实现函数的方法不同,一个较大的不同是系统实现函数的方法不同,ODBC 使用一组使用一组DLL实现,而实现,而DAO使用使用OLE对象来实现。对象来实现。一般地讲,一般地讲,DAO类提供了比类提供
55、了比ODBC类更广泛的支持。类更广泛的支持。一方面,只要有一方面,只要有ODBC驱动程序,使用驱动程序,使用Microsoft Jet的的DAO就可以访问就可以访问ODBC数据源。另一方面,由于数据源。另一方面,由于DAO是基于是基于Microsoft Jet引擎的,因而在访问引擎的,因而在访问Access数据库数据库(即即*.MDB文件文件)时具有很好的性能,但在访问时具有很好的性能,但在访问其他类型的库时,效率不是很高。其他类型的库时,效率不是很高。还有以下的不同之处:还有以下的不同之处: (1) 记录集的默认类型不同。记录集的默认类型不同。ODBC记录集的默认类记录集的默认类型是快照型是
56、快照(Snapshot),而而DAO默认类型则是动态集默认类型则是动态集(Dynaset)。(2) 参数化的方式不同。参数化的方式不同。DAO记录集的记录集的m_strFilter和和m_strSort中的参数不是中的参数不是“?”号,而是一个有意义的号,而是一个有意义的参数名。参数名。例如,在下面的过滤器中有一个名为例如,在下面的过滤器中有一个名为CourseIDParam的参数:的参数:m_pSet-m_strFilter =CourseID = CourseIDParam;在在DoFieldExchange函数中,有下面两行:函数中,有下面两行:pFX-SetFieldType(CDao
57、FieldExchange:param);DFX_Text(pFX, _T(CourseIDParam), m_strCourseIDParam);DFX函数的第二个参数也是函数的第二个参数也是CourseIDParam。(3) 处理异常的方式不同。例如,在删除记录时,对处理异常的方式不同。例如,在删除记录时,对异常的处理如下所示:异常的处理如下所示:try m_pSet-Delete();catch(CDaoException* e) AfxMessageBox(e- m_pErrorInfo-m_strDescription); e-Delete();DAO记录集是使用是记录集是使用是DF
58、X数据交换机制数据交换机制(DAO record field exchange)而不是而不是RFX,在在DAO记录集的记录集的DoFieldExchange中中, 使用的是使用的是DFX函数而不是函数而不是RFX函数。函数。DAO可以通过可以通过ODBC驱动程序访问驱动程序访问ODBC数据源。但数据源。但DAO是基于是基于Microsoft Jet引擎的,通过该引擎,引擎的,通过该引擎,DAO可以直接访问可以直接访问Access、FoxPro、dBASE、Paradox、Excel和和Lotus WK等数据库。等数据库。CDaoDatabase类可以直接与这些数据库进行连接,类可以直接与这些数
59、据库进行连接,而不必在而不必在ODBC管理器中注册管理器中注册DSN。例如,下面的代例如,下面的代码用来打开一个码用来打开一个FoxPro数据库:数据库:CDaoDatabase daoDb; daoDb.Open( “”,FALSE,FALSE,FoxPro 2.5;DATABASE=c:zyf); CDaoDatabase:Open函函数数用用来来连连接接某某个个数数据据库库,该该函数的声明为:函数的声明为:virtual void Open( LPCTSTR lpszName, BOOL bExclusive = FALSE, BOOL bReadOnly = FALSE, LPCTST
60、R lpszConnect = _T() );throw( CDaoException, CMemoryException );参数参数bExclusive如果为如果为TRUE,则函数以独占方式打则函数以独占方式打开数据库,否则就用共享方式。如果开数据库,否则就用共享方式。如果bReadOnly为为TRUE,那么就以只读方式打开数据库。如果要打开那么就以只读方式打开数据库。如果要打开一个一个Access数据库,则可以在数据库,则可以在lpszName参数中指定参数中指定MDB文件名。如果要访问非文件名。如果要访问非Access数据库,则应使数据库,则应使该参数为该参数为“”,并在,并在lpsz
61、Connect中说明一个连接字中说明一个连接字符串。连接字符串的形式一般为符串。连接字符串的形式一般为“数据库类型;数据库类型; DATABASE=路径(文件)路径(文件)”,例如,例如“dBASE ;DATABASE=c:MYDIR”。Open函数也可以打开一个函数也可以打开一个ODBC数据源,但这需要数据源,但这需要相应的相应的ODBC驱动程序,并需要在驱动程序,并需要在ODBC管理器中注管理器中注册册DSN。此时此时lpszConnect的形式为的形式为“ODBC;DSN=MyDataSource”。显然,用显然,用DAO访问象访问象FoxPro这样的数据库时,直接打开比把它当作这样的数
62、据库时,直接打开比把它当作ODBC数据源打开要省事。数据源打开要省事。支持支持DDL是是DAO对数据库编程良好支持的一个重要对数据库编程良好支持的一个重要体现。体现。DDL(data definition language)在在SQL术语中术语中叫做叫做“数据定义语言数据定义语言”,它用来完成生成、修改和删,它用来完成生成、修改和删除数据库结构的操作。除数据库结构的操作。ODBC类只支持类只支持DML(data manipulation language,数据操作语言数据操作语言),不支持,不支持DDL,所以用所以用ODBC类只能完成数据的操作,不能涉类只能完成数据的操作,不能涉及数据库的结构
63、。要执行及数据库的结构。要执行DDL操作,只有通过操作,只有通过ODBC API。而而DAO类同时提供了对类同时提供了对DML和和DDL的支持,的支持,这意味着程序可以使用这意味着程序可以使用DAO类方便地创建数据库及类方便地创建数据库及修改数据库的结构。修改数据库的结构。与与ODBC相比,相比,DAO提供了一些新类来加强其功能,提供了一些新类来加强其功能,这些新类包括:这些新类包括: CDaoTableDef类提供了对表的结构的定义。调用类提供了对表的结构的定义。调用CDaoTableDef:Open可以获得表的结构定义可以获得表的结构定义, 调用调用CDaoTableDef:Create可
64、以创建一张新表,调用可以创建一张新表,调用CDaoTableDef: CreateField可为表添加字段,调用可为表添加字段,调用CDaoTableDef:CreateIndex可以为表添加索引可以为表添加索引, 调用调用CDaoTableDef:Append可以把新创建的表保存到数可以把新创建的表保存到数据库中。据库中。CDaoQueryDef类代表一个查询定义类代表一个查询定义(Query definition),该定义可以被存储到数据库中。该定义可以被存储到数据库中。CDaoWorkspace提供了数据工作区提供了数据工作区(Workspace)。一一个工作区可以包含几个数据库,工作区
65、可以对所属的个工作区可以包含几个数据库,工作区可以对所属的数据库进行全体或单独的事务处理,工作区也负责数数据库进行全体或单独的事务处理,工作区也负责数据库的安全性。如果需要,程序可以打开多个工作区。据库的安全性。如果需要,程序可以打开多个工作区。DAO的另一个重要特色在于它对的另一个重要特色在于它对Access数据库提供了数据库提供了强大的支持。由于强大的支持。由于DAO是基于是基于Microsoft Jet引擎的,引擎的,所以所以DAO肯定要在肯定要在Access数据库上多做一些文章。例数据库上多做一些文章。例如,调用如,调用CDaoDatabase:Create可以直接建立一个可以直接建立
66、一个MDB文件,代码如下所示:文件,代码如下所示: m_db.Create(d:zdmstudent.mdb);利用利用AppWizard和和ClassWizard,用户可以方便地开用户可以方便地开发出性能优良的基于发出性能优良的基于DAO的的Access数据库应用程序。数据库应用程序。 由于由于DAO可以访问可以访问ODBC数据源,下面几条可以作数据源,下面几条可以作为为DAO替代替代ODBC的理由:的理由: 在某些情况下可以获得更好的性能,特别是在访问在某些情况下可以获得更好的性能,特别是在访问Microsoft Jet(.MDB)数据库时与数据库时与ODBC兼容;兼容; DAO允许数据有
67、效检查;允许数据有效检查; DAO允许用户说明表与表之间的关系。允许用户说明表与表之间的关系。当然,当然,DAO的出现并不意味着的出现并不意味着ODBC已经过时了。已经过时了。如果用户的工作必须严格限于如果用户的工作必须严格限于ODBC数据源,尤其是数据源,尤其是在开发在开发Client/Server结构的应用程序时,用结构的应用程序时,用ODBC有有较好的性能。较好的性能。例例14.2 用用DAO技术来改写例技术来改写例14.1中以中以ODBC为数据源为数据源的应用实例。的应用实例。按照与前面类似的步骤来创建按照与前面类似的步骤来创建DAO数据源的应用程数据源的应用程序,只是在指定数据源时,
68、指定一个序,只是在指定数据源时,指定一个DAO数据源,数据源,如图如图14.8所示,只要在对话框中选中所示,只要在对话框中选中DAO单选按钮,单选按钮,并指定相应的数据库文件的目录路径。并将新项目的并指定相应的数据库文件的目录路径。并将新项目的名称设为名称设为daostu。此时,应用程序向导能够创建如图此时,应用程序向导能够创建如图14.9所示的几个类。在此,可以比较与例所示的几个类。在此,可以比较与例14.1的不同的不同之处,以了解两类数据源的不同。之处,以了解两类数据源的不同。图图14.8 为工程指定为工程指定DAO数据源数据源图图14.9 应用程序向导为新工程创建的类应用程序向导为新工程
69、创建的类在对新工程进行功能设置时,为了提高效率,避免再在对新工程进行功能设置时,为了提高效率,避免再次输入例次输入例14.1中的代码及再次进行视图的设计,可以中的代码及再次进行视图的设计,可以向新工程的工作区添加原先的实例工程,直接拷贝页向新工程的工作区添加原先的实例工程,直接拷贝页面布局及相应的代码。具体做法如下:面布局及相应的代码。具体做法如下: 选择菜单命选择菜单命令令ProjectInsert Project into Workspace,弹出一对弹出一对话框,在该对话框中选择相应的工程文件话框,在该对话框中选择相应的工程文件d:exof6studentstudent.dsp,如图如图
70、14.10所示。添加另一所示。添加另一工程后的工程区窗口如图工程后的工程区窗口如图14.11所示。所示。图图14.10 向新工程所在工作区间向新工程所在工作区间添加前一个实例工程添加前一个实例工程图图14.11 添加了另一工程后的视图区添加了另一工程后的视图区首先,在视图内工程首先,在视图内工程student对应的资源视图目录下对应的资源视图目录下找到对话框条目,将找到对话框条目,将IDD_STUDENT_FORM对应的对应的窗口布局复制到新工程的视图窗口布局复制到新工程的视图IDD_DAOSTU_FORM中。中。接下来,复制菜单资源中用于添加和删除记录的两个接下来,复制菜单资源中用于添加和删
71、除记录的两个菜单项菜单项, 并分别为它们添加相应的响应处理函数。并分别为它们添加相应的响应处理函数。最后,复制相应的处理函数代码。表最后,复制相应的处理函数代码。表14.4显示相应函显示相应函数的对应项。数的对应项。表表14.4 两个工程中对应函数两个工程中对应函数工程工程daostu中的函数中的函数工程工程student中的函数中的函数void CDaostuView:OnAdd()void CStudentView:OnAdd()void CDaostuView:OnDel()voidCStudentView:OnDelrec()long CDaostuSet:GetMaxID()long
72、 CStudentSet:GetMaxID()CDaoRecordset* CDaostuView:OnGetRecordset()Crecordset *CStudentView:OnGetRecordset()接着,还应为添加的控件指定关联变量。可以利用类接着,还应为添加的控件指定关联变量。可以利用类向导来完成这个任务,按照表向导来完成这个任务,按照表14.2所示,分别为各个所示,分别为各个控件指定关联变量,如图控件指定关联变量,如图14.12所示。所示。编译新工程,可以看到,它能够完成例编译新工程,可以看到,它能够完成例14.1的功能。的功能。图图14.12 指定控件关联变量指定控件关联
73、变量本章重点介绍了如下内容:本章重点介绍了如下内容: ODBC基本概念;基本概念; MFC的的ODBC类简介;类简介; CDatabase类、类、CRecordset类、类、CRecordView类功能概述;类功能概述; MFC的的DAO和和DAO类简类简介。介。借助借助AppWizard和和ClassWizard工具创建了一个数据工具创建了一个数据库应用例程,分别以库应用例程,分别以ODBC数据源和数据源和DAO数据源来数据源来实现。实现。14.8 小结小结14-1 ODBC的含义是什么?的含义是什么?14-2 MFC中与中与ODBC有关的类有哪些?各有什么功有关的类有哪些?各有什么功能?能
74、?14-3 利用熟悉的数据库管理系统建立一个数据库,利用熟悉的数据库管理系统建立一个数据库,并建立关于该数据库的数据源。并建立关于该数据库的数据源。14-4 如何使用如何使用ClassWizard类向导建立一个数据库应类向导建立一个数据库应用程序用程序?14-5 ODBC类和类和DAO类在系统实现上有哪些不同之类在系统实现上有哪些不同之处?处?习题习题14-6 利用利用ODBC编写一个有关学生成绩数据库编写一个有关学生成绩数据库.dbf的的应用程序,其主要任务是:应用程序,其主要任务是: (1) 在屏幕上显示学生的成绩;在屏幕上显示学生的成绩; (2) 学生的英语成绩在学生的英语成绩在90分以
75、上,语言能力成绩在分以上,语言能力成绩在60分以下时,其语言能力成绩加分以下时,其语言能力成绩加10分;分;(3) 为学生成绩数据库添加为学生成绩数据库添加10个新记录,并以数学成个新记录,并以数学成绩为索引对记录进行排序;绩为索引对记录进行排序; (4) 删除学生成绩数据库中语文成绩小于删除学生成绩数据库中语文成绩小于50的记录;的记录; (5) 画一个饼图,显示学生平均成绩在画一个饼图,显示学生平均成绩在0分分59分、分、60分分79分、分、80分分89分及分及90分以上的百分比例。分以上的百分比例。14-7 利用利用DAO技术改写上一题的代码,实现同样的技术改写上一题的代码,实现同样的功能。功能。