Download presentation
Presentation is loading. Please wait.
1
第 11 章 数据库编程 关系数据库模型 使用ODBC 11.3 使用DAO 返回主目录
2
11章 数据库编程 11.1 关系数据库模型 数据结构 关系数据库采用关系模型,关系模型的数据结构是一种二维表格结构。二维表由行和列构成,称为关系数据表。如表11.1所示,表中每一列描述了对象的一个属性,如姓名、地址等,而表的每一行则是对一个对象的具体描述。表中的一行称作记录(record)或行(row),每一列称作字段(field)或列(column)。
3
表11.1 关系形式的数据 ID 姓名 地址 邮编 电话 李明 南京 张芳 西安 关系数据表中能够唯一标识一个对象的字段称为关键字。如果表中存在多个字段都能用来唯一标识表中的对象,可指定一个字段为该表的关键字,称为主关键字。 一个数据库可以包含多个相关的表,例如表11.1中字段ID为该表的关键字。如果数据库中还有另一个表用于记录表11.1中人员 的收发记录,则可以通过ID这个关键字段将两个表联系起来。此时表11.1称为主表,记录 收发记录的表称为从表,字段ID为该表的外关键字。
4
在设计关系数据库的表结构时,如果不存在可作为关键字的字段,可增加一个字段作为关键字段,用于唯一标识该行,例如上面表中的ID。大部分数据库管理系统允许在建立表时生成一个自动增加类型的字段,每当表增加一行时,由数据库管理系统自动生成该字段的内容,保证其唯一性。
5
完整性规则 数据库的数据完整性,是指数据库中数据的正确性和一致性。数据完整性由数据完整性规则来维护。 为了维护数据库中数据的正确性和一致性,在对关系数据库执行插入、删除和修改操作时必须遵循三类完整性规则。 (1) 实体完整性规则:要求主关键字不能为空值,否则主关键字起不到唯一标识表中对象的作用。 (2) 引用完整性规则:不允许引用不存在的对象。例如,如果在上面记录 收发记录的数据库中插入一条记录,其ID值在表11.1中不存在,则破坏了引用完整性规则。 (3) 用户定义的完整性规则:这是针对某一具体数据的约束条件,由应用环境决定。
6
关系数据库管理系统 关系数据库管理系统(RDBMS)是一套程序,用来定义、管理和处理关系数据库与应用程序之间的联系。例如FoxPro、Access等都是RDBMS。 一个数据库管理系统一般具有如下功能: (1) 数据定义功能:提供数据定义语言及其翻译程序,定义数据库结构、数据完整性和保密性约束等。 (2) 数据操纵功能:提供数据操纵语言及其翻译程序,实现对数据库的查询、插入、更新和删除等操作。
7
(3) 数据库运行和控制功能:包括数据完整性控制、数据安全性控制、多用户环境的并发控制等。
(4) 数据库维护功能:包括数据库数据的载入、转储和恢复,数据库的维护及数据库的功能、性能分析和检测等。 (5) 数据字典:存放数据库各级模式结构的描述,是访问数据库的接口。 (6) 数据通信功能:包括与操作系统的联机处理、分时处理和远程作业传输的相应接口等。
8
结构化查询语言(SQL) SQL(Structured Query Language,结构化查询语言)最早由IBM提出,是专门用来处理关系数据库的基于文本的语言,这种数据库语言的最初版本叫做Sequel。SQL的最新标准是SQL–92,关系数据库都使用SQL。SQL向数据库提供了完善而一致的接口,它不是独立的计算机语言,需要数据库管理系统的支持方能执行。当前大部分的数据库管理系统都支持SQL。 SQL语言不是一种过程化的语言,它是用于处理一组记录的,这些记录可以来自于存储在关系数据库中的一个表或多个表。
9
SQL的基本思想是,首先获取满足用户指定的约束条件的一组记录,然后再对这些记录进行某种操作。SQL具有与英语相似的句法。一个SQL语句一般有一个表示要采取的动作的动词,如select、update、delete或insert命令,其中最常用的动作是检索(select)记录。
10
11.2 使 用 ODBC ODBC工作原理 ODBC(Open Database Connectivity,开放数据库互连)是微软公司开放服务结构(WOSA,Windows Open Services Architecture)中有关数据库的一个组成部分,它为关系数据库的应用软件提供了一种统一接口。 基于ODBC的应用程序对数据库的操作不依赖数据库管理系统,所有的数据库操作由对应的ODBC驱动程序完成。不论是FoxPro、Access还是Oracle数据库,均可通过ODBC接口访问。ODBC的最大优点是能以统一的方式处理所有的关系数据库。图11.1是ODBC体系结构示意图。Visual C++6.0提供了各种常见数据库的ODBC驱动程序,在安装时可以选择安装。
11
图11.1 ODBC体系结构示意图
12
应用程序要访问一个数据库,首先必须用ODBC管理器注册一个数据源,管理器根据数据源提供的数据库位置、数据库类型及ODBC驱动程序等信息,建立起ODBC与具体数据库的联系。这样,只要应用程序将数据源名提供给ODBC,ODBC就能建立起与相应数据库的连接。ODBC管理器通过Windows 95控制面板启动,其主要任务是管理安装的ODBC驱动程序和管理数据源。 在ODBC管理器中的“用户DSN”页单击“添加”按钮,在弹出的对话框中选择ODBC驱动程序,然后将出现数据源配置对话框。图11.2是Access数据库配置时的对话框。
13
图11.2 数据源的配置
14
MFC ODBC类简介 MFC的ODBC类对较复杂的ODBC编程接口进行了封装,提供了简化的调用接口,从而大大方便了数据库应用程序的开发。程序员不必了解ODBC接口的细节,利用ODBC类即可完成对数据库的大部分操作。 MFC的ODBC类主要包括: (1) CDatabase类:主要功能是建立与数据源的连接。要建立与数据源的连接,首先应构造一个CDatabase对象,然后再调用CDatabase的Open函数成员。 (2) CRecordset类:该类代表从数据源选择的一组记录(记录集),程序可以选择数据源中的某个表作为一个记录集,也可以通过对表的查询得到记录集,还可以合并同一数据源中多个表的列到一个记录集中。
15
通过该类可对记录集中的记录进行滚动、修改、增加和删除等操作。
在多任务操作系统或网络环境中,多个用户可以共享同一个数据源。共享数据存在的一个问题就是如何协调各个用户对数据源的修改。CRecordset类支持快照(Snapshot) 和动态集(Dynaset)两种记录集类型,它们的不同表现在,对其他用户改变数据源记录采取了不同的处理方法。 快照型记录集提供了对数据的静态视。当别的用户改变了记录时(包括修改、添加和删除),快照中的记录不受影响,它不反映别的用户对数据源记录的改变。
16
直到调用了CRecordset::Requery重新查询后,快照才会反映变化。对于像产生报表或统计计算这样的不希望中途变动的工作,快照是很有用的。需要指出的是,快照的这种静态特性是相对于别的用户而言的,它会正确反映由本身用户对记录的修改和删除,但对于新添加的记录直到调用Requery后才能反映到快照中。 动态集提供了数据的动态视。当别的用户修改或删除了记录集中的记录时,会在动态集中反映出来:当滚动到修改过的记录时,对其所作的修改会立即反映到动态集中;当记 录被删除时,MFC代码会跳过记录集中的删除部分。对于其它用户添加的记录,直到调用Requery时,才会在动态集中反映出来。应用程序本身对记录的修改、添加和删除会反映在动态集中。当数据必须是动态的时侯,使用动态集是最适合的。例如,在一个火车票联网售票系统中,显然应该用动态集随时反映出共享数据的变化。
17
(3) CRecordView类:CRecordView(记录视图)是CFormView的派生类,提供了一个表单视图与某个记录集直接相连,利用对话框数据交换机制(DDX)在记录集与表单视图的控件之间传输数据,用户可以通过表单视图显示当前记录,可以修改、添加和删除数据。该类支持对记录的浏览和更新,在撤消时会自动关闭与之相联系的记录集。用户一般需要创建一个CRecordView的派生类并在其对应的对话框资源中加入控件。 (4) CFieldExchange类:支持记录字段数据交换(DFX),即记录集字段数据成员与相应的数据库的表的字段之间的数据交换。该类的功能与CDataExchange类的对话框数据交换功能类似。 (5) CDBException类:代表ODBC类产生的异常。
18
创建ODBC应用程序 AppWizard和ClassWizard提供了对ODBC的支持,本节首先通过一个地址簿应用程序演示如何创建一个简单的数据库应用。 第一步,首先使用Access 97建立地址簿数据库,在该数据库中创建地址簿表格。然后建立ODBC数据源,名字为AddressBook。 第二步,在Visual C++中使用AppWizard创建单文档界面的应用程序框架dbdemo。在Step 2中选择“Database view without file support”,如图11.3所示。
19
图11.3 AppWizard创建数据库应用程序Step 2 of 6
20
第三步,选择数据源。在图11. 3所示的窗口中单击“Data Source”按钮,弹出窗口如图11
第三步,选择数据源。在图11.3所示的窗口中单击“Data Source”按钮,弹出窗口如图11.4所示。选择ODBC数据源AddressBook。单击“OK”按钮,弹出选择数据表的窗口,如图11.5所示。在该窗口中选择所需的数据表。 第四步,在AppWizard窗口中单击“finish”按钮,AppWizard生成dbdemo程序的框架,并自动打开主窗口资源编辑窗口。如图11.6所示,在该窗口中加入用于查看编辑地址表记录字段内容的文本编辑框,它们的ID分别为:IDC_IDEDIT、IDC_NameEDIT、IDC_AddressEDIT、IDC_ZipEDIT、IDC_TelenumEDIT、IDC_ EDIT。
21
图11.4 选择数据源
22
图11.5 选择数据表
23
图11.6 编辑主窗口资源
24
第五步,启动ClassWizard设置控件与CDbdemoView类数据成员的对应关系,例如IDC_AddressEdit对应m_pSet->m_Address,如图11.7所示。这些数据成员用于从记录集中获得当前记录的对应的字段值。 最后,编译执行dbdemo.exe,程序执行情况如图11.8所示,可以通过“记录”菜单或工具条上的按钮查阅地址表中的记录,也可以对记录内容进行修改。 现在再回过头来看看AppWizard所生成的部分主要代码,看看使用MFC ODBC类的数据库应用程序是如何工作的。 AppWizard为每一个数据表创建一个CRecordset的派生类。自动生成的CDbdemoset类的声明代码如下:
25
图11.7 设置控件与CDbdemoView类数据成员的对应关系
26
图11.8 执行地址簿程序
27
class CDbdemoSet : public CRecordset
{ public: CDbdemoSet(CDatabase* pDatabase = NULL); DECLARE_DYNAMIC(CDbdemoSet) // Field/Param Data //{{AFX_FIELD(CDbdemoSet, CRecordset) long m_ID; CString m_name; CString m_address; CString m_zip; CString m_telenum; CString m_ ;
28
//}}AFX_FIELD // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CDbdemoSet) public: virtual CString GetDefaultConnect( ); // Default connection string virtual CString GetDefaultSQL( ); // default SQL for Recordset virtual void DoFieldExchange(CFieldExchange* pFX);
29
// RFX support //}}AFX_VIRTUAL // Implementation #ifdef _DEBUG virtual void AssertValid( ) const; virtual void Dump(CDumpContext& dc) const; #endif };
30
从第8行开始进行数据映射,AFX_FIELD定义指示这是记录集的字段数据,MFC RFX机制定义了数据表中的字段数据与这些数据成员之间的联系。DoFieldExchange函数用于完成RFX支持,下面的程序片段是上面例子中DoFieldExchange函数的实现代码: void CDbdemoSet::DoFieldExchange(CFieldExchange* pFX) { //{{AFX_FIELD_MAP(CDbdemoSet) pFX->SetFieldType(CFieldExchange::outputColumn); RFX_Long(pFX, _T("[ID]"), m_ID); RFX_Text(pFX, _T("[name]"), m_name); RFX_Text(pFX, _T("[address]"), m_address);
31
RFX_Text(pFX, _T("[zip]"), m_zip);
RFX_Text(pFX, _T("[telenum]"), m_telenum); RFX_Text(pFX, _T("[ ]"), m_ ); //}}AFX_FIELD_MAP } 函数成员GetDefaultConnect 、GetDefaultSQL包含了创建dbdemo过程中选定的ODBC数据库及数据表的信息。这两个函数的实现代码如下,它们在数据表打开的过程中被调用。
32
CString CDbdemoSet::GetDefaultConnect( )
{ return _T("ODBC;DSN=AddressBook"); } CString CDbdemoSet::GetDefaultSQL( ) return _T("[addr]"); AppWizard创建的另一个重要的类是CDbdemoView,由CRecordView类派生而来。CDbdemoView类声明代码如下:
33
class CDbdemoView : public CRecordView
{ protected: // create from serialization only CDbdemoView( ); DECLARE_DYNCREATE(CDbdemoView) public: //{{AFX_DATA(CDbdemoView) enum { IDD = IDD_DBDEMO_FORM }; CDbdemoSet* m_pSet; //}}AFX_DATA
34
// Attributes public: CDbdemoDoc* GetDocument( ); // Operations // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CDbdemoView) virtual CRecordset* OnGetRecordset( ); virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual void DoDataExchange(CDataExchange* pDX);
35
virtual void OnInitialUpdate( );
// called first time after construct virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); //}}AFX_VIRTUAL // Implementation public: virtual ~CDbdemoView( ); #ifdef _DEBUG
36
virtual void AssertValid( ) const;
virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions //{{AFX_MSG(CDbdemoView) // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code ! //}}AFX_MSG DECLARE_MESSAGE_MAP( ) };
37
类CDbdemoView定义了一个指向CDbdemoSet的指针m_pSet,用于从记录集中获得数据。CDbdemoView通过MFC DDX/DDV消息处理机制与CDbdemoSet交换数据。下面是DoDataExchange函数成员的实现代码: void CDbdemoView::DoDataExchange(CDataExchange* pDX) { CRecordView::DoDataExchange(pDX); //{{AFX_DATA_MAP(CDbdemoView) DDX_FieldText(pDX, IDC_AddressEDIT, m_pSet->m_address, m_pSet);
38
DDX_FieldText(pDX, IDC_EmailEDIT, m_pSet->m_email, m_pSet);
DDX_FieldText(pDX, IDC_IDEDIT, m_pSet->m_ID, m_pSet); DDX_FieldText(pDX, IDC_NameEDIT, m_pSet->m_name, m_pSet); DDX_FieldText(pDX, IDC_TelenumEDIT, m_pSet->m_telenum, m_pSet); DDX_FieldText(pDX, IDC_ZipEDIT, m_pSet->m_zip, m_pSet); //}}AFX_DATA_MAP }
39
派生于CDocument类的CDbdemoDoc类包含一个CDbdemoSet类的数据成员m_dbdemoSet,用于将文档—视图结构中的文档对象与记录集对象连接起来。可以通过CDbdemoView的函数成员GetDocument获得指向文档对象的指针,然后通过m_dbdemoSet对象来直接对记录集进行操作。
40
遍历、添加、修改和删除记录 上一小节中使用AppWizard建立了一个简单的数据库应用,对于复杂的数据库应用,还必须通过手工编写对记录集的操作程序代码来完成。本节首先介绍如何遍历记录集中的所有记录。 1.记录集的遍历 CRecordset提供了几个成员函数用来在记录集中滚动,如下所示。 void MoveNext( ); //前进一个记录 void MovePrev( ); //后退一个记录 void MoveFirst( ); //滚动到记录集中的第一个记录
41
void MoveLast( ); //滚动到记录集中的最后一个记录
void SetAbsolutePosition( long nRows ); 最后一个函数用于滚动到由参数nRows指定的绝对位置处。若nRows为负数,则从后往前滚动。例如,当nRows为–1时,函数就滚动到记录集的末尾。注意,该函数不会跳过被删除的记录。 virtual void Move( long nRows, WORD wFetchType = SQL_FETCH_RELATIVE ); 该函数功能强大。wFetchType参数可指定为SQL_FETCH_NEXT、SQL_FETCH_PRIOR、SQL_FETCH_FIRST、SQL_FETCH_LAST和SQL_FETCH_ABSOLUTE,分别完成上面五个函数的功能。
42
如果在建立记录集时选择了CRecordset::skipDeletedRecords选项,那么除了SetAbsolutePosition外,在滚动记录时将跳过被删除的记录,这一点对像FoxPro这样的数据库十分重要。 如果记录集是空的,那么调用上述函数将产生异常。必须保证滚动没有超出记录集的边界。可调用IsEOF和IsBOF进行检测。 BOOL IsEOF( ) const; 如果记录集为空或滚动过了最后一个记录,那么函数返回TRUE,否则返回FALSE。 BOOL IsBOF( ) const; 如果记录集为空或滚动过了第一个记录,那么函数返回TRUE,否则返回FALSE。
43
下面是一个使用IsEOF的例子: while(!m_pSet->IsEOF( )) m_pSet->MoveNext( );
44
2.记录的修改 要修改当前记录,应该按下列步骤进行: 调用Edit函数成员,调用该函数后就进入了编辑模式,程序可以修改域数据成员。如果在一个空的记录集中调用Edit,将产生异常。Edit函数会把当前域数据成员的内容保存在一个缓冲区中,这样做有两个目的:一是可以与域数据成员作比较以判断哪些字段被改变了;二是在必要的时候可以恢复域数据成员原来的值。若再次调用Edit,则将从缓冲区中恢复域数据成员,调用后程序仍处于编辑模式。Edit函数的形式为: virtual void Edit( ); throw( CDBException, CMemoryException ); 这里throw( CDBException, CMemoryException )表示该函数可能抛出CDBException、CMemoryException异常。
45
取消修改退出编辑模式可调用Move(AFX_MOVE_REFRESH)或Move(0),同时该函数会从缓冲区中恢复域数据成员。
确认修改内容应调用Update。Update把变化后的记录写入数据源并结束编辑模式。Update函数的形式为: virtual BOOL Update( ); throw( CDBException );
46
3.添加新记录 要向记录集中添加新的记录,应该按下列步骤进行: 调用AddNew函数成员。调用该函数后就进入了添加模式,该函数把所有的域数据成员都设置成NULL(注意,在数据库术语中,NULL是指没有值,这与C++的NULL是不同的)。与Edit一样,AddNew会把当前域数据成员的内容保存在一个缓冲区中,在必要的时候,程序可以再次调用AddNew,取消添加操作并恢复域数据成员原来的值,调用后程序仍处于添加模式。调用Move(AFX_MOVE_REFRESH)可退出添加模式,同时该函数会从缓冲区中恢复域数据成员。AddNew函数的形式为: virtual void AddNew( ); throw( CDBException );
47
如果记录集是快照,那么在添加一个新的记录后,需要调用Requery重新查询,因为快照无法反映添加操作。
要为上一节中的地址簿程序加入增加新记录的功能,可以按照下面的步骤来进行:首先修改菜单资源,在“记录”菜单增加一个菜单项“新增”,假设ID为IDC_ADDNEW。然后为该菜单项编写响应程序,代码类似于下面的程序片段: void CDbdemoView::OnAddnew( ) { // TODO: Add your command handler code here CDbdemoSet *m_pSet=&GetDocument( )->m_dbdemoSet; m_pSet->AddNew( ); UpdateData(FALSE);
48
4.记录的删除 要删除记录集的当前记录,应按下面两步进行: 调用Delete函数成员,该函数会同时给记录集和数据源中当前记录加上删除标记。在一个空记录集中调用Delete会产生一个异常。Delete函数的形式为: virtual void Delete( ); throw( CDBException ); 若更新失败则函数返回FALSE,且会产生一个异常。 在对记录集进行更改以前,程序也许要调用下列函数来判断记录集是否是可以更改的,因为如果在不能更改的记录集中进行修改、添加或删除将导致异常的产生。 BOOL CanUpdate( ) const; 该函数返回TRUE表明记录是可以修改、添加和删除的。 BOOL CanAppend( ) const; 该函数返回TRUE则表明可以添加记录。
49
* 数据库异常 Visual C++中,MFC ODBC类库当数据库操作发生例外情况时,抛出CDBException异常。CDBException类为CException的派生类。数据库操作的返回码可通过CDBException类的m_nRetCode数据成员获得,各代码的具体含义可参考Visual C++的有关文档。 下面是一个数据库异常处理的例子,通过CDBException的成员m_strError获得简单的错误信息: CRecordSet * CSectionView::OnGetRecordSet( )
50
{ if(m_pSet!=NULL) return m_pSet; m_pSet = new CsectionSet( NULL); try{ m_pSet->Open( ); } catch( CDBException *e){ AfxMessageBox( e->m_strError,MB_ICONEXCLAMATION); Delete m_pSet; M_pSet=NULL;
51
e->delete( ); } return m_pSet;
52
* 记录的筛选和排序 CRecordSet::Open( )和CRecordSet::Requery( )函数成员用于从数据表中查询记录建立有效的记录集。在使用CRecordSet类对象之前,必须首先使用CRecordSet::Open( )函数;使用过CRecordSet::Open( )函数后,再次查询时可以应用CRecordSet::Requery( )函数。 调用CRecordSet::Open( )函数时,如果CRecordSet类对象的m_pDatabase成员变量为一个已经打开的CDatabase对象指针,则使用该数据库对象建立ODBC连接;若m_pDatabase为空指针,则使用缺省的数据源(由GetDefaultConnect( )函数获得)新建一个CDatabase类对象,然后初始化CRecordSet类对象。调用CRecordSet::Open( )函数时也可以直接提供SQL语句,如:
53
DbdemoSet.Open(AFX_DATABASE_USE_DEFAULT, strSQL);
如果没有指定参数,则程序使用缺省的SQL语句,即对在GetDefaultSQL( )函数中指定的SQL语句进行操作。地址簿程序中该函数返回_T("[addr]"),对应的缺省操作是SELECT语句,即: SELECT * FROM addr 在查询过程中,可以利用CRecordSet的成员变量m_strFilter和m_strSort来执行条件查询和结果排序。m_strFilter为过滤字符串,只有符合条件的记录才会出现在结果记录集中;m_strSort为排序字符串,查询结果将按照指定的方式排序。如:
54
Dbdemoset.m_strFilter="name='李明'";
Dbdemoset.m_strSort="zip"; Dbdemoset.Requery( ); 查询地址簿中姓名为“李明”的记录,查询结果按照邮政编码排序。 除了直接赋值给m_strFilter以外,还可以使用参数化。利用参数化可以更直观、更方便地完成条件查询任务。使用参数化的步骤如下: (1) 在记录集类中声明参变量: CString p1; (2) 在构造函数中初始化参变量: p1=_T(""); m_nParams=1; //参变量数量
55
(3) 将参变量与对应字段绑定,在DoFieldExchange函数中增加下面的代码:
pFX->SetFieldType(CFieldExchange::param) RFX_Text(pFX, _T("name"), p1); (4) 完成以上步骤后就可以利用参变量进行条件查询: m_pSet->m_strFilter="name=?"; m_pSet->p1="李明"; m_pSet->Requery( ); 如果有多个参变量的值,则按绑定的顺序替换查询字串中的"?"适配符。
56
如果查询的结果是多条记录,可以用上面介绍的CRecordSet的函数成员Move( )、MoveNext( )、MovePrev( )、MoveFirst( )和MoveLast( )来遍历记录集。
57
* 统计函数使用 在数据库应用程序中,经常会对某个数据表中的数据进行统计、计算字段平均值等操作,SQL语言中在select语句中采用SUM、AVG等函数来获得统计结果。SQL中的统计函数主要有: (1) SUM:字段数值总计。 (2) MIN:字段数值最小值。 (3) MAX:字段数值最大值。 (4) AVG:字段数值的平均值。 (5) COUNT:统计记录的数量。
58
使用这些SQL函数取得有关数据表中记录的统计信息,而不是从数据表中提取记录。通常建立的记录集只包含单个记录,在使用MFC ODBC类库编程时,可以修改记录集的DoFieldExchange函数成员在指定的数据表中进行统计查询操作,通过记录集中的记录获得统计结果。主要步骤如下: (1) 建立要从中取得统计结果的记录集类。方法可参考11.2.8节中记录集类的建立过程。 (2) 修改记录集类的DoFieldExchange函数,用代表字段上统计函数的字符串替换字段名。例如用
59
RFX_Double(pFX, "SUM(Sales)", m_SumSales);
替代 RFX_Double(pFX, "Sales", m_Sales); (3) 打开记录集,统计结果将留在m_SumSales中。 如果需要按照某个字段的值分组进行统计,则可在记录集的m_strFilter中设置。例如: m_strFilter="sales >10 group by salesperson_id" 按照salesperson_id分组统计sales字段值大于10的记录。此时结果记录集将包含多条记录。
60
* 多表的连接 在数据库应用程序中,经常会同时使用多个相关表,例如地址簿程序。在打开地址簿后,查看相关的 收发记录,此时地址簿为主表, 收发记录为从表,二表通过ID字段连接。本节演示如何使用Visual C++进行多表的连接。 首先在地址簿数据库中增加一个 收发记录表,主要字段有ID、rdate(接收日期)、sname(发件人)。打开前面建立的地址簿程序文件,启动ClassWizard,单击“Add Class…”,选择“New”,如图11.9所示,输入类名CErecordSet,基类为CRecordSet。
61
单击“OK”按钮,按照提示选择ODBC数据源地址簿和E-mail收发记录数据表,此过程与11. 2
单击“OK”按钮,按照提示选择ODBC数据源地址簿和 收发记录数据表,此过程与11.2.3节中选择数据源和数据表过程相同。ClassWizard自动建立ErecordSet.h、ErecordSet.h文件,为数据表中的每一个字段在类CErecordSet中建立一个对应的数据成员。 现在修改ErecordSet.h,为类CErecordSet增加一个数据成员如下: long filterid; 该数据成员用于存放地址簿与 收发记录表连接时的ID字段值。下面在类CErecordSet的构造函数中设置过滤条件:
62
filterid=_T(0); m_nParams=1; //参变量数量 m_strFilter="id=?"; 在CErecordSet::DoFieldExchange的实现中加上下面两行,将filterid与过滤条件中的参变量绑定。 pFX->SetFieldType(CFieldExchange::param); RFX_Long(pFX,_T("id"),filterid); 现在建立一个新的对话框资源,此对话框将用于显示 收发记录表中的数据。根据数据库字段在该对话框中建立相应的编辑控件。然后增加四个按钮控件,控件ID分别为:IDC_BUTTON1、IDC_BUTTON2、IDC_BUTTON3、IDC_BUTTON4,按钮标题分别设为Top、Previous、Next、Bottom。
63
图11.9 建立记录集类
64
RFX_Long(pFX,_T("id"),filterid);
现在建立一个新的对话框资源,此对话框将用于显示 收发记录表中的数据。根据数据库字段在该对话框中建立相应的编辑控件。然后增加四个按钮控件,控件ID分别为:IDC_BUTTON1、IDC_BUTTON2、IDC_BUTTON3、IDC_BUTTON4,按钮标题分别设为Top、Previous、Next、Bottom。 启动ClassWizard建立CTestDialog类,基类为CDialog,Dialog ID为上面建立的对话框模板的ID。在ClassWizard窗口的Class Info页中,选择CTestDialog类,设置Foreign Class 为CErecordSet类,在Foreign Variable中填写m_pSet,然后在Member Varibles页设置对话框中的编辑控件与m_pSet成员之间的关系,方法可参考11.2.3节。
65
编写四个按钮控件的响应程序如下: void CTestDialog::OnButton1( ) { // TODO: Add your control notification handler code here m_pSet->MoveFirst( ); UpdateData(FALSE); } void CTestDialog::OnButton2( )
66
m_pSet->MovePrev( );
UpdateData(FALSE); } void CTestDialog::OnButton3( ) { // TODO: Add your control notification handler code here m_pSet->MoveNext( );
67
void CTestDialog::OnButton4( )
{ // TODO: Add your control notification handler code here m_pSet->MoveLast( ); UpdateData(FALSE); } 接下来的主要工作将是如何激活显示 收发记录数据的对话框。可以在原来主菜单的“记录”菜单下增加一项“ 记录”,在该菜单的响应程序中激活该对话框。该菜单响应程序的主要内容如下:
68
CTestDialog mydlg; mydlg.m_pSet = new CErecordSet; mydlg.m_pSet->filterid=m_pSet->m_ID; mydlg.m_pSet->Open(AFX_DB_USE_DEFAULT_TYPE); mydlg.DoModal( ); delete mydlg.m_pSet; 现在可以编译执行该程序了。程序的主窗口与前面的地址簿程序相同,但在浏览某一记录时,可通过“E–mail记录” 菜单项在弹出的窗口中查看 收发记录。 多表连接,实际上是根据主表的某一字段对从表的内容进行过滤。上面的程序在查看 收发记录时,只出现主窗口正在浏览的人员的记录。
69
* 直接使用SQL语言 通过CRecordSet类可以完成大多数的查询操作,而且在CRecordSet::Open( )函数中也可以提供SQL语句。但是,有时候还是希望进行一些其它操作,例如建立新表、删除表、建立新的字段等,这时就需要使用CDatabase类直接执行SQL语句的机制。 通过调用CDatabase::ExecuteSQL( )函数来完成SQL语句的直接执行: BOOL CDB::ExecuteSQLAndReportFailure(const CString& strSQL) {
70
TRY { m_pdb->ExecuteSQL(strSQL); //直接执行SQL语句 } CATCH (CDBException,e) CString strMsg; strMsg.LoadString(IDS_EXECUTE_SQL_FAILED); strMsg+=strSQL; return FALSE;
71
END_CATCH return TRUE; } 应当指出的是, 于不同的RDBMS提供的数据操作语句不尽相同,直接执行SQL语句可能会破坏软件的RDBMS无关性,因此在应用中应当慎用此类操作。
72
*11.3 使 用 DAO DAO概述 DAO是数据访问对象(Database Access Object)的英文缩写,使用Microsoft Jet数据库引擎来访问数据库。Microsoft Jet是Microsoft Access和Visual Basic的数据库引擎。 使用DAO,可以访问如下的数据源: (1) 使用Microsoft Jet数据库引擎用Microsoft Access 或Visual Basic创建的数据库。 (2) 可安装的ISAM数据库,包括:Microsoft Foxpro 2.0、2.5、2.6版本的数据库以及可在3.0版本中输入输出的数据;dBaseⅢ、BaseⅣ及dBase .0数据库;Paradox 3.x、4.x和5.x版本的数据库。
73
(3) ODBC数据源。 (4) Microsoft Excel 3.0、4.0、5.0、7.0版的工作表。 (5) Lotus WKS、WK1、WK3和WK4版的电子数据表。 (6) 文本文件。 Visual C++从4.0版开始引入DAO,由于DAO是基于Microsoft Jet引擎的,因而在访问Access数据库(即*.MDB文件)时具有很好的性能。但如果用户的工作必须严格限于ODBC数据源,尤其是在开发Client/Server结构的应用程序时,则用ODBC有较好的性能。
74
MFC DAO类 Visual C++提供了与ODBC类库功能相似的MFC DAO类。DAO的CDaoDatabase类对应ODBC的CDatabase类,CDaoRecordset对应CRecordset,CDaoRecordView对应CRecordView,CDaoException对应CDBException。这些对应的类功能相似,它们的大部分函数成员都是相同的。 与ODBC相比,DAO提供了一些新类来加强其功能,这些新类包括: (1) DaoTableDef:提供了对数据表结构的操作。 CDaoTableDef::Open可以获得表的结
75
构信息;CDaoTableDef::Create可以创建一张新表;CDaoTableDef:: CreateField可为表添加字段;CDaoTableDef::CreateIndex可以为表添加索引;CDaoTableDef::Append可以把新创建的表保存到数据库中。(2) CDaoQueryDef:表示一个查询定义(Query definition),该定义可以被存储到数据库中。 (3) CDaoWorkspace:数据工作区(Workspace),一个工作区可以包含多个数据库,工作区可以对所属的数据库进行全体或单独的事务处理,工作区也负责数据库的安全性。如果需要,程序可以打开多个工作区。
76
创建DAO应用程序 AppWizard和ClassWizard对使用DAO和ODBC对象的应用程序提供了类似的支持。利用AppWizard和ClassWizard,用户可以方便地开发出基于DAO的数据库应用程序。 由于DAO和ODBC类的许多方面都比较相似,因此只要用户掌握了ODBC,就很容易学会使用DAO,用户可以很轻松地把数据库应用程序从ODBC移植到DAO。 下面使用AppWizard和ClassWizard来建立地址簿程序的DAO版本。
77
首先按照11.2.3节中的方法建立单文档应用程序框架(项目名设为DAODemo)。选取数据源时,在Database Options对话框中选择DAO,而不是ODBC。DAO的数据源是通过选择一个.MDB文件来指定的,即点击“...”按钮后在文件对话框中选择要访问的.MDB文件,记录集的缺省类型是动态集(Dynaset)。 然后按照11.2.3节中的方法编辑主窗口模板,设置控件与记录集数据成员的对应关系即可。 在生成的应用程序中,主要的类有派生于CDaoRecordset类的CDaodemoSet,派生于CDaoRecordView类的CDaodemoView。
78
使用DAO与ODBC方式相比,除选择的数据源不同外,还有:
(1) 参数化的方式不同。DAO记录集的m_strFilter和m_strSort中的参数不是“?”号,而是一个有意义的参数名。例如,在下面的过滤器中有一个名为IDParam的参数: m_pSet->m_strFilter ="ID = IDParam"; 在DoFieldExchange函数中,有下面两行: pFX->SetFieldType(CDaoFieldExchange::param); DFX_Text(pFX, _T("IDParam"), m_strIDParam); DFX函数的第二个参数也是IDParam。这里m_strIDParam为对应的参数。
79
(2) 处理异常的方式不同。DAO数据库操作发生例外情况抛出CDaoException。例如,在删除记录时,对异常的处理如下所示:
try { m_pSet->Delete( ); } catch(CDaoException* e) AfxMessageBox(e->m_pErrorInfo->m_strDescription); e->Delete( );
80
另外,AppWizard和ClassWizard也隐藏了一些细微的不同之处。例如,DAO记录集使用的是DFX数据交换机制(DAO record field exchange),而DoFieldExchange中使用的是DFX函数而不是RFX函数。
Similar presentations