MFC数据库编程

数据库开发技术简介

DAO
ODBC
OLE DB
ADO

数据库开发实例

实例说明和程序框架编程
利用DAO开发本地Access数据库
利用ADO开发本地Access数据库
利用ODBC开发远程服务器端的SQL/MySql数据库

为了能用同样的方法访问不同的数据库,许多大公司提出“通用数据库接口标准”,如微软的ODBC、DAO、RDO、OLEDB、ADO、Borland公司的BDE等等。
这些接口封装了各类数据库驱动,应用程序通过标准接口编程,程序就会根据数据类型调用不同驱动。
微软早期提出的数据库接口为DAO,并且封装到MFC中。目前流行的数据库接口:ODBC、ADO
数据库简介------DAO

数据库开发技术简介——DAO

DAO(Database Access Object,数据访问对象)使用Microsoft Jet引擎来访问数据库,允许开发者直接连接到 Access 表。
DAO 适用于单系统应用程序开发Access数据库。
MFC 的DAO类封装了DAO的大部分功能,适合数据库编程初学者。

数据库开发技术简介——ODBC

ODBC(Open Data Base Connectivity 开放式数据互联),提供访问多种基于SQL 的数据库的接口。
能够以统一的方式去处理所有数据库:将具体的数据库抽象为“数据源”,数据源与ODBC接口之间,通过数据库的ODBC驱动程序连接。
如:在安装SQL Driver for ODBC和SQLite Driver for ODBC后,程序可以通过同样的代码去访问两者。
ODBC连接数据库的示意图

数据库开发技术简介——OLE DB

概念:OLE DB——是微软的战略性的数据源低级程序接口,是一组读写数据的方法。
将传统的数据库系统划分为多个逻辑组件:
Data Providers 数据提供者:即各类数据库
Data Consumers 数据使用者 :即使用数据库的应用程序
Service Components 服务组件 :提供数据库操作

OLE DB 和ODBC 标准都提供统一的数据库接口,但ODBC 标准基于SQL 数据源,而OLE DB 支持任何数据存储。
因此可以说,ODBC标准 是OLE DB 标准子集。

数据库开发技术简介——ADO

ADO,(ActiveX Data Objects),是一种特殊的OLE DB接口,以OLEDB为基础而封装的,它提供了编程语言和OLE DB的一个中间层。

ADO 是对当前微软所支持的数据库进行操作的最有效和最简单直接的方法,也是微软大力推荐使用的一种接口。

实例说明和程序框架

建立一个学生信息管理系统,
具有添加、删除、修改、查找功能
查找具有多种方式

框架:采用SDI结构,主视图为FormView,数据库采用Access2000格式(*.mdb)(源代码下载)
分别用DAO和ADO两种接口实现编程,数据库结构参考W13_StudentInfo.mdb


利用DAO开发Access数据库
MFC封装的DAO类
MFC通过封装了一套DAO类来实现DAO的功能,在afxdao.h头文件中,包括:
CDaoWorkspace类
CDaoDatabase类
CDaoRecordset类
前三个类是重点
使用DAO,首先要在stdafx.h中添加引用afxdao.h头文件

CDaoTableDef类
CDaoQueryDef类
CDaoException类

(1) CDaoWorkspace类——DAO工作区类

工作区是数据库处理事务管理器,它管理在同一个“事务”区里的一系列打开的数据库。
CDaoWorkspace类在MFC DAO体系结构中处于最顶层,负责管理从登录到断开连接过程中的所有数据库会话的全过程。
CDaoWorkspace类从CObject类里派生而来。

(2) CDaoDatabase类——数据库对象

代表一个数据库连接,通过它程序可操作数据库中的数据。
每个CDaoDatabase对象都拥有自己的表定义、查询定义、记录集和关系对象的集合,并提供操作方法。
重要的两个操作函数:Open和Create,分别打开一个数据库,和创建一个数据库。
CDaoWorkspace和CDaoDatabase的关系:在定义CDaoDatabase对象时,如果在构造函数中没有指定工作区指针,程序会自动创建一个“默认工作区”。

(3)CDaoRecordset类——数据记录集对象

对DAO记录集对象的封装,它代表从数据源中选择的一组记录。
CDaoRecordset可分成3种类型:
表记录集(dbOpenTable):代表一个数据库中的表,通过它可以从单个数据库的表中检索、添加、删除和修改记录。
动态记录集(dbOpenDynaset):是对数据库执行查询操作的结果,它可能包含一个或者多个数据库的所有或者某些特定的字段。
快照记录集(dbOpenSnapshot):是一组记录的静态拷贝通过快照记录集可以查找数据或者生成报表,但是不能对其中的记录进行操作。

CDaoTableDef类——表定义类,用来对数据库中的表进行操作。需掌握打开一个表的方法
CDaoQueryDef类——记录查询定义对象
CDaoException类——处理异常的类
CDaoFieldInfo类——获取表的结构信息

DAO编程准备工作

