1
第 13章 数据库应用程序的开发
2
13.1有关数据库的基础知识
现有的数据库软件有很多,如大型数据库
Oracle,SQL Server,小数据库 Access
等,都支持关系模型
数据库
模型
层次模型
网状模型
关系模型
面向对象模型
3
13.2 ODBC介绍和引用
4
13.2.1 ODBC简介
MS推出了 Open Database Connectivity,
简称 ODBC。 它包含访问不同数据库所要求的
ODBC驱动程序 。 只要调用 ODBC所支持的函数,
动态链接到不同的驱动程序上即可 。
一个基于 ODBC的应用程序对数据库的操作
不依赖任何 DBMS,不直接与 DBMS打交道, 所
有的数据库操作由对应的 DBMS的 ODBC驱动程
序完成 。 也就是说, 不论是 Oracle,SQL
Server还是 Access数据库, 均可用 ODBC API
进行访问 。 由此可见, ODBC的最大优点是能
以统一的方式处理所有的数据库 。
5
ODBC数据源控制台就是 Windows系统管理
数据源的控制台, 所有的数据库驱动, 以及
数据源登记都要在此发布, 并向系统发出请
求 。
通过使用 ODBC API 和 MFC ODBC 类,可以访
问任何数据资源 。 只要应用程序的用户的终
端机器上有 ODBC的驱动, 都可以访问任何地
方的数据源 。
ODBC是一种接口,它是通过相应的各个
数据库的 ODBC驱动来访问各种数据库中的数
据。使用 ODBC,能够使应用程序独立于数据
库的硬件环境,ODBC提供的 API函数独立于
数据库管理系统。
6
ODBC 是 Microsoft的 Windows系统下的数据库服务的一
部分 。 它是由下面几个部分构成的:
? ODBC API,包含在一个动态库中的函数集合, 一
个错误代码的集合, 一个标准的 SQL语句集合, 用
来调用 DBMS中的数据 。
? ODBC Driver Manager:一个动态库文件 (ODBC32.DLL)
来加载 ODBC驱动, 这个 DLL对你的应用程序是透明
的 。
? ODBC database drivers:由一个或是多个 DLL构成,
其中含有 ODBC API,这些 DLL由其拥有者 DBMS调用 。
? ODBC Cursor Library,这也是一个动态连接库文件 。
? ODBC Administrator,这是一个 ODBC控制台,用来
管理不同的数据源。
7
13.2.2 MFC对 ODBC的封装
8
13.2.3 如何访问数据库
建立 ODBC数据源
连接数据源
选择和处理记录
数据库应用程序中的文档和视图
访问数
据库
9
13.2.4 在数据库应用程序中常
用的几个类
1 CRecordView类
一个 CRecordView对象就是用一个视图
中的控件来显示数据库中的记录。
CRecordView类使用了动态数据交换( DDX)
和数据库交换( RFX),在视图上的控件
和数据源中的数据库中进行数据交换。
AppWizard 生成 CRecordView和 CRecordset类,
并和相应的数据源关联。
10
【 例 13-1】 创建一个数据库应用程序, 可
以显示 Access数据库表中的记录 。
11
步骤:
1,用 AppWizard来生成一个单文档的 ODBC工程文件
12
选择已创建
好的数据库
My_Access_
db.mdb表单
13
用 ClassWizard给相应的 Edit Box连接变量,
注意,在 Add Member Variable 对话框中的
下拉组合框中已经有了相应表中的字段,
只要选中相应的字段就可以了
14
2 CRecordset类
为了能够处理各种的数据库,最好从类
CRecordset派生出一个子类来。数据库从
数据源读取数据后,可以做以下的工作:
?翻阅所有的记录 。
?修改记录, 设定锁定状态 。
?挑选有用的记录 。
?给数据库排序 。
?给定参数, 让数据库在运行的时候自动
选择数据 。
15
3 CDatabase类
CDatabase在 afxdb.h中定义。其对象是用来
连接一个数据源的。
为了使用 CDatabase对象,需调用构造函数,
并调用 OpenEx或是 Open函数,这将会打开一
个连接。
当构造一个 CDatabase类完成后,可以向
CRecordset类的对象传递这个 CDatabase类的
指针。连接数据源结束时,必须用 Close函
数关闭这个对象。
16
4 RFX
RFX (Record Field Exchange)是支持应用程序的
一个交换机制,当从 CRecordset 类派生一个
类,在交换数据的时候没有选择大容量交换
的方式( Bulk RFX)时,RFX机制将在数据交
换中起作用。
RFX 在视图和数据源之间自动交换数据,
由于一次交换的数据可能不止一个,为此可
能要多次调用 DoFieldExchange 函数,同时它
也是应用程序框架和 ODBC交流的媒介。
RFX机制能够安全的通过调用 (例如 ODBC 函
数 SQLBindCol)来保存用户的工作。
17
下面代码就是 【 例 13-1】 工程文件中 AppWizard自动
加入的 RFX代码,见 粗斜体 部分:
void CODBCSet::DoFieldExchange(CFieldExchange* pFX)
{
//{{AFX_FIELD_MAP(CODBCSet)
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Long(pFX,_T("[书籍 ID]"),m___ID);
RFX_Text(pFX,_T("[作者 ]"),m_column1);
RFX_Text(pFX,_T("[出版社 ]"),m_column2);
RFX_Text(pFX,_T("[价格 ]"),m_column3);
//}}AFX_FIELD_MAP
}
18
函数 DoFieldExchange 是 RFX机制的中枢,任何时候应用
框架需要从数据源到数据库或是从数据库到数据源,
都要调用 DoFieldExchange 函数。
下面是 CRecordset派生类的头文件,其中关于 RFX机
制的部分已经用 粗斜体 显示:
class CODBCSet, public CRecordset
{ …
// Field/Param Data
//{{AFX_FIELD(CODBCSet,CRecordset)
long m___ID; CStringm_column1;
CStringm_column2; CStringm_column3;
//}}AFX_FIELD
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CODBCSet)
public:
……
virtual void DoFieldExchange(CFieldExchange* pFX); // RFX support
//}}AFX_VIRTUAL
… };
19
5 CDBException
CDBException是用来处理从其它 ODBC类传
过来的异常情况的。这个类一般是和关键字
CATCH连用的。同样的用户也可以用全局函
数 AfxThrowDBException抛出一个异常情况
m_nRetCode,它包含了一个结构体 RETCODE,里面
包含了 ODBC的错误信息的描述。
m_strError,包含一个描叙异常情况的字符串。
m_strStateNativeOrigin,包含描述异常情况的字符串
m_strStateNativeOrigin;如果变量包含多个错误的描
述,错误会分行显示。
CDBException
类的成员变量
20
【 例 13-2】 在 【 例 13-1】 的基础上增加“删除一
个记录”、“更新记录”和“清除域”三个菜
单项,并实现相应的操作。
响应
COMMAND
命令 响应命令
UPDATE_COMMAND_UI
1 加入菜单项
21
2 重载 OnMove函数
22
BOOL CODBCView::OnMove(UINT nIDMoveCommand)
{switch(nIDMoveCommand)
{ case ID_RECORD_PREV:
m_pSet->MovePrev();
if(!m_pSet->IsBOF())
break; //如果移到数据库的开始,自动执行 MoveFirst函数
case ID_RECORD_FIRST:
m_pSet->MoveFirst(); break;
case ID_RECORD_NEXT:
m_pSet->MoveNext();
if(!m_pSet->IsEOF()) break;
if(!m_pSet->CanScroll())
{ m_pSet->SetFieldNull(NULL); //清空屏幕
break; }
case ID_RECORD_LAST:
m_pSet->MoveLast(); break;
default,ASSERT(FALSE); //异常情况
}
UpdateData(FALSE); //交换数据
return TRUE;
}
23
3 添加菜单响应函数
void CODBCView::OnDeleteRecord() //删除记录
{CRecordsetStatus m_cStatus;
try{ m_pSet->Delete(); }
catch(CDBException* m_pEx)
{ AfxMessageBox(m_pEx->m_strError);
m_pEx->Delete();
m_pSet->MoveFirst(); //若失败,将记录指针移到首记录
UpdateData(FALSE);
return;
}
m_pSet->GetStatus(m_cStatus);
if(m_cStatus.m_lCurrentRecord==0)
m_pSet->MoveFirst(); //删除了最后一个记录
else
m_pSet->MoveNext();
UpdateData(FALSE);
}
24
void CODBCView::OnUpdateDeleteRecord(CCmdUI* pCmdUI)
//删除后的刷新
{ pCmdUI->Enable(!m_pSet->IsEOF()); }
void CODBCView::OnUpdateRecord()
{
m_pSet->Edit();
UpdateData(TRUE);
if(m_pSet->CanUpdate())
m_pSet->Update();
}
25
void CODBCView::OnUpdateUpdateRecord(CCmdUI* pCmdUI)
//刷新记录集
{
pCmdUI->Enable(!m_pSet->IsEOF());
}
void CODBCView::OnClearDomain() //清除域
{
m_pSet->SetFieldNull(NULL);
UpdateData(FALSE);
}
26
【 例 13-3】 在 【 例 13-2】 的基础上增加功能,
使得程序能够向数据库中添加新记录 。
增加一个菜单项“增加
一个新记录”,其 ID标
识为 ID_ADD_RECORD
27
在数据库中增加记录步骤:
? 得到最后一条记录的 ID号
? 将其加 1
? 通过 AddNew函数来添加记录
? 把新的 ID值设置为新增记录中的 ID字段值
? 用 Update函数保存新记录
? 调用 Requery函数更新记录
? 把输入控制滚动到数据库中的最后一条记录上
28
为了计算新的 ID号,需增加 CODBCSet类
的成员函数 GetMaxID
long CODBCSet::GetMaxID()
{ MoveLast(); //移到最后一条记录
return m___ID; //返回该 ID值
}
29
void CODBCView::OnAddRecord()
{CRecordset * pSet=OnGetRecordset();//获取指向数据库的指针
if(pSet->CanUpdate()&&!pSet->IsDeleted())
//确认对数据库的任何修改均已保存
{ pSet->Edit();
if(!UpdateData()) return;
pSet->Update();
}
long m_lNewID=m_pSet->GetMaxID()+1;//获取新的 ID值
m_pSet->AddNew(); //添加一个新记录
m_pSet->m___ID=m_lNewID; //设置新的 ID标识
m_pSet->Update(); //保存新的记录
m_pSet->Requery(); //刷新数据库
m_pSet->MoveLast(); //游标移到最后一条记录
UpdateData(FALSE); //更新表单
}
30
【 例 13-4】 在 【 例 13-3】 的基础上增加浏览记
录的功能和对记录进行排序的功能 。
创建对话框, 通过在对话框中指定记录序
号 ( 记录序号不是记录的 ID号标识 ) 来浏览
该条记录的内容 。 IDD_MOVE_RECORD
IDC_RECORD_ID
31
void CODBCView::OnMoveToRecord()
{CMoveToRecord dlgMoveTo; //创建 CMoveToRecord类的对象实例
if(dlgMoveTo.DoModal()==IDOK)
{ CRecordset *pSet=OnGetRecordset();
//指向数据库记录的指针
if(pSet->CanUpdate() && !pSet->IsDeleted())
{ //所有的修改保存否?
pSet->Edit();
if(!UpdateData()) return;
pSet->Update();
}
pSet->SetAbsolutePosition(dlgMoveTo.m_RecordID);
//设置新的位置
UpdateData(FALSE); //更新表单
} }
32
由于在视图中响应了对话框的操作, 因此, 还
需要在 ODBCView类的实现文件 ODBCView.cpp
中加入定义对话框类的头文件:
#include "MoveToRecord.h"
同样,为了使用工具栏按钮进行快速操作,
在工具栏中定义一个 Move按钮,其 ID与菜单
项“移到第 …条记录”的 ID一致,Move按钮的
ID为 ID_MoveToRecord。
33
由于 CRecordset类的对象或从 CRecordset类继承的对象
都拥有一个 m_strSort成员, 它决定了对记录的排序,
在, 记录, 菜单中增加菜单项, 按价格排序,,
(ID_SORT_PRICE),并为它映射 COMMAND消息处理函
数 OnSortPrice()。
void CODBCView::OnSortPrice()
{ m_pSet->Close(); //关闭数据库
m_pSet->m_strSort=“价格, ; //指定排序字段
m_pSet->Open(); //再次打开数据库
UpdateData(FALSE); //更新已经排序过的记录
}
由于用了 CRecordset类的成员 m_strSort,因此对数据库
记录的排序不用进行太多的代码干预 。
最后在工具栏中增加 Sort工具按钮,实现菜单项“按价
格排序”的功能。
34
【 例 13-5】 在 【 例 13-4】 的基础上增加查询功能
为了编写查找功能的代码,增加菜单项“按
作者查找” (ID_Search),映射的 COMMAND消
息处理函数为 OnSearch()。
假设按“作者”字段进行查询,为菜单项“按
作者查找”所映射的 COMMAND消息处理函数代
码如下:
void CODBCView::OnSearch()
{
DoFilter("作者 ");
}
35
void CODBCView::DoFilter(CString col)
{CSearchDlg dlg;
int result=dlg.DoModal();
if(result==IDOK)
{ CString str=col+"='"+dlg.m_Edit_Search+"'";
//接收查询字符串
m_pSet->Close(); //关闭原来的表单
m_pSet->m_strFilter=str; //将查询条件赋给过滤器
m_pSet->Open(); //打开经过过滤的表单
int recCount=m_pSet->GetRecordCount();
//计算满足条件的记录数
36
if(recCount==0) //如果没有找到相关记录
{ MessageBox(“No matching records.”);
m_pSet->Close(); //关闭表单
m_pSet->m_strFilter; //将过滤结果给过滤器
m_pSet->Open(); //据过滤结果打开表单 (什么都没找到 )
}
UpdateData(FALSE); //不论任何情况, 都更新表单
}
}
由于上述代码都是在 ODBCView.cpp中, 即上述操作是在
视图中完成的, 但查询条件是在, 查询, 对话框中输
入的, 在视图中接收了对话框的输入内容, 因此, 需
要在 ODBCView.cpp中加入如下代码:
#include "SearchDlg.h"