Step1:为所有相关控件添加变量、按钮响应函数,初始化控件,包括性别、班级、List显示。(略)
Step2:预备工作
在stdafx.h中,包含afxdao.h头文件;
在View类中添加CDaoDatabase和CDaoRecordset 对象指针。添加数据库文件名和当前表名称变量。
在MainFrm.cpp里的OnCreate函数中,添加如下语句:
AfxGetModuleState()->m_dwVersion = 0x0601;
// 更新mfc为6.01版本
Step3:在view类中重载菜单“打开”按钮,调用“打开文件”通用对话框,选择数据库文件
Step4.1:打开数据库文件
 
//step4.1打开数据库,创建记录集合对象
 m_pDatabase = new CDaoDatabase;
 try{
  m_pDatabase->Open(m_DBFileName);
  m_pRecordset = new CDaoRecordset(m_pDatabase);
  m_List.EnableWindow(true);
 }
 catch (CDaoException* e){
  e->ReportError();
  delete m_pDatabase;
  m_pDatabase = NULL;
  e->Delete();
  return;
 }
Step4.2:读取Student_Info表结构显示在CListCtrl控件中。
 //step4.2 读取数据库中的“Student_Info”表的结构
    //写入FormView中的CListCtrl控件中。
 m_strTableName = _T("Student_Info"); // 设置当前操作的数据库表名称
 int nFields;
 CDaoFieldInfo fieldInfo;   // 取表的结构信息
 CDaoTableDef td(m_pDatabase);  // 表对象
 try{
  td.Open(m_strTableName);  //打开指定名称的表
  nFields = td.GetFieldCount(); //获取该表的栏数
  for (int j=0; j < nFields; j++){
   td.GetFieldInfo(j,fieldInfo); //获取每个栏的信息
   int nWidth = m_List.GetStringWidth(fieldInfo.m_strName) + 15;
   m_List.InsertColumn(j,fieldInfo.m_strName,LVCFMT_LEFT, nWidth);
  }
 }catch (CDaoException* e){
  e->ReportError(); 
  e->Delete();
  return false;
 }
 td.Close();
Step4.3:读取数据并显示
 //step 4.3 读取表的数据,写入FormView中的CListCtrl控件中。
 int nItem = 0;
 try{
  //拼一个SQL查询语句:select * from [Student_Info]
  CString strSelect(_T("Select * From ["));
  strSelect += m_strTableName;
  strSelect += _T("]");   
  //指定m_pRecordset类型为动态类型,同时查询
  m_pRecordset->Open(dbOpenDynaset,strSelect);
  
  while (!m_pRecordset->IsEOF()) {
   COleVariant var;
   var = m_pRecordset->GetFieldValue(0); //第0列
   m_List.InsertItem(nItem,strVARIANT(var));
   for (int i=0; i < nFields; i++){
    var = m_pRecordset->GetFieldValue(i);
    CString strItem = strVARIANT(var);
    int width1 = m_List.GetColumnWidth(i);
    int width2 = m_List.GetStringWidth(strItem)+15;
    if (width2 > width1){
     m_List.SetColumnWidth(i,width2);
    }
    m_List.SetItemText( nItem,i,strItem);
   }
   nItem++;
   m_pRecordset->MoveNext();
  }
 }
 catch (CDaoException* e)
 {
  e->ReportError(); 
  e->Delete();
  return false;
 }
由于显示数据库内容在程序其他地方也要用,因此单独做成函数OnUpdateView,详见代码.
操作数据库的一般方法:用CString拼一个SQL语句,然后调用程序去执行该语句,解析返回的结果,显示结果。

DAO编程-----添加数据

Step5.1:编写OnAddButton函数,核心就是拼一个SQL语句,如下
void CW13_1View::OnAddButton() 
{
 //检查数据是否完整
 UpdateData(true);
 if (m_strName1.GetLength()<2 || m_strNum1.GetLength()<1)
 { MessageBox("必须填写姓名和学号");
  return;
 }
 CString strGender,strClass;
 m_Combo1.GetWindowText(strGender);
 m_Combo2.GetWindowText(strClass);
 //数据准备完毕,开始写入数据
 m_strTableName = "Sdudent_Info";
 if(!m_pDatabase->IsOpen()) return;          // 测试DAO数据库对象的有效性
 if(!m_pRecordset) return;
 if(m_pRecordset->IsOpen()) m_pRecordset->Close();
 // 拼一个SQL语句
 CString strSql;
 strSql.Format("insert into Student_Info(学号,姓名,性别,班级,EMail) \
      values('%s','%s','%s','%s','%s')",\
    m_strNum1,m_strName1,strGender,strClass,m_strEmail);  
 try{
  if(m_pDatabase->CanUpdate())
   m_pDatabase->Execute(strSql, dbDenyWrite|dbConsistent);
 }
 catch(CDaoException* e){
  e->ReportError();
  e->Delete();
  return;
 }

 //写入完成,更新view
 UpdateView();
}
在添加数据后,调用 UpdateView();完成数据库更新。
这里注意两个bug:
程序并没有实现“学号重复”的判断。
程序每次添加数据都要重新读取数据库里所有内容,当数据量很大时,显然不现实,还需要改进,比如,通过添加翻页机制,将刷新范围局限在一页内。

DAO编程------编辑数据

Step6: 编辑数据不再给出代码,只给出一个思路:
添加ListView的双击事件,将对应条目数据推入控件显示,并将Add按钮名称改为“编辑”。
记录要修改的条目的索引,即“ID”, 修改控件中的数据,完成后点击按钮,编写SQL语句,根据记录的ID,将控件数据“更新”而非“添加”到数据库
最后更新界面。
DAO编程----删除数据
在ListCtrl中按下delete按键后,删除对应的条目
Step7.1: 在View类中重载PreTranslateMessage,实现对键盘事件的响应
BOOL CW13_1View::PreTranslateMessage(MSG* pMsg) 
{
 CWnd  *pwnd = GetDlgItem(IDC_LIST1); 
 if( pMsg->message == WM_KEYDOWN )  
    {   switch( pMsg->wParam ){  
  case VK_DELETE:
   if(GetFocus() == pwnd){ //判断焦点在不在IDC_LIST1上 
    DeleteItem();//将删除代码放在这里,做成一个函数
   }
   break;
  default:
   break;
  }
 }  
 return CFormView::PreTranslateMessage(pMsg);
}
Step7.2: 编写DeleteItem函数,实现删除数据
bool CW13_1View::DeleteItem()
{ m_strTableName = "Student_Info";
 if(!m_pDatabase->IsOpen()) return false;
 if(!m_pRecordset) return false;
 if(m_pRecordset->IsOpen()) m_pRecordset->Close();
 //获取当前选择的项的索引
 int nSelect = m_List.GetSelectedCount(); 
 if (nSelect > 0) {
  CString strID; // 获取ID,根据ID拼删除SQL命令
  strID = m_List.GetItemText(nSelect, 0);
  CString strSQL;
  strSQL.Format("delete from %s where ID=%s",
   m_strTableName, strID);
  
  try{
   m_pDatabase->Execute(strSQL);
  }
  catch (CDaoException* e) {
   e->ReportError(); 
   e->Delete();
   return false;
  }
  m_List.DeleteItem(nSelect);
  UpdateData(false);
 }

 return true;
}

DAO编程----查找数据

查找数据的核心,是建立一个SQL查询语句,根据查询结果建立一个记录集,并将记录集中的检索结果显示在视图里。
首先看精确查找
精确查找允许按照姓名或者学号进行匹配,两者有一即可。
Step8.1 查找代码,根据不同情况拼SQL语句
void CW13_1View::OnFind1Button() 
{
 //精确查找代码
 if(!m_pDatabase->IsOpen()) return;
 if(!m_pRecordset)     return;
 if(m_pRecordset->IsOpen()) m_pRecordset->Close();

 UpdateData(true);
 CString strSelect;
 if (m_strName2.GetLength()==0 && m_strNum2.GetLength()==0)
 { MessageBox("请补充数据");
  return;
 }

 m_List.DeleteAllItems();

 if (m_strName2.GetLength()==0){ 
  strSelect.Format("SELECT * FROM Student_Info WHERE 学号 like '%s'", m_strNum2);
 }else if (m_strNum2.GetLength()==0){
  strSelect.Format("SELECT * FROM Student_Info WHERE 姓名 like '%s'", m_strName2);
 }else{
  strSelect.Format("SELECT * FROM Student_Info WHERE 姓名 like '%s' AND 学号 like '%s'",
   m_strName2,m_strNum2);
 }

 int nItem = 0;
 try{   // 取记录集的数据
  m_pRecordset->Open(dbOpenDynaset,strSelect);
 
  while (!m_pRecordset->IsEOF()) {
   COleVariant var;
   var = m_pRecordset->GetFieldValue(0);
   m_List.InsertItem(nItem,strVARIANT(var));
   for (int i=0; i < 5; i++){
    var = m_pRecordset->GetFieldValue(i);
    m_List.SetItemText( nItem,i,strVARIANT(var));
   }
   nItem++;
   m_pRecordset->MoveNext();
  }
 }
 catch (CDaoException* e){
  e->ReportError(); 
  e->Delete();
  return;
 }
}
模糊查找
原理与精确查找一样,只是稍微复杂一点:
要查找所有列,一旦有类似的,就显示出来。

小结

利用DAO编程,基本思路非常清晰:拼写SQL语句,调用execute方法执行该语句,分析结果并且显示。
类似,所有数据库编程接口,都采用该方式设计。
下篇给大家介绍ADO和ODBC的编程方法

解决W13_1中的遗留问题,包括:
向程序中添加“学号重复”的判断,如果学号重复,则更新而非添加。
实现模糊查询,输入字符串,显示出所有匹配的字符。如输入abey,则显示所有叫abey的学生的信息,以及邮件中出现abey字符的项。
该程序仍然不完善,尽可能修改其中的逻辑bug

Related Articles

Quote Of The Day