VC6数据库综合开发资料

来源:互联网 发布:2018中国经济知乎 编辑:程序博客网 时间:2024/06/05 00:49

VC6数据库综合开发资料

 

        目录

 

VC++6.0开发多表联接的数据库应用程序

《使用OLEDB编写数据库应用程序》

《在Visual C++中用ADO进行数据库编程》

《用Visual C++开发数据库应用程序》

Visual C++ 中 的ODBC 编 程

利用ODBC管理数据库

ODBC 中 应 用DDX 和RFX

MFC 中ODBC 类 库 简 介

RFX 简 介

DDX 简 介

DDX 和RFX 的 关 系 及 比 较

VC++中建立自定义数据库类

VC中使用ADO开发数据库应用程序

数据库中的ODBC编程(1)

数据库中的ODBC编程(2)

数据库中的ODBC编程(3)

Visual C++ 中的 ODBC 编程

ODBC中的同步与异步执行模式

从数据库中读大于32k的内容?

DAO的密码?

ODBC许可问题

使用了CRecordset类

VC 开 发 基 于ORACLE 数 据 库 应 用 程 序 的 两 种 方 法

基于MFC的大型数据文件处理方法

利用ODBC管理数据库

一种专家数据库的开发与实现

利用VC++获取异构型数据库库结构信息

Visual C++中的ODBC编程实例

calling stored procedures

classes for direct sql calls with odbc

sample database program using dialog interface

VC中使用ADO开发数据库应用程序

ISAPI和DAO实现Access数据库Web通用查询

VC++5.0下CGridCtrl类的建立与应用

ADO进行数据库编程

Visual C++开发数据库应用程序

一个优秀的网格控件CGridCtrl

 

 

 

 

用VC++6.0开发多表联接的数据库应用程序

摘要 本文介绍了用Visual C++6.0开发数据库应用程序时,使用MFC ODBC类的编程方法,详细说明了在MFC ODBC的派生类中设置SQL语句参数的方法,实现了二个表的关联。 
关键字 数据库 ,多表联接,MFC ODBC 

1.引言 
  开发Windows应用程序时,在很多情况下可能要和数据库连接。数据库类型多种多样,功能结构也各不相同。从比较简单的DBASE、FoxPro等到复杂的SYBASE、Qracle等大型数据库系统。VC++6.0都提供了一些接口。程序员可利用这些接口方便地开发数据库应用程序。MFC ODBC类就是其中的一个,在快速生成简单一致的接口应用程序方面这些类非常有用。用户不必了解ODBC API和SQL的具体细节,利用ODBC类即可完成对数据库的大部分操作。然而,VC++ Appwizard 生成的数据库应用程序,只是基于单个数据表的数据库应用程序。而实际应用中,往往要求数据库应用程序能关联二个或多个数据表。VC++的好多书籍对此只是简单的介绍。本文透彻地解决这一问题。 

2.ODBC与MFC 
2.1 ODBC 
  ODBC(开放数据库互连)应用程序可通过ODBCAPI访问不同数据源中的数据,每个不同的数据源类型由一个ODBC驱动程序支持,这个驱动程序完成了ODBC API程序的核心,并与具体的数据库通信。ODBC环境提供了驱动程序管理器(Driver Manager),管理那些与不同数据源连接的驱动程序在ODBC32.DLL中执行。应用程序只需要与驱动程序管理器连接,驱动程序管理器就会根据应用程序提供的数据源名,选择正确的驱动程序来访问数据源。 

  要使用ODBC来开发数据库应用程序,必须使用在控制面板处的ODBC数据源管理器,来建立、配制数据源。应本例应用程序需要,按以下步骤建立所需的数据源。 
1. 双击控制面板处的32位的ODBC程序,选择对话框中的User DSN(用户数据源名)选项卡。 
2. 单击Add按钮,然后选择一个数据源:Microsoft Visual FoxPro Driver。单击“完成”,进入下一步设置。 
3. 在Data Source Name域内输入数据源名:DB-FSB。然后选择Visual FoxPro数据库的位置。 
4. 单击OK按钮,返回到控制面板。 

2.2 MFC ODBC 
  MFC的数据库扩展部分封装了使用ODBC数据资源的细节,提供了VC++与ODBC间一种简单的调用接口。MFC的ODBC类主要包括:用来与一个数据源相连的CDatabase类;用来处理从数据库返回的一组记录集的CRecordset类;简化从Crecordset对象中得到数据的显示的CRecordView类。 

  虽然Cdatabase类允许你对一个数据库执行SQL语句,但是CRecordset类提供了应用程序与数据交互的实质。本例应用程序使用CRecordset类来操作数据源. 
CRecordset类的主要目的是让应用程序访问从数据库中返回的结果集。在应用程序中要使用CRecordset类,可根据数据源并使用VC++中的ClassWizard来创建Crecordset派生类。通常,一个CRecordset派生类对应用户数据源中的一个表。每生成一个Crecordset派生类,就要选择一个数据源和一个数据源中的一个表。若生成一个Crecordset派生类时,选择了一个数据源中的多个表,那么Crecordset派生类中的结果集是多个表的卡氏积(迪卡尔积)连接,显然,在实际应用中没什么意义。应用程序通过派生出的Crecordset类可对记录集中的记录进行滚动、修改、增加和删除等操作。 

  CRecordView类具有几个增强功能,允许使用对话框方式(DoDataExchange()函数)直接从记录集显示数据,使得从记录集中显示数据更为容易。并提供了记录移动等操作。 

3. 多表联接的数据库应用程序 
3. 1本例程序功能: 
通过FSB表的BZM字段及DBK1表的HH字段,将Visual FoxPro 的 FSB表与DBK1表(结构如下)关联起来。程序运行出现界面如图1。用鼠标点击工具条的?、?、(、(则FSB表记录移动而DBK1表的记录没移动。用鼠标点击“关联”按钮,则DBK1表的记录和FSB表记录同步移动(BZM编辑框内容与HH编辑框内容相同)。如果,要求按照具体的关键字值来查询二个表中的相关记录,那么,在“定位” 编辑框中输入具体的关键字值,然后,用鼠标点击“关联”按钮,就会见到二个表在新的记录集实现关联。 

表1: FSB表结构 
字段名 
类型 
备注 
BZM 

索引关键字 
DGDL1 


DGZD1 


其它字段 



表2: DBK1表 
字段名 
类型 
备注 
HH 

索引关键字 
BL 


ZZCM 


其它字段 

  基于MFC ODBC类开发的数据库应用程序,是通过MFC ODBC类使用SQL语句方式操纵数据表的。数据库中表FSB与表DBK1关联查询的SQL语句是: 
SELECT * FROM FSB,DBK1 WHERE FSB.BZM=DBK1.HH 
由于创建一个CRecordset派生类时,一般只选择一个数据源中的一个表,因此基于MFC ODBC类开发的数据库应用程序要实现二个表关联,就要使用CRecordset类的参数m _strFilter。它相当于SQL语句中的WHERE子句。参数m _strSort相当于SQL语句中的GROUP BY子句。要注意m_strFilter字符串中不要包含“WHERE”关键字。本例在表FSB与表DBK1对应的CRecordset派生类中分别使用了mbzm和mhh二个m _strFilter参数。用鼠标点击“关联”按钮时,程序首先根据“定位” 编辑框中的内容作为mbzm的值,在表FSB检索结果集。表DBK1对应的CRecordset派生类根据表FSB对应的CRecordset派生类的当前记录m_bzm值,作为mhh的值实行检索,从而得到与表FSB关键字段BZM对应的表DBK1的记录。实现了表FSB与表DBK1的关联。由此可见,二表关联的关键是m _strFilter参数的设置。 

图 1 

3.2 数据库应用程序创建 
3.2. 1 创建单表单文档的数据库应用程序 
根据前面建立的数据源DB-FSB,使用VC++ Appwizard 生成一个单表单、单文档的数据库应用程序。选择数据源DB-FSB的数据表时应选择FSB.DBF。应用程序名为ZF0001(具体步骤可参考有关VC++资料)。ZF0001应用程序中创建了CZf0001Doc、CZf0001Set、CZf0001View等派生类。 
3.2.2 设置m _strFilter参数 
在上一步生成的CZf0001Set类中,按以下方式,在① ② ③程序中设置m _strFilter参数(黑体部分的语句都是为CZf0001Set的参数mbzm而手动增加的)。为节省篇幅,省略程序清单的部分内容。 
①. 在Crecordset派生类的定义中,描述了被连接的数据源表的字段,并在VC++ Appwizard 生成的程序注释“// Field/Param Data ”中提示在此可定义参数。 
CRecordset派生类:CZf0001Set的定义 
class CZf0001Set : public Crecordset //Crecordset派生类CZf0001Set 

public: 
CZf0001Set(CDatabase* pDatabase = NULL); 
DECLARE_DYNAMIC(CZf0001Set) 
// Field/Param Data 
//{{AFX_FIELD(CZf0001Set, CRecordset) //被绑定的字段 
CString m_bzm; 
CString m_dgqd1; 
、、、、、、 //为节省篇幅,省略部分字段 
CString m_bz; 
//}}AFX_FIELD 
CString mbzm; // 参数mbzm 
// Overrides 
// ClassWizard generated virtual function overrides 
、、、、、、、 
virtual void Dump(CDumpContext& dc) const; 
#endif 
}; 
②.Crecordset派生类:CZf0001Set的构造函数 
其中,对被绑定字段的相应内存变量进行了初始化。 
CZf0001Set::CZf0001Set(CDatabase* pdb) : CRecordset(pdb) 

//{{AFX_FIELD_INIT(CZf0001Set) 
m_bzm = _T(""); 
m_dgqd1 = _T(""); 
、、、、、、 
m_bz = _T(""); 
m_nFields = 16; //数据源表的记录字段个数 
//}}AFX_FIELD_INIT 
m_nDefaultType = snapshot; 
m_nParams=1; // CZf0001Set的参数个数 
mbzm=""; //参数初始化 

③.记录字段交换(RFX) 
通过使用RFX,MFC框架可以在数据库和CRecordset类变量之间交换。交换是通过执行DoFieldExchange()函数而建立的。 
void CZf0001Set::DoFieldExchange(CFieldExchange* pFX) 

//{{AFX_FIELD_MAP(CZf0001Set) 
pFX->SetFieldType(CFieldExchange::outputColumn); 
RFX_Text(pFX, _T("[bzm]"), m_bzm); 
RFX_Text(pFX, _T("[dgqd1]"), m_dgqd1); 
、、、、、、; 
RFX_Text(pFX, _T("[dgdl2]"), m_dgdl2); 
RFX_Text(pFX, _T("[bz]"), m_bz); 
//}}AFX_FIELD_MAP 
pFX->SetFieldType(CFieldExchange::param); 
//把字段类型设为CFieldExchange::param 
RFX_Text(pFX,"mbzm",mbzm); //为参数设置RFX 宏,如果有多个参数,必须按SQL的语句中的位置标志符的顺序设置,RFX 宏中的参数的名字如"mbzm",并非用来与参数匹配,可以自己定义。 

3.2.3 增加第二个表,并设置第二个表的参数 
在3。2。1创建的数据库应用程序基础上,进入ClassWizard,点击Add Class...按钮并在弹出的菜单中选择New...,然后在Create New Class对话框中的Name栏中输入CZf1001,在Base class栏中选择CRecordset,按Create按钮。 
在弹出的Database Options对话框中,在ODBC组合框里选择DB-FSB数据源。然后按OK按钮。在弹出的Select Database Tables对话框中选择DBK1表。按OK确认。并在所有存在 #include "CZf0001Set.h" 的文件中,都加入#include "CZf1001.h" 。这样就创建了与DBK1表对应的Crecordset派生类。 
在第一步创建的CZf0001Doc类中,增加一个CZf1001 对象的指针变量m_zf1002(即:CZf1001* m_zf1002)。 
按3.2.2介绍的CZf0001Set类m _strFilter参数的设置方法,在CZf1001类中,设置参数mhh。 
3. 3 参数mhh及参数mbzm在CrecordView的派生类CZf0001View中的使用 
3.3.1 参数在CZf0001View::OnInitialUpdate()函数使用 
在CZf0001View::OnInitialUpdate()函数的开头部分,调用CZf0001View:: GetDocument()从文档类CZf0001Doc类中,返回二个CrecordSet类(CZf0001Set、CZf1001)的指针。根据返回的指针,设置m _strFilter (相当于SQL语句的WHERE子句),并确定二个参数的初始值。这里要说明一点: 
m_pSet->m_strFilter="BZM like ?"; 
m_pSet2->m_strFilter="hh like ?"; 
语句中的“?”,在调用Open或Requery时,“?"将分别自动地被CZf0001Set::mbzm和 CZf1001::mhh的值取代。例如,指定mbzm为“31001",则m_pSet->m_strFilter将变成"BZM =31001"。这样用户只要指定了mbzm,就可以得到所需要的记录集。CZf0001View::OnInitialUpdate()的程序清单如下(黑体部分的语句是手工增加的): 
void CZf0001View::OnInitialUpdate() 
{ m_pSet = &GetDocument()->m_zf0001Set; 
m_pSet2=&GetDocument()->m_zf1002; 
if(!m_pSet2->Open()) 
return; 
m_pSet->m_strFilter="BZM like ?"; 
m_pSet->mbzm= "%"; //初始选择所有记录 
m_pSet->m_strSort=""; 
m_pSet2->m_strFilter="hh like ?"; 
m_pSet2->mhh=m_pSet->m_bzm; //将表FSB对应的CRecordset派生类的m_bzm的值,作为参数mhh的值  
m_pSet2->m_strSort="";  //检索的结果不进行排序 
m_pSet->m_pDatabase= m_pSet2->m_pDatabase; //共享CDatabase 

CRecordView::OnInitialUpdate(); 
GetParentFrame()->RecalcLayout(); 
ResizeParentToFit(); 

3.3.2 在对话框中加入编辑框 
在资源视图Dialog的IDD_ZF0001_FORM表单中,加入用户需要的编辑框。用ClassWizard在第一个表FSB中选择有关字段与它们相连。但是.使用ClassWizard无法找到第二个表DBK1字段变量,因此,对于计划与第二个表DBK1字段相连的编辑框,必须用手工修改CRecordView类的DoDataExchange()(对话框数据交换函数)。 在DoDataExchange()函数 “//}}AFX_DATA_MAP” 后面加入有关内容。见下面程序的黑体部分。如果黑体部分语句加在“//}}AFX_DATA_MAP”的前面,那么,要再次修改IDD_ZF0001_FORM表单时,就无法使用ClassWizard. 
void CZf0001View::DoDataExchange(CDataExchange* pDX) 

CRecordView::DoDataExchange(pDX); 
//{{AFX_DATA_MAP(CZf0001View) 
DDX_Control(pDX, IDC_COMBO1, m_comb); 
DDX_Control(pDX, IDC_EDIT4, m_SS); 
DDX_FieldText(pDX, IDC_EDIT2, m_pSet->m_bl1, m_pSet); 
DDX_FieldText(pDX, IDC_EDIT3, m_pSet->m_dgdl1, m_pSet); 
DDX_FieldCBString(pDX, IDC_COMBO1, m_pSet->m_bzm, m_pSet); 
DDX_FieldText(pDX, IDC_EDIT5, m_pSet->m_dgqd1, m_pSet); 
//}}AFX_DATA_MAP 
DDX_FieldText(pDX, IDC_EDIT1, m_pSet2->m_bl, m_pSet2); 
DDX_FieldText(pDX, IDC_EDIT6, m_pSet2->m_hh, m_pSet2); 
DDX_FieldText(pDX, IDC_EDIT7, m_pSet2->m_zzcm, m_pSet2); 

3.3.3 在对话框中加入一个按钮 
为演示二个表关联的效果,在对话框中加入一个“关联”按钮和一个输入参数用的"定位"编辑框。并给此按钮增加单击事件代码如下: 

void CZf0001View::OnButton1() 

// TODO: Add your control notification handler code here 
char ll[11]; 
int nn=0; 
m_SS.GetLine(0,ll); //读“定位”编辑框中的内容 
nn=m_SS.LineLength(0); //读“定位”编辑框中的字符内容的长度 
if(nn) //“定位”编辑框中有输入内容,则按内容检索形成新的记录集 
{ m_pSet->mbzm=(CString)ll; 
m_pSet->mbzm=m_pSet->mbzm.Left(nn)+"%"; //“%”由SQL语法规定代表任意长度(长度可以为0)的字符串 
m_pSet->Requery(); //在表FSB中重新检索 
if(m_pSet->IsEOF()) 
{ MessageBox("检索结果为空!"); 
UpdateData(FALSE); 
m_pSet->mbzm="%"; 
m_pSet->Requery(); 

m_pSet2->Requery(); //以表1当前记录BZM值,在表2:DBK1中检索 
UpdateData(FALSE); //刷新表单内容 
m_SS.SetSel(0,-1); 
m_SS.ReplaceSel(""); //清除“定位”编辑框中的内容,使下一次单击“关联”按钮时,不会重复检索(即使nn=0) 

else //“定位”编辑框为空,则按表1现有的结果集与表2:DBK1关联 

m_pSet2->mhh=m_pSet->m_bzm; //将表1当前记录BZM值,作为表DBK1的查找参数 
m_pSet2->Requery(); 
UpdateData(FALSE); 
m_pSet->MoveNext(); 


4. 小结 
通过本例可知,虽然VC++提供了Appwizard和Classwizard, 为编程者节省了大量的工作,但是想编出优秀的VC++应用程序,仅凭这些是远远不够的;必须熟悉MFC类库、ActiveX控件等知识。本文作为一个例子介绍了二个表的联接方法。在实际的应用中,读者可以举一翻三,编制更加完善的数据库应用程序。 

参考文献 
1. 康博创作室等编著. Visual C++6.0 高级开发教程. 人民邮电出版社 
2. 齐舒创作室 编著. Visual C++6.0编程技巧与实例分析. 中国水利水 电出版社 
3. 官章全 刘加明编著. Visual C++6.0 类库大全. 电子工业出版社 

  摘自《软件世界》 

 

《使用OLEDB编写数据库应用程序》

日期:2001.11.10 浏览:7

 

使用OLE DB
一、概述
OLE DB的存在为用户提供了一种统一的方法来访问所有不同种类的数据源。OLE DB可以在不同的数据源中进行转换。利用OLE DB,客户端的开发人员在进行数据访问时只需把精力集中在很少的一些细节上,而不必弄懂大量不同数据库的访问协议。
OLE DB是一套通过COM接口访问数据的ActiveX接口。这个OLE DB接口相当通用,足以提供一种访问数据的统一手段,而不管存储数据所使用的方法如何。同时,OLE DB还允许开发人员继续利用基础数据库技术的优点,而不必为了利用这些优点而把数据移出来。

二、使用ATL使用OLE DB数据使用程序
由于直接使用OLE DB的对象和接口设计数据库应用程序需要书写大量的代码。为了简化程序设计,Visual C++提供了ATL模板用于设计OLE DB数据应用程序和数据提供程序。
利用ATL模板可以很容易地将OLE DB与MFC结合起来,使数据库的参数查询等复杂的编程得到简化。MFC提供的数据库类使OLE DB的编程更具有面向对象的特性。Viual C++所提供用于OLE DB的ATL模板可分为数据提供程序的模板和数据使用程序的模板。
使用ATL模板创建数据应用程序一般有以下几步骤:
1、 创建应用框架
2、 加入ATL产生的模板类
3、 在应用中使用产生的数据访问对象

三、不用ATL使用OLE DB数据使用程序
利用ATL模板产生数据使用程序较为简单,但适用性不广,不能动态适应数据库的变化。下面我们介绍直接使用MFC OLE DB类来生成数据使用程序。
模板的使用
OLE DB数据使用者模板是由一些模板组成的,包括如下一些模板,下面对一些常用类作一些介绍。
1、 会话类
CDataSource类
CDataSource类与OLE DB的数据源对象相对应。这个类代表了OLE DB数据提供程序和数据源之间的连接。只有当数据源的连接被建立之后,才能产生会话对象,可以调用Open来打开数据源的连接。
CSession类
CSession所创建的对象代表了一个单独的数据库访问的会话。一个用CDataSource类产生的数据源对象可以创建一个或者多个会话,要在数据源对象上产生一个会话对象,需要调用函数Open()来打开。同时,会话对象还可用于创建事务操作。
CEnumeratorAccessor类
CEnumeratorAccessor类是用来访问枚举器查询后所产生的行集中可用数据提供程序的信息的访问器,可提供当前可用的数据提供程序和可见的访问器。
2、 访问器类
CAcessor类
CAccessor类代表与访问器的类型。当用户知道数据库的类型和结构时,可以使用此类。它支持对一个行集采用多个访问器,并且,存放数据的缓冲区是由用户分配的。
CDynamicAccessor类
CDynamicAccessor类用来在程序运行时动态的创建访问器。当系统运行时,可以动态地从行集中获得列的信息,可根据此信息动态地创建访问器。
CManualAccessor类
CManualAccessor类中以在程序运行时将列与变量绑定或者是将参数与变量捆定。
3、 行集类
CRowSet类
CRowSet类封装了行集对象和相应的接口,并且提供了一些方法用于查询、设置数据等。可以用Move()等函数进行记录移动,用GetData()函数读取数据,用Insert()、Delete()、SetData()来更新数据。
CBulkRowset类
CBulkRowset类用于在一次调用中取回多个行句柄或者对多个行进行操作。
CArrayRowset类
CArrayRowset类提供用数组下标进行数据访问。
4、 命令类
CTable类
CTable类用于对数据库的简单访问,用数据源的名称得到行集,从而得到数据。
CCommand类
CCommand类用于支持命令的数据源。可以用Open()函数来执行SQL命令,也可以Prepare()函数先对命令进行准备,对于支持命令的数据源,可以提高程序的灵活性和健壮性。
在stdafx.h头文件里,加入如下代码。
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atldbcli.h>
#include <atldbsch.h> // if you are using schema templates
在stdafx.cpp文件里,加入如下代码。
#include <atlimpl.cpp>
CComModule _Module;
决定使用何种类型的存取程序和行集。
获取数据
在打开数据源,会话,行集对象后就可以获取数据了。所获取的数据类型取决于所用的存取程序,可能需要绑定列。按以下步骤。
1、 用正确的命令打开行集对象。
2、 如果使用CManualAccessor,在使用之前与相应列进行绑定。要绑定列,可以用函数GetColumnInfo,如下所示:
// Get the column information
ULONG ulColumns    = 0;
DBCOLUMNINFO* pColumnInfo = NULL;
LPOLESTR pStrings   = NULL;
if (rs.GetColumnInfo(&ulColumns, &pColumnInfo, &pStrings) != S_OK)
AfxThrowOLEDBException(rs.m_pRowset, IID_IColumnsInfo);
struct MYBIND* pBind = new MYBIND[ulColumns];
rs.CreateAccessor(ulColumns, &pBind[0], sizeof(MYBIND)*ulColumns);
for (ULONG l=0; l<ulColumns; l++)
rs.AddBindEntry(l+1, DBTYPE_STR, sizeof(TCHAR)*40, &pBind[l].szValue, NULL, &pBind[l].dwStatus);
rs.Bind();
3、 用while循环来取数据。在循环中,调用MoveNext来测试光标的返回值是否为S_OK,如下所示:
while (rs.MoveNext() == S_OK)
{
  // Add code to fetch data here
  // If you are not using an auto accessor, call rs.GetData()
}
4、 在while循环内,可以通过不同的存取程序获取数据。
1) 如果使用的是CAccessor类,可以通过使用它们的数据成员进行直接访问。如下所示:
2) 如果使用的是CDynamicAccessor 或CDynamicParameterAccessor 类,可以通过GetValue或GetColumn函数来获取数据。可以用GetType来获取所用数据类型。如下所示:
while (rs.MoveNext() == S_OK)
{
  // Use the dynamic accessor functions to retrieve your
  // data
  ULONG ulColumns = rs.GetColumnCount();
  for (ULONG i=0; i<ulColumns; i++)
  {
   rs.GetValue(i);
  }
}
3) 如果使用的是CManualAccessor,可以指定自己的数据成员,绑定它们。就可以直接存取。如下所示:
while (rs.MoveNext() == S_OK)
{
  // Use the data members you specified in the calls to
  // AddBindEntry.
  wsprintf("%s", szFoo);
}
决定行集的数据类型
在运行时决定数据类型,要用动态或手工的存取程序。如果用的是手工存取程序,可以用GetColumnInfo函数得到行集的列信息。从这里可以得到数据类型。
四、总结
由于现在有多种数据源,,想要对这些数据进行访问管理的唯一途径就是通过一些同类机制来实现,如OLE DB。高级OLE DB结构分成两部分:客户和提供者。客户使用由提供者生成的数据。
就像其它基于COM的多数结构一样,OLE DB的开发人员需要实现很多的接口,其中大部分是模板文件。
当生成一个客户对象时,可以通过ATL对象向导指向一个数据源而创建一个简单的客户。ATL对象向导将会检查数据源并创建数据库的客户端代理。从那里,可以通过OLE DB客户模板使用标准的浏览函数。
当生成一个提供者时,向导提供了一个很好的开端,它们仅仅是生成了一个简单的提供者来列举某一目录下的文件。然后,提供者模板包含了OLE DB支持的完全补充内容。在这种支持下,用户可以创建OLE DB提供者,来实现行集定位策略、数据的读写以及建立书签。

 

 

《在Visual C++中用ADO进行数据库编程》

日期:2001.11.10 浏览:11

 

  ActiveX数据对象(ADO)是OLE DB上面的高层数据库API。我们在C++程序中也可以调用ADO。本文将在VC 6.0环境下做一个小小的例子解释如何使用ADO。 

  1. 生成应用程序框架并初始化OLE/COM库环境 

  创建一个标准的MFC AppWizard(exe)应用程序,然后在应用程序类的InitInstance函数中初始化OLE/COM库(因为ADO库是一个COM DLL库)。 

  BOOL CADOTestApp::InitInstance() 

  { //初始化OLE/COM库环境 

AfxOleInit();} 

  2. 引入ADO库文件 

  使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下: 

   #include 〈comdef.h〉 

   #import "c:\program files\common files\system\ado\msado15.dll"

   no_namespace 

   rename ("EOF","adoEOF") 

  头文件comdef.h使我们的应用程序能够使用Visual C++中的一些特殊COM支持类,这些类使得处理OLE自治更为容易一些,OLE自治是ADO使用的数据类型。后三行使用#import指令在我们的应用程序中输入ADO类库定义。 

  ADO类的定义是作为一种资源存储在ADO DLL(msado15.dll)中,在其内部称为类型库。类型库描述了自治接口,以及C++使用的COM vtable接口。当使用#import指令时,在运行时Visual C++需要从ADO DLL中读取这个类型库,并以此创建一组C++头文件。这些头文件具有.tli 和.tlh扩展名,读者可以在项目的目录下找到这两个文件。在C++程序代码中调用的ADO类要在这些文件中定义。 

  程序的第三行指示ADO对象不使用名称空间。在有些应用程序中,由于应用程序中的对象与ADO中的对象之间可能会出现命名冲突,所以有必要使用名称空间。如果要使用名称空间,则可把第三行程序修改为: rename_namespace("AdoNS")。第四行代码将ADO中的EOF(文件结束)更名为adoEOF,以避免与定义了自己的EOF的其他库冲突。 

  3.利用智能指针进行数据库操作 

  在CaboutDlg头文件中定义两个ADO智能指针类实例,并在对话框中加入一个ListCtrl。 

_ConnectionPtr m_pConnection; 

_RecordsetPtr m_pRecordset; 

  ClistCtrl m_List; 

  ADO库包含三个智能指针:_ConnectionPtr、_CommandPtr和_RecordsetPtr。 

_ConnectionPtr通常被用来创建一个数据连接或执行一条不返回任何结果的SQL语句,如一个存储过程。 

_CommandPtr返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储过程和SQL语句。在使用_CommandPtr接口时,可以利用全局_ConnectionPtr接口,也可以在_CommandPtr接口里直接使用连接串。 

_RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多的控制功能,如记录锁定、游标控制等。 

  在OnInitDialog()中加入以下代码: 

  BOOL CAboutDlg::OnInitDialog() 

  { 

CDialog::OnInitDialog(); 

_variant_t TheValue; 

m_List.ResetContent(); 

  m_pConnection.CreateInstance(_uuidof(Connection)); 

  m_pRecordset.CreateInstance(_uuidof(Recordset)); 

  try{ 

m_pConnection->Open("DSN=ADOTest","","",0); //连接叫作ADOTest的ODBC数据源 

m_pRecordset->Open("SELECT * FROM BlockDefine",(IDispatch*)m_pConnection, 
adOpenDynamic,
adLockOptimistic,
adCmdText); 

  //执行SQL语句得到一个记录集 

   while(!m_pRecordset->adoEOF) 

  //遍历所有记录 

   { 

   TheValue = m_pRecordset->GetCollect("BlockIndex");

  //得到字段BlockIndex的值 

   if(TheValue.vt!=VT_NULL) 

   m_List.AddString((char*)_bstr_t(TheValue)); //将该值加入到列表控件中 

   m_pRecordset->MoveNext(); 

  } 

   m_pRecordset->Close(); 

m_pConnection->Close(); 

   } 

   catch(_com_error e) //异常处理 

   { 

AfxMessageBox(e.ErrorMessage()); 

  } 

  m_pRecordset = NULL; 

m_pConnection = NULL; 

  return TRUE; // return TRUE unless you set the focus to a control 

  } 

  程序中通过_variant_t和_bstr_t转换COM对象和C++类型的数据, _variant_t类封装了OLE自治VARIANT数据类型。在C++中使用_variant_t类要比直接使用VARIANT数据类型容易得多。 

  好,编译后该程序就能运行了,但记住运行前要创建一个叫ADOTest的ODBC数据源。该程序将把表BlockDefine中的BlockIndex字段值显示在列表控件中。

作者:蒋东宇

 

DAO技术


DAO技术 

4.1 DAO与ODBC 

在WINDOWS环境下进行数据库访问工作您有两种选择:使用DAO技术或者使用ODBC技术。ODBC(OPEN DATABASE CONNECTIVITY)即开放式数据库互联,作为WINDOWS开放栍准结构的一个重要部分已经为很多的WINDOWS程序员所熟悉。DAO(DATA ACCESS OBJECTS)即数据访问对象集(DATA ACCESS OBJECTS)是MICROSOFT提供的基于一个数据库对象集合的访问技术。它们都是WINDOWS API的一个部分,可以独立于DBMS进行数据库访问。那么ODBC和DAO的区别在哪里呢?ODBC和DAO访问数据库的机制是完全不同的。ODBC的工作依赖于数据库制造商提供的驱动程序,使用ODBC API的时候,WINDOWS的ODBC管理程序,把数据库访问的请求传递给正确的驱动程序,驱动程序再使用SQL语句指示DBMS完成数据库访问工作。DAO则绕开了中间环节,直接使用MICROSOFT提供的数据库引擎(MICROSOFT JET DATABASE ENGINE)提供的数据库访问对象集进行工作。速度比ODBC快。数据库引擎目前已经达到了3.0版本。它是DAO、MS ACCESS、MS VISUAL BASIC等等WINDOWS应用进行数据库访问的基础。引擎本身的数据库格式为MDB,也支持对目前流行的绝大多数数据库格式的访问,当然MDB是数据库引擎中效率最高的数据库。 

如果您使用客户机/服务器模型的话,建议您使用ODBC方案;如果您希望采用MDB格式的数据库,或者利用数据库引擎的速度,那么DAO是更好的选择。 

4.2 使用MFC实现DAO技术 

MFC对所有的DAO对象都进行了封装。使用MFC进行DAO编程,首先要为每一个打开的数据库文件提供一个数据库对象──CDaoDatabase,由这个对象管理数据库的连接。然生成记录集对象──CDaoRecordset,通过它来进行查询、操作、更新等等的工作。如果需要在程序中管理数据库的结构,则需要使用DAO当中的表结构信息对象CDaoTableInfo及字段定义对象CDaoFieldInfo来进行获得或者改变数据库表结构的工作。CDaoDatabase、CDaoRecordset、CDaoTableDefInfo、CDaoFieldInfo是使用MFC进行DAO编程的最基本也是最常用的类。 

下面,我们通过一个实例来介绍如何使用MFC的DAO类来进行数据库访问的工作。在这个实例当中,我们将在程序当中建立一个学生档案管理数据库,并通过对话框来添加、删除和浏览记录。我们首先看以下程序运行的情况。 

我们将针对程序的功能依次介绍如何生成和使用数据库对象、记录集对象以及如何通过记录集来操纵数据库。我们将通过解释对数据库进行操作的源程序来介绍如何用MFC来实现DAO技术。 

下面介绍如何建库: 

首先新建一个数据库对象。 

newDatabase = new CDaoDatabase; 

newDatabase_>Create(_T("stdfile.mdb"), 

dbLangGeneral, dbVersion30); 

利用数据库引擎在磁盘上建立一个MDB格式的数据库文件。 

stdfile.mdb是在磁盘上面建立的数据库文件的名字, 

dbLangGeneral 是语言选项。 

dbVersion30这是数据库引擎版本选项。 

图3.12 

然后新建一个数据库表定义信息对象。 

CDaoTableDef *TableInfo; 

TableInfo = new CDaoTableDef(newDatabase); 

TableInfo_>Create(_T("student")); 

新建一个字段定义信息对象。 

按要求填写字段定义信息对象。 

定义字段名称: 

FieldInfo_>m_strName = CString("studentName"); 

定义字段类型: 

FieldInfo_>m_nType = dbText; 

定义字段所占的字节数大小: 

FieldInfo_>m_lSize = 10; 

定义字段特性: 

FieldInfo_>m_lAttributes = dbVariableField | dbUpdatableField; 

dbVariableField参数的意思是该字段的所占的字节数是可变的。 

dbUpdatableField参数的意思是该字段的值是可变的。 

根据字段定义对象在数据库表对象当中生成字段。 

TableInfo_>CreateField(*FieldInfo); 

在生成了所有的字段之后,将新的数据库表的定义填加到数据库对象当中去。 

TableInfo_>Append(); 

下面介绍如何进行数据库操作: 

首先生成记录集对象: 

Recordset = new CDaoRecordset(newDatabase); 

然后使用SQL语句打开记录集对象。首先把SQL语句记入一个字符串: 

CString strQuery = _T("Select * from student"); 

使用这个字符串打开记录集。 

Recordset_>Open(dbOpenDynaset , strQuery); 

dbOpenDynaset参数的意思是表示记录集打开的类型。dbOpenDynaset的意思是打开一个可以双向滚动的动态记录集。这个记录集中的记录是使用我们定义的SQL语句对数据库进行查询得到的。这个参数还有另外的两种选择: 

dbOpenTable参数指示打开一个数据表类型的记录集,使用这种类型的记录集只能对单一的数据库中的记录进行操纵。 

如果使用dbOpenSnapshot参数表示打开的是映像记录集,它实际上是所选择的记录集的一个静态的拷贝,在只需要进行查询操作或者希望制作报表的时候,使用这种记录集比较合适,它不会对数据库中的数据进行修改。 

接下来对记录集当中的一个标志位赋值,说明是否要求自动地标记出CACHE当中经改变的记录。使用记录集的时候是DAO把被检索出的记录读入CACHE,所有的操纵都是针对CACHE中的记录进行的,要实现对数据库当中的记录更新必须把CACHE记录中被改变的字段的值写回到数据库文件当中去。这个标志位的作用就是当CACHE中的数据改变的时候,是否需要自动的标记出记录中那些应该被写回的字段。 

下面介绍如何填加一个记录。 

  

m_Recordset _>AddNew(); 

m_Recordset_>Update(); 

使用AddNew()这个函数可以在数据表记录集或者是动态记录集当中添加新的记录,调用AddNew()之后必须接着调用Update()来确认这个添加动作,将新的记录保存到数据库文件当中去。新的记录在数据库当中的位置取决于当前记录集的类型:如果是动态记录集,新记录都将被插入到记录集的末尾。如果是数据表记录集的话,当数据库表中定义了主键的时候新记录将按照库表的排序规则插入到合适的地方;如果没有定义主键那么新记录也会被插入到记录集的末尾。 

用AddNew()会改变记录集的当前记录。只有将当前记录定位在新记录上,才能填写它的数据。所以我们使用MoveLast函数使刚刚添加的记录成为当前记录,然后调用Edit函数对新记录进行编辑。 

m_Recordset_>MoveLast(); 

m_Recordset_>Edit(); 

依次给新记录的字段进行赋值: 

COleVariant var1(m_Name , VT_BSTRT); 

m_Recordset_>SetFieldValue(_T("studentName") , var1); 

COleVariant var2(m_ID , VT_BSTRT); 

m_Recordset_>SetFieldValue(_T("studentID") , var2); 

COleVariant var3(m_Class , VT_BSTRT); 

m_Recordset_>SetFieldValue(_T("studentClass") , var3); 

COleVariant var4(m_SID , VT_BSTRT); 

m_Recordset_>SetFieldValue(_T("studentSID") , var4); 

COleVariant 这个类封装了WIN32提供的VARIANT这个结构以及对它的操作。这个类当中可以存储多种类型的数据。需要注意的是这种包容能力是通过C语言当中的UNION提供的,就是说一个COleVariant 对象只能保存一种类型的数据。我们先把字段的值装入OLE变体对象,再使用这个变体对象对记录中的字段进行赋值。VT_BSTRT参数的作用是在生成OLE变体对象的时候指示将要封入的数据的类型为字符串。当对所有的字段都结束赋值后,调用Update 函数来保存刚才的修改。 

m_Recordset_>Update(); 

注意,在调用Update函数之前,如果进行了改变当前记录的操作,那么前面进行的所有的赋值工作都将丢失,而且不会给出任何的警告。 

这段代码从记录集中取出一个记录的值,这里同样要用到OLE变体对象。记录集的GetFieldValue将返回一个变体对象,我们首先取得这个变体对象,然后从中取出需要的值。 

这里V_BSTRT指示从变体对象当中取出字符串类型的数据。 

如何从数据库中删去一个记录呢?首先要使该记录成为当前记录。然后使用Delete函数来执行删除操作。 

m_Recordset_>Delete(); 

删除之后,我们必须把当前记录更改为其他的记录,以确认这个删除动作。 

以上就是在MFC中使用DAO进行数据库操作的方法。 

了解了前面的内容,相信您对MFC类库已经有了比较深入的认识,可以使用MFC编写出不错的程序了。下面,我们将向您介绍如何在VISUAL C++集成开发环境之下调试自己的程序。 


中华技术网整理发布 http://www.asfocus.com

一个功能强大的用ADO访问数据库的类


ADO 访问数据库是基于COM ,访问速度快、使用方便等特点。我在实际的工作当中发现可以把一些常用的方法构造成一个类,不妨叫她为 CAdoEx 类吧。下面分别是头文件、实现文件。要是有不妥之处请给我发邮件 luoshizhen@163.net 

*说明:--------------------------------------------*/ 
/* 注 stdafx.h 里要有下面两行 */ 
/* #include <comdef.h> */ 
/* #import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") */ 
/* AfxOleInit() ; ADO 初始在应用的主线程初始化时调用 */ 
/*说明:--------------------------------------------*/ 


/* 以下是类的成员说明 */ 
#ifndef _ADOEX_H_ 
#define _ADOEX_H_ 

#include "stdafx.h" 

class CAdoEx 

public: 
CAdoEx(); 
virtual ~CAdoEx(); 

/*打开 Ado 数据库*/ 
BOOL OpenAdo(LPCTSTR lpDSN ,LPCTSTR lpUID ,LPCTSTR lpPWD); 


file://--------------------------------------------// 
/* ADO 通用查询函数 */ 
/* 调用参数: 正确的SQL 查询语句. */ 
/* 可以对一个表查询,也可以对多个表查询 */ 
/* 返回一个纪录集 */ 
/* 如果成功返回一个非空的纪录集 */ 
/* 如果失败则返回一个NULL的纪录集 */ 
_RecordsetPtr Query(LPCTSTR lpQuery); 
file://--------------------------------------------// 

file://--------------------------------------------// 
/*连接数据库 */ 
BOOL CloseAdo(); 
file://--------------------------------------------// 

file://--------------------------------------------// 
/* ADO 通用SQL语句执行函数 */ 
/* 调用参数: 正确的SQL 查询语句. */ 
/* 可以对一个表插入 修改 删除 操作 */ 
/* 如果成功返回真 */ 
/* 如果失败返回假 */ 
BOOL Execute(LPCTSTR lpExcute); 
file://--------------------------------------------// 

protected: 
_RecordsetPtr retRecordsetPtr; 
char lpBuff[500]; 
HRESULT hResult; 
BOOL m_bOpen; 
_ConnectionPtr m_pConnection; 
}; 

#endif 

/*------------------以下为实现文件------------------------- 
/* 说明: 这是一个 Ado 类,可以实现用ADO对数据库操作 */ 
/* 1. 连接数据库 */ 
/* 2. 查询数据库的一个或关联表 */ 
/* 3. 执行SQL 语句插入 修改 删除 操作 */ 
/* 4. 关闭数据库 */ 

#include "stdafx.h" 
#include "AdoEx.h" 

#ifdef _DEBUG 
#undef THIS_FILE 
static char THIS_FILE[]=__FILE__; 
#define new DEBUG_NEW 
#endif 

CAdoEx::CAdoEx() 

m_bOpen = FALSE ; 

/*初始化连接的实例*/ 
m_pConnection.CreateInstance(_uuidof(Connection)) ; 


CAdoEx::~CAdoEx() 


if(retRecordsetPtr !=NULL) 
retRecordsetPtr->Close() ; 
retRecordsetPtr = NULL ; 

CloseAdo() ; 

m_bOpen = FALSE ; 

/*打开 Ado 数据库*/ 
BOOL CAdoEx::OpenAdo(LPCTSTR lpDSN ,LPCTSTR lpUID ,LPCTSTR lpPWD ) 

BOOL bRet = FALSE ; 

if(m_bOpen) return TRUE ; 

try 

hResult = m_pConnection->Open(lpDSN,lpUID,lpPWD,0) ; 

if (SUCCEEDED(hResult)) 

TRACE0("Open ADO Database Succeeded !\n") ; 
m_bOpen = TRUE ; 
bRet = TRUE ; 

else 

m_bOpen = FALSE ; 
bRet = FALSE ; 

} /*end of try*/ 
catch(_com_error e ) 

memset(lpBuff,0x00,500) ; 
sprintf(lpBuff,"打开数据库时发生异常 \n:%s",e.ErrorMessage()); 
AfxMessageBox(lpBuff) ; 


return bRet ; 


/* ADO 通用查询函数 */ 
/* 调用参数: 正确的SQL 查询语句. */ 
/* 可以对一个表查询,也可以对多个表查询 */ 
/* 返回一个纪录集 */ 
/* 如果成功返回一个非空的纪录集 */ 
/* 如果失败则返回一个NULL的纪录集 */ 
_RecordsetPtr CAdoEx::Query(LPCTSTR lpQuery) 

retRecordsetPtr = NULL ; 
_variant_t vRecsAffected ; 

if(!m_bOpen) return NULL ; 

try 

retRecordsetPtr = m_pConnection->Execute(lpQuery, 
&vRecsAffected, 
adOptionUnspecified) ; 

catch(_com_error e) 

memset(lpBuff,0x00,500) ; 
sprintf(lpBuff,"查询数据库表时发生异常 \n:%s",e.ErrorMessage()); 
AfxMessageBox(lpBuff) ; 


return retRecordsetPtr ; 


/* ADO 通用SQL语句执行函数 */ 
/* 调用参数: 正确的SQL 查询语句. */ 
/* 可以对一个表插入 修改 删除 操作 */ 
/* 如果成功返回真 */ 
/* 如果失败返回假 */ 
BOOL CAdoEx::Execute(LPCTSTR lpExcute) 

BOOL bRet = FALSE ; 
_variant_t vRecsAffected ; 

if(!m_bOpen) return NULL ; 

try 

m_pConnection->Execute(lpExcute, 
&vRecsAffected, 
adOptionUnspecified) ; 
bRet = TRUE ; 

catch(_com_error e) 

bRet = FALSE ; 
memset(lpBuff,0x00,500) ; 
sprintf(lpBuff,"更改数据库表时发生异常 \n:%s",e.ErrorMessage()); 
AfxMessageBox(lpBuff) ; 


return bRet ; 


BOOL CAdoEx::CloseAdo() 

if (m_bOpen) 

m_pConnection->Close() ; 
TRACE0("Close ADO Connection !\n") ; 


return TRUE ; 


/* 说明: 这是一个 Ado 类,可以实现用ADO对数据库操作 */ 
/* 1. 连接数据库 */ 
/* 2. 查询数据库的一个或关联表 */ 
/* 3. 执行SQL 语句插入 修改 删除 操作 */ 
/* 4. 关闭数据库 */ 

#include "stdafx.h" 
#include "AdoEx.h" 

#ifdef _DEBUG 
#undef THIS_FILE 
static char THIS_FILE[]=__FILE__; 
#define new DEBUG_NEW 
#endif 

CAdoEx::CAdoEx() 

m_bOpen = FALSE ; 

/*初始化连接的实例*/ 
m_pConnection.CreateInstance(_uuidof(Connection)) ; 


CAdoEx::~CAdoEx() 


if(retRecordsetPtr !=NULL) 
retRecordsetPtr->Close() ; 
retRecordsetPtr = NULL ; 

CloseAdo() ; 

m_bOpen = FALSE ; 

/*打开 Ado 数据库*/ 
BOOL CAdoEx::OpenAdo(LPCTSTR lpDSN ,LPCTSTR lpUID ,LPCTSTR lpPWD ) 

BOOL bRet = FALSE ; 

if(m_bOpen) return TRUE ; 

try 

hResult = m_pConnection->Open(lpDSN,lpUID,lpPWD,0) ; 

if (SUCCEEDED(hResult)) 

TRACE0("Open ADO Database Succeeded !\n") ; 
m_bOpen = TRUE ; 
bRet = TRUE ; 

else 

m_bOpen = FALSE ; 
bRet = FALSE ; 

} /*end of try*/ 
catch(_com_error e ) 

memset(lpBuff,0x00,500) ; 
sprintf(lpBuff,"打开数据库时发生异常 \n:%s",e.ErrorMessage()); 
AfxMessageBox(lpBuff) ; 


return bRet ; 


/* ADO 通用查询函数 */ 
/* 调用参数: 正确的SQL 查询语句. */ 
/* 可以对一个表查询,也可以对多个表查询 */ 
/* 返回一个纪录集 */ 
/* 如果成功返回一个非空的纪录集 */ 
/* 如果失败则返回一个NULL的纪录集 */ 
_RecordsetPtr CAdoEx::Query(LPCTSTR lpQuery) 

retRecordsetPtr = NULL ; 
_variant_t vRecsAffected ; 

if(!m_bOpen) return NULL ; 

try 

retRecordsetPtr = m_pConnection->Execute(lpQuery, 
&vRecsAffected, 
adOptionUnspecified) ; 

catch(_com_error e) 

memset(lpBuff,0x00,500) ; 
sprintf(lpBuff,"查询数据库表时发生异常 \n:%s",e.ErrorMessage()); 
AfxMessageBox(lpBuff) ; 


return retRecordsetPtr ; 


/* ADO 通用SQL语句执行函数 */ 
/* 调用参数: 正确的SQL 查询语句. */ 
/* 可以对一个表插入 修改 删除 操作 */ 
/* 如果成功返回真 */ 
/* 如果失败返回假 */ 
BOOL CAdoEx::Execute(LPCTSTR lpExcute) 

BOOL bRet = FALSE ; 
_variant_t vRecsAffected ; 

if(!m_bOpen) return NULL ; 

try 

m_pConnection->Execute(lpExcute, 
&vRecsAffected, 
adOptionUnspecified) ; 
bRet = TRUE ; 

catch(_com_error e) 

bRet = FALSE ; 
memset(lpBuff,0x00,500) ; 
sprintf(lpBuff,"更改数据库表时发生异常 \n:%s",e.ErrorMessage()); 
AfxMessageBox(lpBuff) ; 


return bRet ; 


BOOL CAdoEx::CloseAdo() 

if (m_bOpen) 

m_pConnection->Close() ; 
TRACE0("Close ADO Connection !\n") ; 


return TRUE ; 
}
中华技术网整理发布 http://www.asfocus.com

《用Visual C++开发数据库应用程序》

日期:2002.1.9 浏览:29

 

1、 概述

1、1 Visual C++开发数据库技术的特点

Visual C++提供了多种多样的数据库访问技术——ODBC API、MFC ODBC、DAO、OLE DB、ADO等。这些技术各有自己的特点,它们提供了简单、灵活、访问速度快、可扩展性好的开发技术。


简单性 

Visual C++中提供了MFC类库、ATL模板类以及AppWizard、ClassWizard等一系列的Wizard工具用于帮助用户快速的建立自己的应用程序,大大简化了应用程序的设计。使用这些技术,可以使开发者编写很少的代码或不需编写代码就可以开发一个数据库应用程序。


灵活性 

Visual C++提供的开发环境可以使开发者根据自己的需要设计应用程序的界面和功能,而且,Visual C++提供了丰富的类库和方法,可以使开发者根据自己的应用特点进行选择。


访问速度快 

为了解决ODBC开发的数据库应用程序访问数据库的速度慢的问题,Visual C++提供了新的访问技术——OLE DB和ADO,OLE DB和ADO都是基于COM接口的技术,使用这种技术可以直接对数据库的驱动程序进行访问,这大大提供了访问速度。


可扩展性 

Visual C++提供了OLE技术和ActiveX技术,这种技术可以增强应用程序的能力。使用OLE技术和ActiveX技术可以使开发者利用Visual C++中提供的各种组件、控件以及第三方开发者提供的组件来创建自己的程序,从而实现应用程序的组件化。使用这种技术可以使应用程序具有良好的可扩展性。


访问不同种类数据源 

传统的ODBC技术只能访问关系型数据库,在Visual C++中,提供了OLE DB访问技术,不仅可以访问关系型数据库,还可以访问非关系型数据库。

1、2 Visual C++开发数据库技术

Visual C++提供了多种访问数据库的技术,如下所示:


ODBC(Open DataBase Connectivity) 


MFC ODBC(Microsoft Foundation Classes ODBC) 


DAO (Data Access Object) 


OLE DB(Object Link and Embedding DataBase) 


ADO(ActiveX Data Object) 

这些技术各有自己的特点,总结如下:


ODBC 

ODBC是客户应用程序访问关系数据库时提供的一个统一的接口,对于不同的数据库,ODBC提供了一套统一的API,使应用程序可以应用所提供的API来访问任何提供了ODBC驱动程序的数据库。而且,ODBC已经成为一种标准,所以,目前所有的关系数据库都提供了ODBC驱动程序,这使ODBC的应用非常广泛,基本上可用于所有的关系数据库。

但由于ODBC只能用于关系数据库,使得利用ODBC很难访问对象数据库及其它非关系数据库。

由于ODBC是一种底层的访问技术,因些,ODBC API可以使客户应用程序能够从底层设置和控制数据库,完成一些高层数据库技术无法完成的功能。


MFC ODBC 

由于直接使用ODBC API编写应用程序要编制大量代码,在Visual C++中提供了MFC ODBC类,封装了ODBC API,这使得利用MFC来创建ODBC的应用程序非常简便。


DAO 

DAO提供了一种通过程序代码创建和操纵数据库的机制。多个DAO构成一个体系结构,在这个结构中,各个DAO对象协同工作。MFC DAO是微软提供的用于访问Microsoft Jet数据库文件(*.mdb)的强有力的数据库开发工具,它通过DAO的封装,向程序员提供了DAO丰富的操作数据库手段。


OLE DB 

OLE DB是Visual C++开发数据库应用中提供的新技术,它基于COM接口。因此,OLE DB对所有的文件系统包括关系数据库和非关系数据库都提供了统一的接口。这些特性使得OLE DB技术比传统的数据库访问技术更加优越。

与ODBC技术相似,OLE DB属于数据库访问技术中的底层接口。

直接使用OLE DB来设计数据库应用程序需要大量的代码。在VC中提供了ATL模板,用于设计OLE DB数据应用程序和数据提供程序。


ADO 

ADO技术是基于OLE DB的访问接口,它继承了OLE DB技术的优点,并且,ADO对OLE DB的接口作了封装,定义了ADO对象,使程序开发得到简化,ADO技术属于数据库访问的高层接口。

 

2、 使用ODBC API

Microsoft 开放数据库互连(ODBC,Open DataBase Connectivity)是Microsoft Windows 开放服务体系(WOSA)的一部分,是一个数据库访问的标准接口。使用这一标准接口,我们可以不关心具体的数据库管理系统(DBMS)的细节,而只要有相应类型数据库的ODBC驱动程序,就可以实现对数据库的访问。

ODBC编程接口为我们提供了极大的灵活性,我们可以通过这一个接口访问不同种类的数据库。而且,通过相应的ODBC驱动程序,我们可以方便地实现不同数据类型之间的转换。

2.1 ODBC API 概述

ODBC是一个应用广泛的数据库访问应用编程接口(API),使用标准的SQL(结构化查询语言)作为其数据库访问语言。

2.11体系结构

ODBC的结构是建立在客户机/服务器体系结构之上,它包含如下四个部分:

应用程序(Application ):

应用程序即用户的应用,它负责用户与用户接口之间的交互操作,以及调用ODBC函数以给出SQL请求并提取结果以及进行错误处理。

ODBC驱动程序管理器(Driver Manager):

ODBC驱动程序管理器为应用程序加载和调用驱动程序,它可以同时管理多个应用程序和多个驱动程序。它的功能是通过间接调用函数和使用动态链接库(DLL)来实现的,因此它一般包含在扩展名为”DLL”的文件中。

ODBC驱动程序(Driver)

ODBC 驱动程序执行ODBC函数调用,呈送 SQL 请求给指定的数据源,并将结果返回给应用程序。驱动程序也负责与任何访问数据源的必要软件层进行交互作用,这种层包括与底层网络或文件系统接口的软件。

数据源

数据源由数据集和与其相关联的环境组成,包括操作系统、DBMS 和网络(如果存在的话)。ODBC 通过引入“数据源”的概念解决了网络拓扑结构和主机的大范围差异问题,这样,用户看到的是数据源的名称而不必关心其它东西。

2.12数据类型

ODBC使用两类数据类型:SQL数据类型和C数据类型。SQL数据类型用于数据源,C数据类型用于应用程序代码中。

2.13句柄

ODBC API 实现数据库操作的手段是语句,这是一个强有力的手段。ODBC语句除了能执行SQL语句和完成查询操作之外,还能实现大多数数据库操作。

在ODBC中,使用不同的句柄(HANDLE)来标志环境(ENVIRONMENT)、连接(CONNECTION)、语句(STATEMENT)、描述器(DESCRIPTOR)等。

句柄就是一个应用程序变量,系统用它来存储关于应用程序的上下文信息和应用程序所用到的一些对象。它和 Windows 编程中的概念类似,不过ODBC 更加完善了句柄的作用。

1、 环境句柄是 ODBC 中整个上下文的句柄,使用 ODBC 的每个程序从创建环境句柄开始,以释放环境句柄结束。所有其它的句柄(这一应用程序所有的联接句柄和语句句柄)都由环境句柄中的上下文来管理。环境句柄在每个应用程序中只能创建一个。

2、联接句柄管理有关联接的所有信息。联接句柄可以分配多个,这不仅合法而且很有用;但不要生成不必要的句柄以免资源的浪费。但是,不同的驱动程序支持的联接情况有所不同,有的驱动程序在一个应用程序中仅支持一个联接句柄,有的驱动程序仅支持一个语句句柄。在应用程序中,可以在任何适当的时候联接或脱离数据源,但不要轻易地建立或脱离联接。

3、语句句柄是 ODBC API 真正发挥重要作用的,它被用来处理 SQL 语句及目录函数,每个语句句柄只与一个联接有关。当驱动程序接收一个来自应用程序的函数调用指令而该指令包含一个语句句柄时,驱动程序管理器将使用存储在语句句柄中的联接句柄来将这一函数调用发送给合适的驱动程序。

4、描述器句柄是元数据的集合,这些元数据描述了SQL语句的参数、记录集的列等信息。当有语句被分配内存之后,描述器自动生成,称为自动分配描述器。在程序中,应用程序也可调用SQLAllocHandle分配描述器。

当应用程序调用API函数SQLAllocHandle时,驱动管理器或者ODBC驱动程序将为所声明的句柄类型分配内部结构,返回句柄值。

2.14异常处理

为了在程序开发过程中调试程序,发现程序错误,ODBC API通过两种方式返回有关ODBC API函数执行的的信息:返回码和诊断记录。返回码返回函数执行的返回值,说明函数执行成功与否。诊断记录说明函数执行的详细信息。


返回码(Return Code) 

每一个ODBC API函数都返回一个代码——返回码,指示函数执行的成功与否。如果函数调用成功,返回码为SQL_SUCCESS或SQL_SUCCESS_WITH_INFO。SQL_SUCCESS指示可通过诊断记录获取有关操作的详细信息,SQL_SUCCESS_WITH_INFO指示应用程序执行结果带有警告信息,可通过诊断记录获取详细的信息。如果函数调用失败,返回码为SQL_ERROR。

下面的一段代码根据函数SQLFetch()执行的返回码,判断函数执行的成功与否,从而据此进行相应的处理。

SQLRETURN rtcode;

SQLHSTMT hstmt;

While(rtcode=SQLFetch(hstmt)!=SQL_NO_DATA)

{

if(rtcode==SQL_SUCCESS_WITH_INFO)

{

//显示警告信息

}

else

{

//显示出错信息

break;

}

//函数调用成功,进行处理

}

如果程序执行错误,返回码为SQL_INVALID_HANDLE,程序无法执行,而其它的返回码都带有程序执行的信息。


诊断记录(Diagnostic Records) 

每个ODBC API函数都能够产生一系列的反映操作信息的诊断记录。这些诊断记录放在相关连的ODBC句柄中,直到下一个使用同一个句柄的函数调用,该诊断记录一直存在。诊断记录的大小没有限制。

诊断记录有两类:头记录(Head Record)和状态记录(Status Record)。头记录是第一版权法记录(Record 0),后面的记录为状态记录。诊断记录有许多的域组成,这些域在头记录和状态记录中是不同的。

可以用SQLGetDiagField函数获取诊断记录中的特定的域,另外,可以使用SQLGetDiagRec()获取诊断记录中一些常用的域,如SQLSTATE、原始错误号等。


头记录 

头记录的各个域中包含了一个函数执行的通用信息,无论函数执行成功与否,只要不返回SQL_INVALID_HANDLE,都会生成头记录。


状态记录 

状态记录中的每个域包含了驱动管理器、ODBC驱动程序或数据源返回的特定的错误或警告信息,包括SQLSTATE、原始错误码、诊断信息、列号和行号等。只有函数执行返回SQL_ERROR,SQL_STILL_EXEUTING、SQL_SUCCESS_WITH_INFO、SQL_NEED_DATA或SQL_NO_DATA时,才会生成诊断记录。


使用SQLGetDiagRec和SQLGetDiagField 

应用程序可以调用函数SQLGetDiagRec或SQLGetDiagField获取诊断信息。对于给定的句柄,这两个函数返回最近使用该句柄的函数的诊断信息。当有使用该句柄的函数执行时,句柄记录所记录的原有的诊断信息被覆盖。如果函数执行后产生多个状态记录,程序必须多次调用这两个函数以获取信息。

2.2 应用ODBC API建立应用程序

虽然直接应用ODBC API编制应用程序相对来说较为繁琐,但是,由于直接使用ODBC API编写的程序相对要简洁、高效。所以,我们有必要学习直接使用ODBC API编程。

一般地,编写ODBC程序主要有以下几个步骤:


分配ODBC环境 


分配连接句柄 


连接数据源 


构造和执行SQL语句 


取得执行结果 


断开同数据源的连接 


释放ODBC环境 

2.21 分配ODBC环境

对于任何ODBC应用程序来说,第一步的工作是装载驱动程序管理器,然后初始化ODBC环境,分配环境句柄。

首先,程序中声明一个SQLHENV类型的变量,然后调用函数SQLAllocHandle,向其中传递分配的上述SQLHENV类型的变量地址和SQL_HANDLE_ENV选项。如下代码所示:

SQLHENV henv;

SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&henv);

执行该调用语句后,驱动程序分配一个结构,该结构中存放环境信息,然后返回对应于该环境的环境句柄。

2.22分配连接句柄

分配环境句柄后,在建立至数据源的连接之前,我们必须分配一个连接句柄,每一个到数据源的连接对应于一个连接句柄。

首先,程序定义了一个SQLHDBC类型的变量,用于存放连接句柄,然后调用SQLAllocHandle函数分配句柄。如下代码所示:

SQLHDBC hdbc;

SQLAllocHandle(SQL_HANDLE_DBC,henv,&hdbc);

henv为环境句柄。

2.23 连接数据源

当连接句柄分配完成后,我们可以设置连接属性,所有的连接属性都有缺省值,但是我们可以通过调用函数SQLSetConnectAttr()来设置连接属性。用函数SQLGetConnectAttr()获取这些连接属性。

函数格式如下:

SQLRETURN SQLSetConnectAttr(SQLHDBC ConnectionHandle,SQLINTEGER Attribute,SQLPOINTER ValuePtr,SQLINTEGER StringLength);

SQLRETURN SQLGetConnectAttr(SQLHDBC ConnectionHandle,SQLINTEGER Attribute,SQLPOINTER ValuePtr,SQLINTEGER StringLength);

应用程序可以根据自己的需要设置不同的连接属性。

完成对连接属性的设置之后,就可以建立到数据源的连接了。对于不同的程序和用户接口,可以用不同的函数建立连接:SQLConnect、SQLDriverConnect、SQLBrowseConnect。

SQLConnect

该函数提供了最为直接的程序控制方式,我们只要提供数据源名称、用户ID和口令,就可以进行连接了。

函数格式:

SQLRETURN SQLConnect(SQLHDBC ConnectionHandle,SQLCHAR ServerName,SQLSMALLINT NameLength1,SQLCHAR UserName,SQLSMALLINT NameLength2,SQLCHAR *Authentication,SQLSMALLINT NameLength3);

参数:

ConnectionHandle 连接句柄

ServerName 数据源名称

NameLength1 数据源名称长度

UserName 用户ID

NameLength2 用户ID长度

Authentication 用户口令

NameLength3 用户口令长度

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE.

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

下面的代码演示了如何使用ODBC API的SQLConnect函数建立同数据源SQLServer的连接。

 

#include “sqlext.h”

SQLHENV henv;;

SQLHDBC hdbc;

SQLHSTMT hstmt;

SQLRETURN retcode;

/*Allocate environment handle */

retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Set the ODBC version environment attribute */

retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Allocate connection handle */

retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Set login timeout to 5 seconds. */

SQLSetConnectAttr(hdbc, (void*)SQL_LOGIN_TIMEOUT, 5, 0);

/* Connect to data source */

retcode = SQLConnect(hdbc, (SQLCHAR*) "Sales", SQL_NTS,

(SQLCHAR*) "JohnS", SQL_NTS,

(SQLCHAR*) "Sesame", SQL_NTS);

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){

/* Allocate statement handle */

retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Process data */;

SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

}

SQLDisconnect(hdbc);

}

SQLFreeHandle(SQL_HANDLE_DBC, hdbc); 



}

SQLFreeHandle(SQL_HANDLE_ENV, henv);

SQLDriveConnect

函数SQLDriveConnect用一个连接字符串建立至数据源的连接。它可以提供比SQLConnect函数的三个参数更多的信息,可以让用户输入必要的连接信息。

如果连接建立,该函数返回完整的字符串,应用程序可使用该连接字符串建立另外的连接。

函数格式:

SQLRETURN SQLDriverConnect(SQLHDBC ConnectionHandle,SQLHWND WindowHandle,SQLCHAR InConnectionString,SQLSMALLINT StringLength1,SQLCHAR OutConnetionString,SQLSMALLINT BufferLength,SQLSMALLINT *StringLength2Ptr,SQLSMALLINT DriverCompletion);

参数:

ConnectionHandle 连接句柄

WindowHandle 窗口句柄,应用程序可以用父窗口的句柄,或用NULL指针

InConnectionString 连接字符串长度

OutConnectionString 一个指向连接字符中的指针

BufferLength 存放连接字符串的缓冲区的长度

StringLength2Ptr 返回的连接字符串中的字符数

DriverCompletion 额外连接信息,可能取值有:SQL_DRIVER_PROMPT,

SQL_DRIVER_COMPLETE, 

SQL_DRIVER_COMPLETE_REQUIRED, or

SQL_DRIVER_NOPROMPT.

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE.

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

SQLBrowseConnect

函数SQLBrowseConnect支持以一种迭代的方式获取到数据源的连接,直到最后建立连接。它是基于客房机/服务器的体系结构,因此,本地数据库不支持该函数。

一般,我们提供部分连接信息,如果足以建立到数据源的连接,则成功建立连接,否则返回SQL__NEED__DATA,并在OutConnectionString参数中返回所需要的信息。

函数格式:

SQLRETURN SQLBrowseConnect(SQLHDBC ConnectionHandle,SQLCHAR* InConnectionString,SQLSAMLLINT StringLength1,SQLCHAR* OutConnectionString,SQLSMALLINT BufferLength,SQLSMALLINT *StringLength2Ptr);

参数:

ConnectionHandle 连接句柄

InConnectionString 指向输出字符串的指针

StringLength1 输出字符串的指针长度

OutConnectionString 指向输出字符串的指针

BufferLength 用于存放输出字符串的缓冲区的长度

StringLength2Ptr 实际返回的字符串的长度

 

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE.

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

下面的代码讲述了如何使用ODBC API的SQLBrowseConnect函数建立同数据源的连接。

 

#define BRWS_LEN 100SQLHENV 

henv;SQLHDBC hdbc;

SQLHSTMT hstmt;

SQLRETURN retcode;

SQLCHAR szConnStrIn[BRWS_LEN], szConnStrOut[BRWS_LEN];

SQLSMALLINT cbConnStrOut;/* Allocate the environment handle. */

retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Set the version environment attribute. */

retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3, 0);

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Allocate the connection handle. */

retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Call SQLBrowseConnect until it returns a value other than */

/* SQL_NEED_DATA (pass the data source name the first time). */

/* If SQL_NEED_DATA is returned, call GetUserInput (not */

/* shown) to build a dialog from the values in szConnStrOut. */

/* The user-supplied values are returned in szConnStrIn, */

/* which is passed in the next call to SQLBrowseConnect. */

lstrcpy(szConnStrIn, "DSN=Sales"); do {

retcode = SQLBrowseConnect(hdbc, szConnStrIn, SQL_NTS,

szConnStrOut, BRWS_LEN, &cbConnStrOut);

if (retcode == SQL_NEED_DATA)

GetUserInput(szConnStrOut, szConnStrIn);

} while (retcode == SQL_NEED_DATA);

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){

/* Allocate the statement handle. */

retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Process data after successful connection */ ...;

SQLFreeHandle(SQL_HANDLE_STMT, hstmt); }

SQLDisconnect(hdbc); } }

SQLFreeHandle(SQL_HANDLE_DBC, hdbc); }}

SQLFreeHandle(SQL_HANDLE_ENV, henv);

2.24 SQL操作

构造和执行SQL语句

构造SQL语句

可以通过三种方式构造SQL语句:在程序开发阶段确定、在运行时确定或由用户输入SQL语句。

在程序开发时确定的SQL语句,具有易于实现、且可在程序编码时进行测试的优势。

在程序运行时确定SQL语句提供了极大灵活性,但给程序高度带来了困难,且需更多的处理时间。由用户输入的SQL语句,极大的增强了程序的功能,但是,程序必须提供友好的人机界面,且对用户输入的语句执行一定程序的语法检查,能够报告用户错误。

执行SQL语句

应用程序的绝大部分数据库访问工作都是通过执行SQL语句完成的,在执行SQL语句之前,必须要先分配一个语句句柄,然后设置相应语句的语句属性,再执行SQL语句。当一个语句句柄使用完成后,调用函数SQLFreeHandle()释放该句柄。


SQLExecute() 

SQLExecute用于执行一个准备好的语然,当语句中有参数时,用当前绑定的参数变量的值。

函数格式:

SQLRETURN SQLExecute(SQLHSTMT StatementHandle);

参数:

StatementHandle 标识执行SQL语句的句柄,可以用SQLAllocHandle()来获得。

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NEED_DATA, SQL_STILL_EXECUTING, SQL_ERROR, SQL_NO_DATA, or SQL_INVALID_HANDLE

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 


SQLExecDiret() 

SQLExecDirect直接执行SQL语句,对于只执行一次的操作来说,该函数是速度最快的方法。

函数格式:

SQLRETURN SQLExecDirect(SQLHSTMT StatementHandle,SQLCHAR *StatementText,SQLINTEGER TextLength);

参数:

StatementHandle 语句句柄

StatementText 要执行的SQL语然

StatementText SQL语句的长度。

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NEED_DATA, SQL_STILL_EXECUTING, SQL_ERROR, SQL_NO_DATA, or SQL_INVALID_HANDLE

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 


SQLPripare() 

对于需多次执行的SQL语句来说,除了使用SQLExecDirect函数之外,我们也可以在执行SQL语句之前,先准备SQL语句的执行。对于使用参数的语句,这可大提高程序执行速度。

函数格式:

SQLRETURN SQLPrepare(SQLHSTMT StatementHandle,SQLCHAR* StatementText,SQLINTEGER TextLength);

参数:

StatementHandle 语句句柄

StatementText 要执行的SQL语然

StatementText SQL语句的长度。

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NEED_DATA, SQL_STILL_EXECUTING, SQL_ERROR, SQL_NO_DATA, or SQL_INVALID_HANDLE

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

使用参数

使用参数可以使一条SQL语句多次执行,得到不同结果

SQLBindParameter

函数SQLBindParameter负责为参数定义变量,实现参数值的传递。

函数格式如下:

SQLRETURNSQLBindParameter(SQLHSTMT StatementHandle,SQLUSMALLINT ParameterNumber,SQLSMALLINT InputOutputType,SQLSMALLINT ValueType,SQLSMALLINT ParameterType,SQLUINTEGER ColumnSize,SQLSMALLINT DecimalDigits,SQLPOINTER ParameterValuePtr,SQLINTEGER BufferLength,SQLINTEGER *StrLen_or_IndPtr);

参数:

StatementHandle 语句句柄

ParameterNumber 绑定的参数在SQL语句中的序号,在SQL中,所有参数从左到右顺序编号,从1开始。SQL语句执行之前,应该为每个参数调用函数SQLBindParameter绑定到某个程序变量。

InputOutputType 参数类型,可为SQL_PARA_INPUT, SQL_PARAM_INPUT_OUTPUT, SQL_PARAM_OUTPUT。

ParameterType 参数数据类型

ColumnSIze 参数大小

DecimalDigits 参数精度

ParameterValutePtr 指向程序中存放参数值的缓冲区的指针

BufferLength 程序中存放参数值的缓冲区的字节数

StrLen_or_IndPtr 指向存放参数ParameterValuePtr的缓冲区指针

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

执行时传递参数

对于某些文本文档或位图文件,要占用大量的存储空间。因此,在数据源传递这些数据时,可以分开传递。有两个函数可完成这个工作。

函数格式:

SQLRETURN SQLPutData(SQLHSTMT StatementHandle,

SQLPOINTER DataPtr,SQLINTEGER StrLen_or_Ind);

参数:

StatementHandle 参数句柄

DataPtr 指向包含实际数据的缓冲区指针。

StrLen_or_Lnd 缓冲区长度

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

函数格式:

SQLRETURNSQLParamData(SQLHSTMT StatementHandle,SQLPOINTER *ValuePtrPtr);

参数:

StatementHandle 参数句柄

ValuePtrPtr 指向缓冲区地址的指针

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

下面的代码展示如何使用这两个函数

#define MAX_DATA_LEN 1024

SQLINTEGER cbPartID = 0, cbPhotoParam, cbData;

SQLUINTEGER sPartID; szPhotoFile;

SQLPOINTER pToken, InitValue;

SQLCHAR Data[MAX_DATA_LEN];

SQLRETURN retcode;

SQLHSTMT hstmt;

retcode = SQLPrepare(hstmt, "INSERT INTO PICTURES (PARTID, PICTURE) VALUES 

(?, ?)", SQL_NTS);

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Bind the parameters. For parameter 2, pass */

/* the parameter number in ParameterValuePtr instead of a buffer */ 

/* address. */ SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_ULONG, 

SQL_INTEGER, 0, 0, &sPartID, 0, &cbPartID);

SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT,

SQL_C_BINARY, SQL_LONGVARBINARY,

0, 0, (SQLPOINTER) 2, 0, &cbPhotoParam);

/* Set values so data for parameter 2 will be */

/* passed at execution. Note that the length parameter in */

/* the macro SQL_LEN_DATA_AT_EXEC is 0. This assumes that */

/* the driver returns "N" for the SQL_NEED_LONG_DATA_LEN */

/* information type in SQLGetInfo. */

cbPhotoParam = SQL_LEN_DATA_AT_EXEC(0);

sPartID = GetNextID(); /* Get next available employee ID */

/* number. */ retcode = SQLExecute(hstmt);

/* For data-at-execution parameters, call SQLParamData to */

/* get the parameter number set by SQLBindParameter. */

/* Call InitUserData. Call GetUserData and SQLPutData */

/* repeatedly to get and put all data for the parameter. */

/* Call SQLParamData to finish processing this parameter */

while (retcode == SQL_NEED_DATA) {

retcode = SQLParamData(hstmt, &pToken);

if (retcode == SQL_NEED_DATA) {

InitUserData((SQLSMALLINT)pToken, InitValue);

while (GetUserData(InitValue, (SQLSMALLINT)pToken, Data, 

&cbData))

SQLPutData(hstmt, Data, cbData); } }}

VOID InitUserData(sParam, InitValue)SQLPOINTER InitValue;{

SQLCHAR szPhotoFile[MAX_FILE_NAME_LEN];

/* Prompt user for bitmap file containing employee */

/* photo. OpenPhotoFile opens the file and returns the */ /* file handle. */

PromptPhotoFileName(szPhotoFile);

OpenPhotoFile(szPhotoFile, (FILE *)InitValue); break; }

BOOL GetUserData(InitValue, sParam, Data, cbData)SQLPOINTER InitValue;

SQLCHAR *Data;SQLINTEGER *cbData;BOOL Done;{

/* GetNextPhotoData returns the next piece of photo */

/* data and the number of bytes of data returned */

/* (up to MAX_DATA_LEN). */ Done = GetNextPhotoData((FILE *)InitValue, Data,

MAX_DATA_LEN, &cbData); if (Done) {

ClosePhotoFile((FILE *)InitValue); 

return (TRUE); } 

return (FALSE); }

记录的添加、删除和更新

应用程序对数据源的数据更新可以通过三种方式实现:使用相应的SQL语句在数据源上执行;调用函数SQLSetPos实现记录集的定义更新;调用函数SQLBulkOperations实现数据的更新。

直接在数据源上执行SQL语句的方式,可以适用于任何的ODBC数据源,但是,对于后两种更新方式,有的数据源并不支持,应用程序可以调用函数SQLGetInfo确定数据源是否支持这两种方式。


定位更新和删除 

要使用定位更新和删除功能,要按如下顺序:

1)取回记录集:

2)定位到要进行更新或删除操作的行

3)执行更新或删除操作

 

参考如下的代码:

#define ROWS 20#define STATUS_LEN 6

SQLCHAR szStatus[ROWS][STATUS_LEN], szReply[3];

SQLINTEGER cbStatus[ROWS], cbOrderID;

SQLUSMALLINT rgfRowStatus[ROWS];

SQLUINTEGER sOrderID, crow = ROWS, irow;

SQLHSTMT hstmtS, hstmtU;

SQLSetStmtAttr(hstmtS, SQL_ATTR_CONCURRENCY, (SQLPOINTER) SQL_CONCUR_ROWVER, 0);

SQLSetStmtAttr(hstmtS, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER) SQL_CURSOR_KEYSET_DRIVEN, 0);

SQLSetStmtAttr(hstmtS, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) ROWS, 0);

SQLSetStmtAttr(hstmtS, SQL_ATTR_ROW_STATUS_PTR, (SQLPOINTER) rgfRowStatus, 0);

SQLSetCursorName(hstmtS, "C1", SQL_NTS);

SQLExecDirect(hstmtS, "SELECT ORDERID, STATUS FROM ORDERS ", SQL_NTS);

SQLBindCol(hstmtS, 1, SQL_C_ULONG, &sOrderID, 0, &cbOrderID);

SQLBindCol(hstmtS, 2, SQL_C_CHAR, szStatus, STATUS_LEN, &cbStatus);

while ((retcode == SQLFetchScroll(hstmtS, SQL_FETCH_NEXT, 0)) != SQL_ERROR) {

if (retcode == SQL_NO_DATA_FOUND) break;

for (irow = 0; irow < crow; irow++) {

if (rgfRowStatus[irow] != SQL_ROW_DELETED)

printf("%2d %5d %*s\n", irow+1, sOrderID, NAME_LEN-1, szStatus[irow]);

} while (TRUE) { printf("\nRow number to update?");

gets(szReply); irow = atoi(szReply);

if (irow > 0 && irow <= crow) { printf("\nNew status?");

gets(szStatus[irow-1]);

SQLSetPos(hstmtS, irow, SQL_POSITION, SQL_LOCK_NO_CHANGE);

SQLPrepare(hstmtU,

"UPDATE ORDERS SET STATUS=? WHERE CURRENT OF C1", SQL_NTS);

SQLBindParameter(hstmtU, 1, SQL_PARAM_INPUT,

SQL_C_CHAR, SQL_CHAR,

STATUS_LEN, 0, szStatus[irow], 0, NULL);

SQLExecute(hstmtU); } else if (irow == 0) { break; }

}

}


用SQLBulkOperations()更新数据 

函数SQLBulkOperations的操作是基于当前行集的,在调用函数SQLBulkOperations之前,必须先调用函数SQLFetch或SQLFetchScroll获取行集,然后,再执行修改或删除操作。

 

函数格式:

SQLRETURN SQLBulkOperations(SQLHSTMT StatementHandle,

SQLUSMALLINT Operation);

 

参数:

StatementHandle 参数句柄

Operation 标识执行的操作类型,可以是以下几种之一:

SQL_ADD

SQL_UPDATE_BY_BOOKMARK

SQL_DELETE_BY_BOOKMARK

SQL_FETCH_BY_BOOKMARK

 

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NEED_DATA, SQL_STILL_EXECUTING, SQL_ERROR, or SQL_INVALID_HANDLE

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

取回查询结果

绑定列

从数据源取回的数据存放在应用程序定义的变量中,因此,我们必须首先分配与记录集中字段相对应的变量,然后通过函数SQLBindCol将记录字段同程序变量绑定在一起。对于长记录字段,可以通过调用函数SQLGetData()直接取回数据。

绑定字段可以根据自己的需要,全部绑定,也可以绑定其中的某几个字段。

记录集中的字段可以在任何时候绑定,但是,新的绑定只有当下一次从数据源中取数据时执行的操作才有郊,而不会对已经取回的数据产生影响。

函数格式:

SQLRETURN SQLBindCol(SQLHSTMT StatementHandle,

SQLUSMALLINTColumnNumber, SQLSMALLINT TargetType,

SQLPOINTERTargetValuePtr,SQLINTEGERBufferLength,

SQLINTEGER *StrLen_or_IndPtr);

参数:

StatementHandle 语句句柄

ColumnNumber 标识要绑定的列号。数据列号从0开始升序排列,其中第0列用作书签。如果没有使用书签,则列号从1开始。

TargetType 数据类型

TargetValuePtr 绑定到数据字段的缓冲区的地址

BufferLength 缓冲区长度

StrLen_or_IndPtr 指向绑定数据列使用的长度的指针.

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

SQLFetch()

函数SQLFetch用于将记录集中的下一行变为当前行,并把所有捆绑过的数据字段的数据拷贝到相应的缓冲区。

函数格式:

SQLRETURN SQLFetch(SQLHSTMT StatementHandle)

参数:

StatementHandle 语句句柄

返回值:

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA, SQL_STILL_EXECUTING, SQL_ERROR, or SQL_INVALID_HANDLE.

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

光标

应用程序获取数据是通过光标(Cursor)来实现的,在ODBC中,主要有三种类型的光标:


单向光标: 

单向光标只能向前移动,要返回记录集的开始位置,程序必须先关闭光标,再打开光标,它对于只需要浏览一次的应用非常有用,而且效率很高。对于需要光标前后移动的,单向光村不适用。


可滚动光标 

可滚动光标通常基于图形用户界面的程序,用户通过屏幕向前或向后滚动,浏览记录集中的数据。


块光标 

所谓块光标,可以理解为执行多行的光标,它所指向的行称为行集。对于网络环境下的应用,使用块光标可以在一定程序上减轻网络负载。

要使用块光标,应完成以下工作:

1)设定行集大小

2)绑定行集缓冲区

3)设定语句句柄属性

4)取行行集结果

由于块光标返回多行记录,应用程序必须把这多行的数据绑定到某些数据组中,这些数据称为行集缓冲区。绑定方式有两种:列方式和行方式。


ODBC光标库 

有些应用程序不支持可滚动光标和块光标,ODBC SDK 提供了一个光标库(ODBCCR32.DLL),在应用程序中可通过设置连接属性(SQL_STTR_ODBC_CURSORS)激活光标库。

 

参考如下代码:

#define NAME_LEN 50

#define PHONE_LEN 10

SQLCHAR szName[NAME_LEN], szPhone[PHONE_LEN];

SQLINTEGER sCustID, cbName, cbCustID, cbPhone;

SQLHSTMT hstmt;

SQLRETURN retcode;retcode = SQLExecDirect(hstmt,

"SELECT CUSTID, NAME, PHONE FROM CUSTOMERS ORDER BY 2, 1, 3", 

SQL_NTS);

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

/* Bind columns 1, 2, and 3 */

SQLBindCol(hstmt, 1, SQL_C_ULONG, &sCustID, 0, &cbCustID);

SQLBindCol(hstmt, 2, SQL_C_CHAR, szName, NAME_LEN, &cbName);

SQLBindCol(hstmt, 3, SQL_C_CHAR, szPhone, PHONE_LEN, &cbPhone);

/* Fetch and print each row of data. On */

/* an error, display a message and exit. */ while (TRUE) {

retcode = SQLFetch(hstmt);

if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO) {

show_error(); }

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){

fprintf(out, "%-*s %-5d %*s", NAME_LEN-1, szName,

sCustID, PHONE_LEN-1, szPhone); } else { break; }

}

}

2.25 断开同数据源的连接

当完成对数据库操作后,就可以调用SQLDisconnect函数关闭同数据源连接。

当该句柄的事务操作还未完成时,应用程序调用SQLDisconnect,这时,驱动器返回SQLSTATE 25000,表明此次事务没有变化且连接还打开。

在应用程序调用SQLDisconnect之前没有释放与之相连的描述时,当成功的与数据源断开连接后,驱动器自动释放与之相连的参数、描述器等。但当与之相连的某一个参数还在执行异步操作时,SQLDisConnect返回SQL_ERROR,且SQLSTATE的值置为HY010.

2.26 释放ODBC环境

最后一步就是释放ODBC环境参数了。

2.3 ODBC API编程总结

关系数据库的研究与应用是当今计算机界最活跃的领域之一,各种数据库产品行行色色,各有千秋;这种情况一方面给用户带来了好处,另一方面又给应用程序的移植带来了困难。尤其是在客户机/服务器体系结构中,当用户要从客户机端访问不同的服务器,而这些服务器的数据库系统又各不相同,数据库之间的互连访问就成为一个难题,因此,微软公司提出了ODBC。由于ODBC思想上的先进性及其微软公司的开放策略,ODBC现在已经成为事实上的工业标准,它是目前数据库应用方面很多问题强有力的解决方案,正逐步成为Windows平台上的标准接口。

ODBC是一种用来在相关或不相关的数据库管理系统(DBMS)中存取数据的标准应用程序设计接口(API)。它的基本思想是为用户提供简单、标准、透明、统一的数据库联接的公共编程接口,在各个厂家的支持下能为用户提供一致的应用开发界面,使应用程序独立于数据库产品,实现各种数据库之间的通信。开发厂商根据ODBC的标准去实现底层的驱动程序,它对用户是透明的。

作为一种数据库联接的标准技术,ODBC有以下几个主要特点:

1·ODBC是一种使用SQL的程序设计接口;

2·ODBC的设计是建立在客户机/服务器体系结构基础之上的;

3·ODBC使应用程序开发者避免了与数据源联接的复杂性;

4·ODBC的结构允许多个应用程序访问多个数据源,即应用程序与数据源的关系是多对多的关系。

 

3、 使用MFC访问ODBC数据源

3.1 概述

VisualC++的MFC类库定义了几个数据库类。在利用ODBC编程时,经常要使用到CDatabase(数据库类),CRecordSet(记录集类)和CRecordView(可视记录集类)。 其中: 

CDatabase类对象提供了对数据源的连接,通过它你可以对数据源进行操作。

CRecordView类对象能以控制的形式 显示数据库记录。这个视图是直接连到一个CRecordSet对象的表视图。

CRecordSet类对象提供了从数据源 中提取出的记录集。CRecordSet对象通常用于两种形式: 动态行集(dynasets)和快照集(snapshots)。动态行集能保 持与其他用户所做的更改保持同步。快照集则是数据的一个静态视图。每一种形式在记录集被打开时都提供一组记录,所不同的是,当你在一个动态行集里滚 动到一条记录时,由其他用户或是你应用程序中的其他记录集对该记录所做的更改会相应地显示出来。

 

Visual C++提供了几种记录集,可以用来定制应用程序的工作方式。查看这些不同选项的最快方式要兼顾速度和特征。你会发现,在很多情况下,如果想添加特征,就必须付出程序执行速度降低的代价。下面告诉你一些可以自由支配的记录集选项。更重要的是,要告诉你从这个选项可以获得更快的速度还是更多的特征。

1、Snapshot(快照) 这个选项要Visual C++在一次快照中下载整个查询。换言之,及时快速地给数据库内容拍照,并把它作为未来工作的基础。这种方法有三个缺点。第一,你看不到别人在网络上做的更新,这可能意味着你的决定是建立在老信息的基础上。第二,一次就下载所有这些记录,这意味着在下载期间给网络增加了沉重的负担。第三,记录下载时用户会结束等待,这意味着网络的呼叫性能变得更低。然而这种方法也有两个优点。第一,记录一旦被下载,该工作站所需的网络活动几乎就没有了棗这为其它请求释放了带宽。总之,你会看到网络的吞吐量增大了。第二,因为所有被申请的记录都在用户的机器上,所以用户实际上会得到应用程序更佳的总体性能。你可能想把快照的方法限制在较小的数据库上使用,原因在于快照适用于用户请求信息而不适用于数据编辑会话。

2、Dynaset(动态集) 使用这个选项时,Visual C++创建指向所请求的每个记录的实际指针。另外,只有填充屏幕时实际需要的记录是从服务器上下载来的。这种方法的好处很明显。几乎马上就能在屏幕上看到记录。而且还会看到其它用户对数据库所做的更改。最后,其它用户也会看到你做的更改,因为动态集在你更改记录时被上载到服务器上。很明显,这种方法要求对服务器的实时访问,它减小了网络总吞吐量并降低了应用程序的性能。这个选项适合于创建用户要花费很多时间来编辑数据的应用程序。同时,它也是大型数据库的最佳选择,原因在于只需下载用户实际需要的信息。 

3.2 应用ODBC编程

可以应用AppWizard来建立一个ODBC的应用程序框架,也可以直接使用ODBC来进行数据库编程,这时,应包括头文件afxdb.h。

应用ODBC编程两个最重要的类是CDatabase和CRecordSet,但在应用程序中,不应直接使用CRecordSet类,而必须从CRecordSet类产生一个导出类,并添加相应于数据库表中字段的成员变量。随后,重载CRecordset类的成员函数DoFieldExchange,该函数通过使用RFX函数完成数据库字段与记录集域数据成员变量的数据交换,RFX函数同对话框数据交换(DDX)机制相类似,负责完成数据库与成员变量间的数据交换。

 

下面举例说明在VisualC++环境中ODBC 的编程技巧:

3.21 数据库连接

在CRecordSet类中定义了一个成员变 量m_pDatabase:

CDatabase *m_pDatabase;

它是指向对象数据库类的指针。如果在CRecordSet类对象调用Open()函数之前,将一个已经打开的CDatabase类对象指针传给m_pDatabase,就能共享相同 的CDatabase类对象。如:

 

CDatabase m_db;

CRecordSet m_set1,m_set2;

m_db.Open(_T("Super_ES")); // 建 立ODBC 连 接

m_set1.m_pDatabase=&m_db; //m_set1 复 用m_db 对 象

m_set2.m_pDatabse=&m_db; // m_set2 复 用m_db 对 象

或如下:


Cdatabase db;

db.Open(“Database”); //建立ODBC连接

CrecordSet m_set(&db); //构造记录集对象,使数据库指向db

3.22 查询记录

查询记录使用CRecordSet::Open()和 CRecordSet::Requery()成员函数。在使用CRecordSet类对象之前,必须使用 CRecordSet::Open()函数来获得有效的记录集。一旦已经使用过CRecordSet::Open() 函数,再次查询时就可以应用CRecordSet::Requery()函数。在调用CRecordSet::Open()函数时,如果已经将一个已经打开的CDatabase 对象指针传给CRecordSet类对象的m_pDatabase成员变量,则使 用该数据库对象建立ODBC连接;否则如果m_pDatabase为空指 针,就新建一个CDatabase类对象并使其与缺省的数据源 相连,然后进行CRecordSet类对象的初始化。缺省数据源 由GetDefaultConnect()函数获得。你也可以提供你所需要的SQL 语句,并以它来调用CRecordSet::Open()函数,例如:

m_Set.Open(AFX_DATABASE_USE_DEFAULT,strSQL);

如果没有指定参数,程序则使 用缺省的SQL语句,即对在GetDefaultSQL()函数中指定的SQL语 句进行操作:

 

CString CTestRecordSet::GetDefaultSQL()

{return _T("[BasicData],[MainSize]");}

对于GetDefaultSQL()函数返回的表名, 对应的缺省操作是SELECT语句,即:

SELECT * FROM BasicData,MainSize

 

查询过程中也可以利用CRecordSet的 成员变量m_strFilter和m_strSort来执行条件查询和结果排序。m_strFilter 为过滤字符串,存放着SQL语句中WHERE后的条件串;m_strSort 为排序字符串,存放着SQL语句中ORDERBY后的字符串。 如:

m_Set.m_strFilter="TYPE='电动机'";

m_Set.m_strSort="VOLTAGE";

m_Set.Requery();

对应的SQL语句为:

SELECT * FROM BasicData,MainSize 

WHERE TYPE='电动机'

ORDER BY VOLTAGE

除了直接赋值给m_strFilter以外,还 可以使用参数化。利用参数化可以更直观,更方便地 完成条件查询任务。使用参数化的步骤如下:

 

(1).声明参变量:

 

Cstring p1;

Float p2;

(2).在构造函数中初始化参变量

p1=_T("");

p2=0.0f;

m_nParams=2;

(3).将参变量与对应列绑定

pFX->SetFieldType(CFieldExchange::param)

RFX_Text(pFX,_T("P1"),p1);

RFX_Single(pFX,_T("P2"),p2);

完成以上步骤之后就可以利用参变量进行条件查询了:

m_pSet->m_strFilter="TYPE=?ANDVOLTAGE=?";

m_pSet->p1="电动机";

m_pSet->p2=60.0;

m_pSet->Requery();

参变量的值按绑定的顺序替换 查询字串中的“?”适配符。

如果查询的结果是多条记录的 话,可以用CRecordSet类的函数Move(),MoveNext(),MovePrev(),MoveFirst() 和MoveLast()来移动光标。

3.23 增加记录

增加记录使用AddNew()函数,要求数据库必须是以允许增加的方式打开:

 

m_pSet->AddNew(); //在表的末尾增加新记录

m_pSet->SetFieldNull(&(m_pSet->m_type),FALSE);

m_pSet->m_type="电动机";

... //输入新的字段值

m_pSet-> Update(); //将新记录存入数据库

m_pSet->Requery(); //重建记录集

3.24 删除记录

直接使用Delete()函数,并且在调用Delete() 函数之后不需调用Update()函数:

 

m_pSet->Delete();

if(!m_pSet->IsEOF())

m_pSet->MoveNext();

else

m_pSet->MoveLast();

 

3.25 修改记录

修改记录使用Edit()函数:

 

m_pSet->Edit(); //修改当前记录

m_pSet->m_type="发电机"; //修改当前记录字段值

...

m_pSet->Update(); //将修改结果存入数据库

m_pSet->Requery();

3.26 统计记录

统计记录用来统计记录集的总数。可以先声明一个CRecordset对象m_pSet。再绑定一个变量m_lCount,用来统计记录总数。执行如下语句:

m_pSet->Open(“Select Count(*) from 表名 where 限定条件”);

RecordCount=m_pSet->m_lCount;

m_pSet->Close();

RecordCount即为要统计的记录数。

或如下:

CRecordset m_Set(&db); //db 为CDatabase对象

CString strValue;

m_Set.Open(Select count(*) from 表名 where 限定条件”);

m_pSet.GetFieldValue((int)0,strValue);

long count=atol(strValue);

m_set.Close();

count为记录总数。

3.27 执行SQL语句

虽然通过CRecordSet类,我们可以完成 大多数的查询操作,而且在CRecordSet::Open()函数中也可以 提供SQL语句,但是有的时候我们还想进行一些其他操 作,例如建立新表,删除表,建立新的字段等等,这 时就需要使用到CDatabase类的直接执行SQL语句的机制。通 过调用CDatabase::ExecuteSQL()函数来完成SQL语句的直接执行:

如下代码所示

BOOL CDB::ExecuteSQLAndReportFailure(const CString& strSQL)

{

TRY

{

m_pdb->ExecuteSQL(strSQL); //直接执行SQL语句

}

CATCH (CDBException,e)

{

CString strMsg;

strMsg.LoadString(IDS_EXECUTE_SQL_FAILED);

strMsg+=strSQL;

return FALSE;

}

END_CATCH 

return TRUE;

}

应当指出的是,由于不同DBMS提 供的数据操作语句不尽相同,直接执行SQL语句可能会破坏软件的DBMS无关性,因此在应用中应当慎用此类操作。

3.28 注意

从CRecordSet导出的类中如果包含DataTime类型的数据,在VC中是用CTime类型来替代的,这时,构造函数没有赋于缺省值。这时,我们应当手工赋值。如下所示:

CTime m_time;

m_time=NULL;

3.3 总结

VisualC++中的ODBC类库可以帮助程序员完成绝大多数的数据库操作。利用ODBC技术使得程序员从具体的DBMS中解脱出来,从而极大的减少了软件开发的工作量,缩短开发周期,提高了效率和软件的可靠性。

4、 使用DAO 

4.1 概述

Visual C++提供了对DAO的封装,MFC DAO类封装了DAO(数据库访问对象)的大部分功能,从面Visual C++程序就可以使用Visual C++提供的MFC DAO类方便的访问Microsoft Jet 数据库,编制简洁、有Visaul C++特色的数据库应用程序。

数据库访问对象(DAO)提供了一种通过程序代码创建和操纵数据库的机制。多个DAO对象构成一个体系结构,在这个结构里,各个DAO对象协同工作。DAO支持以下四个数据库选项:


打开访问数据库(MDB文件)——MDB文件是一个自包含的数据库,它包括查询定义、安全信息、索引、关系,当然还有实际的数据表。用户只须指定MDB文件的路径名。 


直接打开ODBC数据源——这里有一个很重要的限制。不能找开以Jet引擎作为驱动程序的ODBC数据源;只可以使用具有自己的ODBC驱动程序DLL的数据源。 


用Jet引擎找开ISAM型(索引顺序访问方法)数据源(包括dBase,FoxPro,Paradox,Btrieve,Excel或文本文件)——即使已经设置了ODBC数据源,要用Jet引擎来访问这些文件类型中的一种,也必须以ISAM型数据源的方式来找开文件,而不是以ODBC数据源的方式。 


给ACCESS数据库附加外部表——这实际上是用DAO访问ODBC数据源的首选方法。首先使用ACCESS把ODBC表添加到一个MDB文件上,然后依照第一选项中介绍的方法用DAO找开这个MDB文件就可以了。用户也可以用ACCESS把IASM文件附加到一个MDB文件上。 

4.2 应用DAO编程

4.21 打开数据库

CDaoWorkspace对象代表一个DAO Workspace对象,在MFC DAO体系结构中处于最高处,定义了一个用户的同数据库的会话,并包含打开的数据库,负责完成数据库的事务处理。我们可以使用隐含的workspace对象。

CDaoDatabase对象代表了一个到数据库的连接,在MFC中,是通过CDaoDatabase封装的。

在构造CDaoDatabase对象时,有如下两种方法:


创建一个CDaoDatabase对象,并向其传递一个指向一个已经找开的CdaoWorkspace对象的指针。 


创建一个CDaoDatabase对象,而不明确地指定使用的workspace,此时,MFC将创建一个新的临时的CDaoWorkspace对象。 

如下代码所示:

CDaoDatabase db;

db.Open(“test.mdb”,FALSE,FALSE,_T(“”);

其中参数一包括要打开的文件的全路径名。

4.22 查询记录

一个DAO recordset对象,代表一个数据记录的集合,该集合是一个库表或者是一个查询的运行结果中的全部记录。CDaoRecorset对象有三种类型:表、动态集、快照。

通常情况下,我们在应用程序中可以使用CDaoRecordset的导出类,这一般是通过ClassWizard或AppWizard来生成的。但我们也可以直接使用CDaoRecordset类生成的对象。此时,我们可以动态地绑定recordset对象的数据成员。

如下代码所示:

COleVariant var;

long id;

CString str;

CDaoRecordset m_Set(&db);

m_Set.Open(“查询的SQL语句”);

while(!m_Set.IsEOF())

{

/*

处理

m_Set.GetFieldValue(“ID”,var);

id=V_I4(var);

m_Set.GetFieldValue(“Name”,var);

str=var.pbVal;

*/

m_Set.MoveNext();

}

m_Set.Close();

4.23 添加记录

添加记录用AddNew函数,此时用SetFieldValue来进行赋值。

如下代码所示:

m_pDaoRecordset->AddNew ();

sprintf(strValue,"%s",>m_UserName );

m_pDaoRecordset->SetFieldValue ("UserName",strValue);

sprintf(strValue,"%d",m_PointId );

m_pDaoRecordset->SetFieldValue ("PointId",strValue);

dataSrc.SetDateTime (m_UpdateTime .GetYear ),m_UpdateTime .GetMonth ),m_UpdateTime .GetDay (),

m_UpdateTime .GetHour (),m_UpdateTime .GetMinute (),m_UpdateTime .GetSecond ());

valValue=dataSrc;

m_pDaoRecordset->SetFieldValue ("UpdateTime",valValue);

sprintf(strValue,"%f",m_pRecordset->m_OldValue );

m_pDaoRecordset->SetFieldValue ("OldValue",strValue);

sprintf(strValue,"%f",m_pRecordset->m_NewValue );

m_pDaoRecordset->SetFieldValue ("NewValue",strValue);

m_pDaoRecordset->Update ();

此时,要注意,日期时间型数据要用SetDataTime函数来赋值,这里面要用到COleVariant类型数据,具体用法可以参考有关帮助。

4.24 修改记录

修改记录用Edit()函数,把记录定位到要修改的位置,调用Edit函数,修改完成后,调用Update函数。

如下代码所示:

m_Set.Edit();

m_Set.SetFieldValue(“列名”,”字符串”);

m_Set.Update();

4.25 删除记录

删除记录用Delete()函数,使用后不需调用Update()函数。

4.26 统计记录

可以使用如下代码来统计记录数:

COleVariant varValue;

CDaoRecordset m_Set(&db);

m_Set.Open(dbOpenDynaset,”SQL语句”);

varValue=m_Set.GetFieldValue(0);

m_lMaxCount=V_I4(&varValue);

m_Set.Close();

如果是统计一张表中总记录,可以使用CDaoTableDef对象,如下代码所示:

CDaoTableDef m_Set(&gUseDB);

Count=m_Set.GetRecordCount();

m_Set.Close();

不能用CDaoRecordset对象的GetRecordCount()来取得记录数。

4.3 总结

使用DAO技术可以便我们方便的访问Microsoft Jet引擎数据库,由于Microsoft Jet不支持多线程,因此,必须限制调用到应用程序主线程的所有DAO。

5 使用OLE DB

5.1 概述

OLE DB的存在为用户提供了一种统一的方法来访问所有不同种类的数据源。OLE DB可以在不同的数据源中进行转换。利用OLE DB,客户端的开发人员在进行数据访问时只需把精力集中在很少的一些细节上,而不必弄懂大量不同数据库的访问协议。

OLE DB是一套通过COM接口访问数据的ActiveX接口。这个OLE DB接口相当通用,足以提供一种访问数据的统一手段,而不管存储数据所使用的方法如何。同时,OLE DB还允许开发人员继续利用基础数据库技术的优点,而不必为了利用这些优点而把数据移出来。

5.2 使用ATL使用OLE DB数据使用程序

由于直接使用OLE DB的对象和接口设计数据库应用程序需要书写大量的代码。为了简化程序设计,Visual C++提供了ATL模板用于设计OLE DB数据应用程序和数据提供程序。

利用ATL模板可以很容易地将OLE DB与MFC结合起来,使数据库的参数查询等复杂的编程得到简化。MFC提供的数据库类使OLE DB的编程更具有面向对象的特性。Viual C++所提供用于OLE DB的ATL模板可分为数据提供程序的模板和数据使用程序的模板。

使用ATL模板创建数据应用程序一般有以下几步骤:


创建应用框架 


加入ATL产生的模板类 


在应用中使用产生的数据访问对象 

 


不用ATL使用OLE DB数据使用程序 

利用ATL模板产生数据使用程序较为简单,但适用性不广,不能动态适应数据库的变化。下面我们介绍直接使用MFC OLE DB类来生成数据使用程序。

模板的使用

OLE DB数据使用者模板是由一些模板组成的,包括如下一些模板,下面对一些常用类作一些介绍。


会话类 

CDataSource类

CDataSource类与OLE DB的数据源对象相对应。这个类代表了OLE DB数据提供程序和数据源之间的连接。只有当数据源的连接被建立之后,才能产生会话对象,可以调用Open来打开数据源的连接。

CSession类

CSession所创建的对象代表了一个单独的数据库访问的会话。一个用CDataSource类产生的数据源对象可以创建一个或者多个会话,要在数据源对象上产生一个会话对象,需要调用函数Open()来打开。同时,会话对象还可用于创建事务操作。

CEnumeratorAccessor类

CEnumeratorAccessor类是用来访问枚举器查询后所产生的行集中可用数据提供程序的信息的访问器,可提供当前可用的数据提供程序和可见的访问器。


访问器类 

CAcessor类

CAccessor类代表与访问器的类型。当用户知道数据库的类型和结构时,可以使用此类。它支持对一个行集采用多个访问器,并且,存放数据的缓冲区是由用户分配的。

CDynamicAccessor类

CDynamicAccessor类用来在程序运行时动态的创建访问器。当系统运行时,可以动态地从行集中获得列的信息,可根据此信息动态地创建访问器。

CManualAccessor类

CManualAccessor类中以在程序运行时将列与变量绑定或者是将参数与变量捆定。


行集类 

CRowSet类

CRowSet类封装了行集对象和相应的接口,并且提供了一些方法用于查询、设置数据等。可以用Move()等函数进行记录移动,用GetData()函数读取数据,用Insert()、Delete()、SetData()来更新数据。

CBulkRowset类

CBulkRowset类用于在一次调用中取回多个行句柄或者对多个行进行操作。

CArrayRowset类

CArrayRowset类提供用数组下标进行数据访问。


命令类 

CTable类

CTable类用于对数据库的简单访问,用数据源的名称得到行集,从而得到数据。

CCommand类

CCommand类用于支持命令的数据源。可以用Open()函数来执行SQL命令,也可以Prepare()函数先对命令进行准备,对于支持命令的数据源,可以提高程序的灵活性和健壮性。

在stdafx.h头文件里,加入如下代码。

#include <atlbase.h>

extern CComModule _Module;

#include <atlcom.h>

#include <atldbcli.h>

#include <atldbsch.h> // if you are using schema templates

在stdafx.cpp文件里,加入如下代码。

#include <atlimpl.cpp>

CComModule _Module;

决定使用何种类型的存取程序和行集。

获取数据

在打开数据源,会话,行集对象后就可以获取数据了。所获取的数据类型取决于所用的存取程序,可能需要绑定列。按以下步骤。


用正确的命令打开行集对象。 


如果使用CManualAccessor,在使用之前与相应列进行绑定。要绑定列,可以用函数GetColumnInfo,如下所示: 

// Get the column information

ULONG ulColumns = 0;

DBCOLUMNINFO* pColumnInfo = NULL;

LPOLESTR pStrings = NULL;

if (rs.GetColumnInfo(&ulColumns, &pColumnInfo, &pStrings) != S_OK)

AfxThrowOLEDBException(rs.m_pRowset, IID_IColumnsInfo);

struct MYBIND* pBind = new MYBIND[ulColumns];

rs.CreateAccessor(ulColumns, &pBind[0], sizeof(MYBIND)*ulColumns);

for (ULONG l=0; l<ulColumns; l++)

rs.AddBindEntry(l+1, DBTYPE_STR, sizeof(TCHAR)*40, &pBind[l].szValue, NULL, &pBind[l].dwStatus);

rs.Bind();


用while循环来取数据。在循环中,调用MoveNext来测试光标的返回值是否为S_OK,如下所示: 

while (rs.MoveNext() == S_OK)

{

// Add code to fetch data here

// If you are not using an auto accessor, call rs.GetData()

}


在while循环内,可以通过不同的存取程序获取数据。 


如果使用的是CAccessor类,可以通过使用它们的数据成员进行直接访问。如下所示: 


如果使用的是CDynamicAccessor 或CDynamicParameterAccessor 类,可以通过GetValue或GetColumn函数来获取数据。可以用GetType来获取所用数据类型。如下所示: 

while (rs.MoveNext() == S_OK)

{

// Use the dynamic accessor functions to retrieve your

// data

ULONG ulColumns = rs.GetColumnCount();

for (ULONG i=0; i<ulColumns; i++)

{

rs.GetValue(i);

}

}


如果使用的是CManualAccessor,可以指定自己的数据成员,绑定它们。就可以直接存取。如下所示: 

while (rs.MoveNext() == S_OK)

{

// Use the data members you specified in the calls to

// AddBindEntry.

wsprintf("%s", szFoo);

}

决定行集的数据类型

在运行时决定数据类型,要用动态或手工的存取程序。如果用的是手工存取程序,可以用GetColumnInfo函数得到行集的列信息。从这里可以得到数据类型。

5.4 总结

由于现在有多种数据源,,想要对这些数据进行访问管理的唯一途径就是通过一些同类机制来实现,如OLE DB。高级OLE DB结构分成两部分:客户和提供者。客户使用由提供者生成的数据。

就像其它基于COM的多数结构一样,OLE DB的开发人员需要实现很多的接口,其中大部分是模板文件。

当生成一个客户对象时,可以通过ATL对象向导指向一个数据源而创建一个简单的客户。ATL对象向导将会检查数据源并创建数据库的客户端代理。从那里,可以通过OLE DB客户模板使用标准的浏览函数。

当生成一个提供者时,向导提供了一个很好的开端,它们仅仅是生成了一个简单的提供者来列举某一目录下的文件。然后,提供者模板包含了OLE DB支持的完全补充内容。在这种支持下,用户可以创建OLE DB提供者,来实现行集定位策略、数据的读写以及建立书签。

6、 使用ADO

6.1 概述

ADO是ActiveX数据对象(ActiveX Data Object),这是Microsoft开发数据库应用程序的面向对象的新接口。ADO访问数据库是通过访问OLE DB数据提供程序来进行的,提供了一种对OLE DB数据提供程序的简单高层访问接口。

ADO技术简化了OLE DB的操作,OLE DB的程序中使用了大量的COM接口,而ADO封装了这些接口。所以,ADO是一种高层的访问技术。

ADO技术基于通用对象模型(COM),它提供了多种语言的访问技术,同时,由于ADO提供了访问自动化接口,所以,ADO可以用描述的脚本语言来访问VBScript,VCScript等。

 

6.2 在VC中使用ADO

可以使用VC6提供的ActiveX控件开发应用程序,还可以用ADO对象开发应用程序。使用ADO对象开发应用程序可以使程序开发者更容易地控制对数据库的访问,从而产生符合用户需求的数据库访问程序。

使用ADO对象开发应用程序也类似其它技术,需产生与数据源的连接,创建记录等步骤,但与其它访问技术不同的是,ADO技术对对象之间的层次和顺序关系要求不是太严格。在程序开发过程中,不必选建立连接,然后才能产生记录对象等。可以在使用记录的地方直接使用记录对象,在创建记录对象的同时,程序自动建立了与数据源的连接。这种模型有力的简化了程序设计,增强了程序的灵活性。下面讲述使用ADO对象进行程序设计的方法。

6.21 引入ADO库文件

使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下所示:

#define INITGUID

#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","EndOfFile")

#include "icrsint.h"

这行语句声明在工程中使用ADO,但不使用ADO的名字空间,并且为了避免冲突,将EOF改名为EndOfFile。

6.22 初始化ADO环境

在使用ADO对象之前必须先初始化COM环境。初始化COM环境可以用以下代码完成:

::CoInitialize(NULL);

在初始化COM环境后,就可以使用ADO对象了,如果在程序前面没有添加此代码,将会产生COM错误。

在使用完ADO对象后,需要用以下的代码将初始化的对象释放:

::CoUninitialize();

此函数清除了为ADO对象准备的COM环境。

6.23 接口简介

ADO库包含三个基本接口:

__ConnectionPtr接口、

__CommandPtr接口、

__RecordsetPtr接口,

 

__ConnectionPtr接口返回一个记录集或一个空指针。通常使用它来创建一个数据连接或执行一条不返回任何结果的SQL语句,如一个存储过程。用__ConnectionPtr接口返回一个记录集不是一个好的使用方法。通常同CDatabase一样,使用它创建一个数据连接,然后使用其它对象执行数据输入输出操作。

 

__CommandPtr接口返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储过程和SQL语句。在使用__CommandPtr接口时,可以利用全局__ConnectionPtr接口,也可以在__CommandPtr接口里直接使用连接串。如果只执行一次或几次数据访问操作,后者是比较好的选择。但如果要频繁访问数据库,并要返回很多记录集,那么,应该使用全局__ConnectionPtr接口创建一个数据连接,然后使用__CommandPtr接口执行存储过程和SQL语句。

 

__RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多的控制功能,如记录锁定,游标控制等。同__CommandPtr接口一样,它不一定要使用一个已经创建的数据连接,可以用一个连接串代替连接指针赋给__RecordsetPtr的connection成员变量,让它自己创建数据连接。如果要使用多个记录集,最好的方法是同Command对象一样使用已经创建了数据连接的全局—ConnectionPtr接口,然后使用__Recordse7tPtr执行存储过程和SQL语句。

6、24 使用ADO访问数据库

__ConnectionPtr是一个连接接口,首先创建一个__ConnectionPtr接口实例,接着指向并打开一个ODBC数据源或OLE DB数据提供者(Provider)。以下代码分别创建一个基于DSN和非DSN的数据连接。

 

  //使用__ConnectionPtr(基于DSN)

  __ConnectionPtr MyDb;

  MyDb.CreateInstance(__uuidof(Connection));

  MyDb-〉Open("DSN=samp;UID=admin;PWD=admin","","",-1);

  //使用—ConnectionPtr (基于非DSN)

  __ConnectionPtr MyDb;

  MyDb.CreateInstance(__uuidof(Connection));

MyDb.Open("Provider=SQLOLEDB;SERVER=server;DATABASE=samp;UID=admin;PWD=admin","","",-1);

 

//使用__RecordsetPtr执行SQL语句

  __RecordsetPtr MySet;

  MySet.CreateInstance(__uuidof(Recordset));

MySet-〉Open("SELECT * FROM some__table",  MyDb.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText);

现在我们已经有了一个数据连接和一个记录集,接下来就可以使用数据了。从以下代码可以看到,使用ADO的__RecordsetPtr接口,就不需要像DAO那样频繁地使用大而复杂的数据结构VARIANT,并强制转换各种数据类型了,这也是ADO的优点之一。假定程序有一个名称为m__List的ListBox控件,下面代码我们用__RecordsetPtr接口获取记录集数据并填充这个ListBox控件:

__variant__t Holder

  try{while(!MySet-〉adoEOF)

  { Holder = MySet-〉GetCollect("FIELD__1");

  if(Holder.vt!=VT__NULL)

  m__List.AddString((char)__bstr__t(Holder));

  MySet-〉MoveNext();} }

  catch(__com__error  e)

  { CString Error = e-〉ErrorMessage();

   AfxMessageBox(e-〉ErrorMessage());

  } catch(...)

  { MessageBox("ADO发生错误!");}

 

必须始终在代码中用try和catch来捕获ADO错误,否则ADO错误会使你的应用程序崩溃。当ADO发生运行错误时(如数据库不存在),OLE DB数据提供者将自动创建一个__com__error对象,并将有关错误信息填充到这个对象的成员变量。

 

6.25 类型转换

由于COM对象是跨平台的,它使用了一种通用的方法来处理各种类型的数据,因此CString 类和COM对象是不兼容的,我们需要一组API来转换COM对象和C++类型的数据。__vatiant__t和__bstr__t就是这样两种对象。它们提供了通用的方法转换COM对象和C++类型的数据。

6.3 在VB中使用ADO

ADO 提供执行以下操作的方式: 

1、连接到数据源。同时,可确定对数据源的所有更改是否已成功或没有发生。

2、指定访问数据源的命令,同时可带变量参数,或优化执行。

3、执行命令。

3、如果这个命令使数据按表中的行的形式返回,则将这些行存储在易于检查、操作或更改的缓存中。

4、适当情况下,可使用缓存行的更改内容来更新数据源。

5、提供常规方法检测错误(通常由建立连接或执行命令造成)。 

在典型情况下,需要在编程模型中采用所有这些步骤。但是,由于 ADO 有很强的灵活性,所以最后只需执行部分模块就能做一些有用的工作。

 

以下元素是 ADO 编程模型中的关键部分: 

6.31 连接

通过“连接”可从应用程序访问数据源,连接是交换数据所必需的环境。对象模型使用 Connection 对象使连接概念得以具体化。

“事务”用于界定在连接过程中发生的一系列数据访问操作的开始和结束。ADO 可明确事务中的操作造成的对数据源的更改或者成功发生,或者根本没有发生。如果取消事务或它的一个操作失败,则最终的结果将仿佛是事务中的操作均未发生,数据源将会保持事务开始以前的状态。对象模型无法清楚地体现出事务的概念,而是用一组 Connection 对象方法来表示。ADO 从 OLE DB 提供者访问数据和服务。Connection 对象用于指定专门的提供者和任意参数。

 

6.32 命令

通过已建立的连接发出的“命令”可以某种方式来操作数据源。一般情况下,命令可以在数据源中添加、删除或更新数据,或者在表中以行的格式检索数据。对象模型用 Command 对象来体现命令概念。使用 Command 对象可使 ADO 优化命令的执行。


参数 

通常,命令需要的变量部分即“参数”可以在命令发布之前进行更改。例如,可重复发出相同的数据检索命令,但每一次均可更改指定的检索信息。

参数对与函数活动相同的可执行命令非常有用,这样就可知道命令是做什么的,但不必知道它如何工作。例如,可发出一项银行过户命令,从一方借出贷给另一方。可将要过户的款额设置为参数。

对象模型用 Parameter 对象来体现参数概念。

6.33 记录集

如果命令是在表中按信息行返回数据的查询(行返回查询),则这些行将会存储在本地。

对象模型将该存储体现为 Recordset 对象。但是,不存在仅代表单独一个 Recordset 行的对象。

记录集是在行中检查和修改数据最主要的方法。

6.34 字段

一个记录集行包含一个或多个“字段”。如果将记录集看作二维网格,字段将排列构成“列”。每一字段(列)都分别包含有名称、数据类型和值的属性,正是在该值中包含了来自数据源的真实数据。

对象模型以 Field 对象体现字段。

要修改数据源中的数据,可在记录集行中修改 Field 对象的值,对记录集的更改最终被传送给数据源。作为选项,Connection 对象的事务管理方法能够可靠地保证更改要么全部成功,要么全部失败。

6.35 错误

错误随时可在应用程序中发生,通常是由于无法建立连接、执行命令或对某些状态(例如,试图使用没有初始化的记录集)的对象进行操作。

对象模型以 Error 对象体现错误。

任意给定的错误都会产生一个或多个 Error 对象,随后产生的错误将会放弃先前的 Error 对象组。

6.36 属性

每个 ADO 对象都有一组唯一的“属性”来描述或控制对象的行为。

属性有内置和动态两种类型。内置属性是 ADO 对象的一部分并且随时可用。动态属性则由特别的数据提供者添加到 ADO 对象的属性集合中,仅在提供者被使用时才能存在。

 

对象模型以 Property 对象体现属性。

6.37 集合

ADO 提供“集合”,这是一种可方便地包含其他特殊类型对象的对象类型。使用集合方法可按名称(文本字符串)或序号(整型数)对集合中的对象进行检索。

ADO 提供四种类型的集合: 

Connection 对象具有 Errors 集合,包含为响应与数据源有关的单一错误而创建的所有 Error 对象。

Command 对象具有 Parameters 集合,包含应用于 Command 对象的所有 Parameter 对象。

Recordset 对象具有 Fields 集合,包含所有定义 Recordset 对象列的 Field 对象。

此外,Connection、Command、Recordset 和 Field 对象都具有 Properties 集合。它包含所有属于各个包含对象的 Property 对象。 

ADO 对象拥有可在其上使用的诸如“整型”、“字符型”或“布尔型”这样的普通数据类型来设置或检索值的属性。然而,有必要将某些属性看成是数据类型“COLLECTION OBJECT”的返回值。相应的,集合对象具有存储和检索适合该集合的其他对象的方法。

6.38 事件 

ADO 2.0 支持事件,事件是对某些操作将要或已经发生的通知。

有两类事件:ConnectionEvent 和 RecordsetEvent。Connection 对象产生 ConnectionEvent 事件,而 Recordset 对象则产生 RecordsetEvent 事件。

事件由事件处理程序例程处理,该例程在某个操作开始之前或结束之后被调用。

某些事件是成对出现的。开始操作前调用的事件名格式为 WillEvent(Will 事件),而操作结束后调用的事件名格式为 EventComplete(Complete 事件)。其余的不成对事件只在操作结束后发生。(其名称没有任何固定模式。)事件处理程序由状态参数控制。附加信息由错误和对象参数提供。

可以请求事件处理程序不接受第一次通知以后的任何通知。例如,可以选择只接收 Will 事件或 Complete 事件。

下面的代码显示了一个使用ADO的例子。

首先加入Microsoft ActiveX Data Object 2.0 Library引用。

Dim db As Connection

Set db = New Connection

db.CursorLocation = adUseClient

db.Open "PROVIDER=MSDASQL;DSN=TestDatabase", "sa", "", -1

Dim i As Long

Dim id As Long

Dim value As Single

Dim rst As New Recordset

Set rst = New Recordset

rst.Open "select * from 模拟量变化历史表", db, adOpenDynamic, adLockOptimistic

rst.MoveFirst

For i = 0 To rst.RecordCount - 1

id = rst.Fields("ID")

value=rst.Fields(“VALUE”)

rst.MoveNext

Next i

rst.Close

Set rst = Nothing

db.Close

6.4总结

ADO技术是访问数据库的新技术,具有易于使用、访问灵活、应用广泛的特点。用ADO访问数据源的特点可总结如下:


易于使用 

这是ADO技术的最重要的一个特征。由于ADO是高层应用,所以相对于OLE DB或者ODBC来说,它具有面向对象的特性。同时,在ADO的对象结构中,其对象之间的层次关系并不明显。相对于DAO等访问技术来讲,又不必关心对象的构造顺序和构造层次。对于要用的对象,不必选建立连接、会话等对象,只需直接构造即可,方便了应用程序的编制。


高速访问数据源 

由于ADO技术基于OLE DB,所以,它也继承了OLE DB访问数据库的高速性。


可以访问不同数据源 

ADO技术可以访问包括关系数据库和非关系数据库的所有文件系统。此特点使应用程序有很多的灵活性和通用性。


可以用于Microsoft ActiveX页 

ADO技术可以以ActiveX控件的形式出现,所以,可以被用于Microsoft ActiveX页,此特征可简化WEB页的编程。


程序占用内存少 

由于ADO是基于组件对象模型(COM)的访问技术,所以,用ADO产生的应用程序占用内存少。

7、 总结

要在访问数据时判断出应该使用哪一种技术,这并不容易。可能需要公用实用程序来处理多个数据库类型;部分数据可能出现在本地硬盘驱动器上,部分在网络上,还有一部分在主机上。甚至客户安装在设备上的产品也会使这种选择更加困难。例如,你所期待的ODBC支持级别也许依赖于所安装的Microsoft Office的版本,因为这个产品不提供ODBC支持。你还会发现,ADO类提供的对象和方法要比ODBC类多。ADO可以提供程序中绝对必须具有的一些特性棗例如,你会发现OLE-DB和ADO两者都支持DFX_Currency,但在ODBC中没有对应的功能,但你要想掌握它们也必须付出一定的努力。

选择OLE-DB或ODBC时,有几条一般的规则。因为ADO实际上只是OLE-DB的包装,所以这些规则也适用于它。下面提供一些基本的原则,可以用来帮助你决定选择OLE-DB还是ODBC。

非OLE环境 如果要访问支持ODBC的数据库,而该数据库又在不支持OLE的服务器上,那么ODBC是最好的选择。

非SQL环境 ODBC在处理SQL时非常出众。处理非SQL数据库时,OLE-DB则具有非常明显的优势。

OLE环境 对支持OLE的服务器来说,选择OLE-DB还是ODBC也许是希望各半。如果有ODBC驱动程序可供利用,那么使用ODBC是一个好主意;否则,就只有选择OLE-DB了。

所需的互操作性 如果需要可互操作的数据库部件,那么只有选择OLE-DB。

 

VCKBASE Online Help Journal No.8  

 


用Visual C++程序实现设置ODBC数据源
 
苏州供电局信息中心
唐一均

---- ODBC(Open Database Conectivity)即开放式数据库互联,作为Windows开放性结构的一个重要部分已经为很多的Windows程序员所熟悉,ODBC的工作依赖于数据库制造商提供的驱动程序,使用ODBC API的时候,Windows的ODBC管理程序,把数据库访问的请求传递给正确的驱动程序,驱动程序再使用SQL语句指示DBMS完成数据库访问工作,因此,ODBC的存在为我们开发应用数据库程序提供了非常强大的能力和灵活性。

---- 为了使ODBC能与数据库一起工作,必须把数据库注册到ODBC驱动程序管理器,这项工作可以通过定义一个DSN或数据源名字来完成。通常,我们只能手动打开系统控制面板,运行其中的ODBC数据源管理器,手工配置数据源,但是这项工作对用户而言过于复杂,我们必须考虑用程序替用户完成这些配置工作。

---- 因此许多程序员在发布自己编写的数据库软件时候都希望能有一个优秀的安装程序能够自动设置好ODBC数据源,虽然现在InstallShield等一些优秀的安装制作软件可以帮助我们实现此类功能,但毕竟缺少灵活,程序员不能完全控制它,事实上,我们完全可以自己编写一些程序实现此类功能,实现的方法有几种,一种办法是用程序修改Windows注册表,程序员可以用Windows API函数增改HKEY_LOCAL_MACHINE\Software\ODBC下的ODBC.INI中的键值,这种方法比较烦琐。我现在推荐一种在程序中使用ODBC API的方法,程序员可以在任何时候都可以用Visual C++编写的程序调用这些API函数来设置ODBC数据源。

---- 下面我用MFC写一个程序来演示如何实现这个功能:

---- 首先,打开Visual C++,在File菜单上选New,然后选定MFC AppWizard(exe)类的项目,Project name我们定为try,按下OK键,下一Step 1屏幕中选Dialog based,由于不必用到后面的选项,此时即可按下Finish键,结果系统将生成一个新的项目。完成上述工作后,在左侧Workspace窗口中,选择ResourceView,打开try resources中的Dialog资源,选择并打开IDD_TRY_DIALOG对话窗口,在Controls菜单窗口中点选按键图标,回到IDD_TRY_DIALOG对话窗口并点击此窗口,将生成一个名叫Button1的按键,选中此按键再按鼠标右键,在弹出式菜单上选Properties选项,在出现的对话框中把Caption项的Button1值改为Setup ODBC,关闭此对话框,再选中此按键按鼠标右键,选择ClassWizard,在出现的对话窗口中,Object Ids选IDC_BUTTON1,Messages中双击BN_CLICKED,此时弹出Add Member Function对话窗,Member function name是OnButton1,按OK键。在Member functions选项中双击onButton1 ON_IDC_BUTTION1:BN_CLICKED,在出现的void CTryDlg::OnButton1()函数中用以下ODBC API函数语句替换 //TODO: Add your control notification handler code here这条注释语句:

  SQLConfigDataSource(NULL,ODBC_ADD
_SYS_DSN,"Microsoft Access Driver (*.mdb)\0","
DSN=TryDB\0DBQ=D:\\Database\\
try.mdb\0DEFAULTDIR=D:\\DATABASE\0\0");
  '

---- 您可以根据您不同的设置需要修改上面的语句,SQLConfigDataSource一般有以下几个许可的参数:ODBC_ADD_DSN: 加入一个新的用户数据源,ODBC_CONFIG_DSN: 修改一个存在的用户数据源,ODBC_REMOVE_DSN: 删除一个存在的用户数据源,ODBC_ADD_SYS_DSN: 增加一个新的系统数据源,ODBC_CONFIG_SYS_DSN: 修改一个存在的系统数据源,ODBC_REMOVE_SYS_DSN: 删除一个存在的系统数据源,ODBC_REMOVE_DEFAULT_DSN: 删除省缺的数据源说明部分。需要注意的是,当我们使用SQLConfigDataSource ODBC API函数时必须声明包含系统的odbcinst.h头文件,所以我们再选择workspace窗口中FileView打开Header Files中try.h,在其中加入#include "odbcinst.h",如果不加入这个头文件,系统编译时就会显示undeclared identifier错误,在完成上述步骤后,假如我们立即编译并Link这个项目,会发现出现下面的错误:

tryDlg.obj : error LNK2001: unresolved
external symbol _SQLConfigDataSource@16
Debug/try.exe : fatal error LNK1120: 
1 unresolved externals

----  有些人可能因为找不出错误而放弃了,其实这是因为当我们使用SQLConfigDataSource 这个API函数时候必须用到odbccp32.dll,它是Microsoft提供的32位ODBC安装和管理的DLL,如果是16位必须用到odbcinst.dll,odbccp32.dll有一个import library,所以解决的办法就是把这个odbccp32.lib加到我们的项目中,我们可以打开Project系统菜单项,选Add to Project子菜单,在其中选Files项,打开VC安装目录下的\vc\lib\目录,文件类型选Library Files(.lib), 选择其中Odbccp32.lib后按OK键,然后重新编译即可通过,运行这个程序,将弹出对话窗,按下Setup ODBC按键,之后,您就可以通过控制面板的ODBC数据源管理器或注册表查看运行结果,您会发现,您的数据库已经成功的注册了。

---- 以上代码均在WIN98,VC5.0上编译通过,您可以灵活应用这些办法,让您编写的软件更易于安装维护和使用。

有任何问题欢迎写Email给我:tangyijun@163.net

 


©1997-2000 VCKBASE.COM All Rights Reserved.

Visual C++ 中 的ODBC 编 程

---- 摘 要:ODBC(Open Database Connectivity, 开 放 式 数 据 库 连 接), 是 一 种 用 来 在 相 关 或 不 相 关 的 数 据 库 管 理 系 统(DBMS) 中 存 取 数 据 的 标 准 应 用 程 序 接 口(API)。 本 文 给 出Windows 95 环 境 下 用Visual C++ 进 行ODBC 编 程 的 具 体 方 法 及 技 巧。

---- 关 键 字:ODBC,Visual C++,Windows 编 程。

---- 一 . 概 述

---- ODBC 是 一 种 使 用SQL 的 程 序 设 计 接 口。 使 用ODBC 让 应 用 程 序 的 编 写 者 避 免 了 与 数 据 源 相 联 的 复 杂 性。 这 项 技 术 目 前 已 经 得 到 了 大 多 数DBMS 厂 商 们 的 广 泛 支 持。

---- Microsoft Developer Studio 为 大 多 数 标 准 的 数 据 库 格 式 提 供 了32 位ODBC 驱 动 器。 这 些 标 准 数 据 格 式 包 括 有:SQL Server、Access、Paradox、dBase、FoxPro、Excel、Oracle 以 及Microsoft Text。 如 果 用 户 希 望 使 用 其 他 数 据 格 式, 用 户 需 要 相 应 的ODBC 驱 动 器 及DBMS。

---- 用 户 使 用 自 己 的DBMS 数 据 库 管 理 功 能 生 成 新 的 数 据 库 模 式 后, 就 可 以 使 用ODBC 来 登 录 数 据 源。 对 用 户 的 应 用 程 序 来 说, 只 要 安 装 有 驱 动 程 序, 就 能 注 册 很 多 不 同 的 数 据 库。 登 录 数 据 库 的 具 体 操 作 参 见 有 关ODBC 的 联 机 帮 助。

---- 二 .MFC 提 供 的ODBC 数 据 库 类

---- Visual C++ 的MFC 基 类 库 定 义 了 几 个 数 据 库 类。 在 利 用ODBC 编 程 时, 经 常 要 使 用 到 CDatabase( 数 据 库 类),CRecordSet( 记 录 集 类) 和CRecordView( 可 视 记 录 集 类)。 其 中:

---- CDatabase 类 对 象 提 供 了 对 数 据 源 的 连 接, 通 过 它 你 可 以 对 数 据 源 进 行 操 作。

---- CRecordSet 类 对 象 提 供 了 从 数 据 源 中 提 取 出 的 记 录 集。CRecordSet 对 象 通 常 用 于 两 种 形 式: 动 态 行 集(dynasets) 和 快 照 集(snapshots)。 动 态 行 集 能 保 持 与 其 他 用 户 所 做 的 更 改 保 持 同 步。 快 照 集 则 是 数 据 的 一 个 静 态 视 图。 每 一 种 形 式 在 记 录 集 被 打 开 时 都 提 供 一 组 记 录, 所 不 同 的 是, 当 你 在 一 个 动 态 行 集 里 滚 动 到 一 条 记 录 时, 由 其 他 用 户 或 是 你 应 用 程 序 中 的 其 他 记 录 集 对 该 记 录 所 做 的 更 改 会 相 应 地 显 示 出 来。

---- CRecordView 类 对 象 能 以 控 制 的 形 式 显 示 数 据 库 记 录。 这 个 视 图 是 直 接 连 到 一 个CRecordSet 对 象 的 表 视 图。

---- 三 . 应 用ODBC 编 程

---- 应 用Visual C++ 的AppWizard 可 以 自 动 生 成 一 个ODBC 应 用 程 序 框 架。 方 法 是: 打 开File 菜 单 的New 选 项, 选 取Projects, 填 入 工 程 名, 选 择MFC AppWizard (exe), 然 后 按AppWizard 的 提 示 进 行 操 作。 当AppWizard 询 问 是 否 包 含 数 据 库 支 持 时, 如 果 你 想 读 写 数 据 库, 那 么 选 定Database view with file support; 而 如 果 你 想 访 问 数 据 库 的 信 息 而 不 想 回 写 所 做 的 改 变, 那 么 选 定Database view without file support 选 项 就 比 较 合 适 了。 选 择 了 数 据 库 支 持 之 后Database Source 按 钮 会 激 活, 选 中 它 去 调 用Data Options 对 话 框。 在Database Options 对 话 框 中 会 显 示 已 向ODBC 注 册 的 数 据 库 资 源, 选 定 你 所 要 操 作 的 数 据 库, 如:Super_ES, 单 击OK 后 会 出 现Select Database Tables 对 话 框, 其 中 列 举 了 你 所 选 中 的 数 据 库 中 包 含 的 全 部 表, 选 择 你 希 望 操 作 的 表 后, 单 击OK。 在 选 定 了 数 据 库 和 数 据 表 之 后, 你 可 以 按 照 惯 例 继 续 进 行AppWizard 操 作。

---- 特 别 需 要 指 出 的 是: 在 生 成 的 应 用 程 序 框 架View 类( 如:CSuper_ESView) 中 包 含 一 个 指 向CSuper_ESSet 对 象 的 指 针m_pSet, 该 指 针 由AppWizard 建 立, 目 的 是 在 视 表 单 和 记 录 集 之 间 建 立 联 系, 使 得 记 录 集 中 的 查 询 结 果 能 够 很 容 易 地 在 视 表 单 上 显 示 出 来。 有 关m_pSet 的 详 细 用 法 可 以 参 见Visual C++ Online Book。

---- 程 序 与 数 据 语 言 建 立 联 系, 使 用CDatebase::OpenEx() 或CDatabase::Open() 函 数 来 进 行 初 始 化。 数 据 库 对 象 必 须 在 你 使 用 它 构 造 一 个 记 录 集 对 象 之 前 被 初 始 化。

---- 下 面 举 例 说 明 在Visual C++ 环 境 中ODBC 的 编 程 技 巧:

---- 1 . 查 询 记 录

---- 查 询 记 录 使 用CRecordSet::Open() 和CRecordSet::Requery() 成 员 函 数。 在 使 用CRecordSet 类 对 象 之 前, 必 须 使 用CRecordSet::Open() 函 数 来 获 得 有 效 的 记 录 集。 一 旦 已 经 使 用 过CRecordSet::Open() 函 数, 再 次 查 询 时 就 可 以 应 用CRecordSet::Requery() 函 数。 在 调 用CRecordSet::Open() 函 数 时, 如 果 已 经 将 一 个 已 经 打 开 的CDatabase 对 象 指 针 传 给CRecordSet 类 对 象 的m_pDatabase 成 员 变 量, 则 使 用 该 数 据 库 对 象 建 立ODBC 连 接; 否 则 如 果m_pDatabase 为 空 指 针, 就 新 建 一 个CDatabase 类 对 象 并 使 其 与 缺 省 的 数 据 源 相 连, 然 后 进 行CRecordSet 类 对 象 的 初 始 化。 缺 省 数 据 源 由GetDefaultConnect() 函 数 获 得。 你 也 可 以 提 供 你 所 需 要 的SQL 语 句, 并 以 它 来 调 用CRecordSet::Open() 函 数, 例 如:

                 Super_ESSet.Open(AFX_DATABASE_USE_DEFAULT,strSQL);

---- 如 果 没 有 指 定 参 数, 程 序 则 使 用 缺 省 的SQL 语 句, 即 对 在GetDefaultSQL() 函 数 中 指 定 的SQL 语 句 进 行 操 作:

         CString CSuper_ESSet::GetDefaultSQL()
{return _T("[BasicData],[MainSize]");}

---- 对 于GetDefaultSQL() 函 数 返 回 的 表 名, 对 应 的 缺 省 操 作 是SELECT 语 句, 即:

         SELECT * FROM BasicData,MainSize

---- 查 询 过 程 中 也 可 以 利 用CRecordSet 的 成 员 变 量m_strFilter 和m_strSort 来 执 行 条 件 查 询 和 结 果 排 序。m_strFilter 为 过 滤 字 符 串, 存 放 着SQL 语 句 中WHERE 后 的 条 件 串;m_strSort 为 排 序 字 符 串, 存 放 着SQL 语 句 中ORDER BY 后 的 字 符 串。 如:

                 Super_ESSet.m_strFilter="TYPE='电动机'";
                 Super_ESSet.m_strSort="VOLTAGE";
                 Super_ESSet.Requery();
          对应的SQL语句为:
                 SELECT * FROM BasicData,MainSize 
                 WHERE TYPE='电动机'
                 ORDER BY VOLTAGE

---- 除 了 直 接 赋 值 给m_strFilter 以 外, 还 可 以 使 用 参 数 化。 利 用 参 数 化 可 以 更 直 观, 更 方 便 地 完 成 条 件 查 询 任 务。 使 用 参 数 化 的 步 骤 如 下:

---- (1) . 声 明 参 变 量:

                 CString p1;
                 float p2;

---- (2) . 在 构 造 函 数 中 初 始 化 参 变 量

                 p1=_T("");
                 p2=0.0f;
                 m_nParams=2;

---- (3) . 将 参 变 量 与 对 应 列 绑 定

                 pFX- >SetFieldType(CFieldExchange::param)
                 RFX_Text(pFX,_T("P1"),p1);
                 RFX_Single(pFX,_T("P2"),p2);

---- 完 成 以 上 步 骤 之 后 就 可 以 利 用 参 变 量 进 行 条 件 查 询 了:

                 m_pSet- >m_strFilter="TYPE=? AND VOLTAGE=?";
                 m_pSet- >p1=" 电 动 机";
                 m_pSet- >p2=60.0;
                 m_pSet- >Requery();

---- 参 变 量 的 值 按 绑 定 的 顺 序 替 换 查 询 字 串 中 的"?" 适 配 符。

---- 如 果 查 询 的 结 果 是 多 条 记 录 的 话, 可 以 用CRecordSet 类 的 函 数Move(),MoveNext(),MovePrev(),MoveFirst() 和MoveLast() 来 移 动 光 标。

---- 2 . 增 加 记 录

---- 增 加 记 录 使 用AddNew() 函 数, 要 求 数 据 库 必 须 是 以 允 许 增 加 的 方 式 打 开:

                 m_pSet- >AddNew();        //在表的末尾增加新记录
                 m_pSet- >SetFieldNull(&(m_pSet- >m_type), FALSE);
m_pSet- >m_type=" 电 动 机";
                          ...      //输入新的字段值
                 m_pSet- >        Update();        //将新记录存入数据库
                 m_pSet- >Requery();       //重建记录集

---- 3 . 删 除 记 录

---- 直 接 使 用Delete() 函 数, 并 且 在 调 用Delete() 函 数 之 后 不 需 调 用Update() 函 数:

                 m_pSet- >Delete();
                 if (!m_pSet- >IsEOF())
                          m_pSet- >MoveNext();
                 else
                          m_pSet- >MoveLast();

---- 4 . 修 改 记 录

---- 修 改 记 录 使 用Edit() 函 数:

                 m_pSet- >Edit(); //修改当前记录
                 m_pSet- >m_type="发电机";
       //修改当前记录字段值
                          ...
m_pSet- >Update();        //将修改结果存入数据库
                 m_pSet- >Requery();

---- 5 . 撤 消 操 作

---- 如 果 用 户 选 择 了 增 加 或 者 修 改 记 录 后 希 望 放 弃 当 前 操 作, 可 以 在 调 用Update() 函 数 之 前 调 用:

                 CRecordSet::Move(AFX_MOVE_REFRESH);

---- 来 撤 消 增 加 或 修 改 模 式, 并 恢 复 在 增 加 或 修 改 模 式 之 前 的 当 前 记 录。 其 中 的 参 数AFX_MOVE_REFRESH 的 值 为 零。

---- 6 . 数 据 库 连 接 的 复 用

---- 在CRecordSet 类 中 定 义 了 一 个 成 员 变 量m_pDatabase:

                          CDatabase* m_pDatabase;

---- 它 是 指 向 对 象 数 据 库 类 的 指 针。 如 果 在CRecordSet 类 对 象 调 用Open() 函 数 之 前, 将 一 个 已 经 打 开 的CDatabase 类 对 象 指 针 传 给m_pDatabase, 就 能 共 享 相 同 的CDatabase 类 对 象。 如:

                 CDatabase m_db;
                 CRecordSet m_set1,m_set2;
                 m_db.Open(_T("Super_ES"));//建立ODBC连接
                 m_set1.m_pDatabase=&m_db;
       //m_set1复用m_db对象
                 m_set2.m_pDatabse=&m_db;  
        // m_set2复用m_db对象

---- 7 .SQL 语 句 的 直 接 执 行

---- 虽 然 通 过CRecordSet 类, 我 们 可 以 完 成 大 多 数 的 查 询 操 作, 而 且 在CRecordSet::Open() 函 数 中 也 可 以 提 供SQL 语 句, 但 是 有 的 时 候 我 们 还 想 进 行 一 些 其 他 操 作, 例 如 建 立 新 表, 删 除 表, 建 立 新 的 字 段 等 等, 这 时 就 需 要 使 用 到CDatabase 类 的 直 接 执 行SQL 语 句 的 机 制。 通 过 调 用CDatabase::ExecuteSQL() 函 数 来 完 成SQL 语 句 的 直 接 执 行:

BOOL CDB::ExecuteSQLAndReportFailure(const CString& strSQL)
         {
         TRY
         {
                 m_pdb- >ExecuteSQL(strSQL);//直接执行SQL语句
         }
         CATCH (CDBException,e)
         {
         CString strMsg;
         strMsg.LoadString(IDS_EXECUTE_SQL_FAILED);
         strMsg+=strSQL;
                 return FALSE;
         }
         END_CATCH 
                 return TRUE;
         }

---- 应 当 指 出 的 是, 由 于 不 同DBMS 提 供 的 数 据 操 作 语 句 不 尽 相 同, 直 接 执 行SQL 语 句 可 能 会 破 坏 软 件 的DBMS 无 关 性, 因 此 在 应 用 中 应 当 慎 用 此 类 操 作。

---- 8 . 动 态 连 接 表

---- 表 的 动 态 连 接 可 以 利 用 在 调 用CRecordSet::Open() 函 数 时 指 定SQL 语 句 来 实 现。 同 一 个 记 录 集 对 象 只 能 访 问 具 有 相 同 结 构 的 表, 否 则 查 询 结 果 将 无 法 与 变 量 相 对 应。

         void CDB::ChangeTable()
         {
                 if (m_pSet- >IsOpen())    m_pSet- >Close();
                 switch (m_id)
         {
                 case 0:
                 m_pSet- >Open(AFX_DB_USE_DEFAULT_TYPE, 
"SELECT * FROM SLOT0");   //连接表SLOT0
                 m_id=1;
                 break;
                 case 1:
                          m_pSet- >Open(AFX_DB_USE_DEFAULT_TYPE, 
"SELECT * FROM SLOT1");   //连接表SLOT1
                          m_id=0;
                          break;
                 }
         }

---- 9 . 动 态 连 接 数 据 库

---- 由 于 与 数 据 库 的 连 接 是 通 过CDatabase 类 对 象 来 实 现 的, 所 以 我 们 可 以 通 过 赋 与CRecordSet 类 对 象 参 数m_pDatabase 以 连 接 不 同 数 据 库 的CDatabase 对 象 指 针, 就 可 以 动 态 连 接 数 据 库。

                 void CDB::ChangeConnect()
                 {
                          CDatabase* pdb=m_pSet- >m_pDatabase;
                          pdb- >Close();
                          
switch (m_id)
                          {
                          case 0:
                                   if (!pdb- >Open(_T("Super_ES")))   
               //连接数据源Super_ES
                                   {
                 AfxMessageBox("数据源Super_ES打开失败,"
"请检查相应的ODBC连接", MB_OK|MB_ICONWARNING);
                                            exit(0);
                                   }
                                   m_id=1;
                                   break;
                          case 1:
                                   if (!pdb- >Open(_T("Motor")))
                //连接数据源Motor
                                   {
                 AfxMessageBox("数据源Motor打开失败,"
"请检查相应的ODBC连接", MB_OK|MB_ICONWARNING);
                                            exit(0);
                                   }
                                   m_id=0;
                                   break;
                          }
                 }

---- 四 . 总 结

---- Visual C++ 中 的ODBC 类 库 可 以 帮 助 程 序 员 完 成 绝 大 多 数 的 数 据 库 操 作。 利 用ODBC 技 术 使 得 程 序 员 从 具 体 的DBMS 中 解 脱 出 来, 从 而 极 大 的 减 少 了 软 件 开 发 的 工 作 量, 缩 短 开 发 周 期, 提 高 了 效 率 和 软 件 的 可 靠 性。 本 文 总 结 的 笔 者 从 事 软 件 开 发 的 一 些 经 验 心 得 希 望 对 从 事ODBC 开 发 的 工 作 者 有 所 帮 助。

---- 参 考 文 献

---- 1. 《Visual C++ 技 术 内 幕( 第 二 版)》,[ 美] David J.Kruglinski, 清 华 大 学 出 版 社,1996。

---- 2. 《Visual C++4.0 教 程》,Microsoft 著, 何 晓 刚 等 译, 科 学 出 版 社 等 出 版,1997。

---- 3. 《ODBC 深 入 剖 析》,[ 美] Kyle Geiger 著, 曹 康 等 译, 电 子 工 业 出 版 社,1996。

 

利用ODBC管理数据库


  利用开放数据库互连(Open Database Connectivity,简称ODBC)实现。使用ODBC 可以避免应用程序随数据库的改变而改变,针对不同类型数据库使用不同的API。采用ODBC可以大大缩短开发应用程序的时间。例如:在开发Turbo C 与FoxPro的接口时,需要研究DBF文件的结构,并在设计界面(API)上占用许多时间。针对另一个数据库,就要重新研究相应的API。使用ODBC,更改数据库只需要在应用程序中调整相应的驱动程序。ODBC通过使用驱动程序来提供数据库的独立性。因此,对于不同的数据库就要求使用不同的数据库驱动程序。驱动程序实际上是一个支持ODBC函数调用的模块,通常就是一个动态链接库DLL,应用程序就是通过调用动态链接上的驱动程序所支持的函数来操作数据库的。目前,很多应用程序需要用到数据库,幸好,微软提供ODBC, Windows9x 的控制面板中都有出现。它非常方便地解决了不同数据库需要的连接。这里笔者用VC++5.0编写了一个简单的利用ODBC管理数据库的应用程序Student。需要完成如下一些步骤:
  1. 用Access97创建StuRecord.mdb数据库(其中包含一个表Student.db),作为数据源。
  2. 在操作系统中通过ODBC注册数据库StuRecord.mdb。
  3. 用vc++5.0中的向导AppWizard创建基本的数据库应用程序。
  4.创建数据库显示。
  5.向基本数据库添加一些代码来实现一些AppWizard不能支持的功能,如删除,添加等功能。
  具体实现如下:
  一、用Access97创建StuRecord.mdb数据库,其中表Student.db中内容如下图1所示:
  二、1.在Windows 98的控制面板Control Panel中用鼠标点击32-Bit ODBC图标。此时,出现Data Sources对话框。如图2所示:
  2.单击Add按钮,出现Create New Data Source对话框。从驱动程序列表中选择Microsoft Access Driver并单击Finish按钮。
  3.当ODBC Microsoft Access 97 Setup对话框出现以后,在Data Source Name文本框中输入Student Record,在Description文本框输入Student Record Sample,数据源的名字是一种用于识别读者正在创建的特定数据源的一种简便方法。Description字段使读者能够包含关于数据源的更多的特定信息。
  4.单击Select按钮。出现Select Database文件选择对话框。利用该文件选择对话框定位并选择StuRecord.mdb文件。
  5.单击OK按钮完成数据库选择,接下来在ODBC Microsoft Access97 Setup对话框中单击OK按钮,完成数据源的创建过程。最后,单击ODBC Data Source Administrator对话框中的OK按钮。
  三、1.从vc++5.0的 Developer Studio菜单栏选取File并选择File菜单下的New,单击Project标签。
  2.选择MFC AppWizard(exe)并在Name文本框中键入Student(即项目文件),单击OK按钮,出现Step 1对话框。
  3.选择Single Document,单击Next。
  4.选择Data View Without File Support选项,AppWizard将生成浏览数据库内容的类。该应用程序除了使用数据库之外不使用任何附加的文件,所以,它不需要文件支持,单击Data Source按钮将该应用程序与数据源相连接。
  5.在Database Options对话框中,下拉ODBC列表并选择Student Record数据源,单击OK。
  6.在Select Database Tables对话框中,选择Student表(Student.db),单击OK。Step 2对话框出现,现在,已经建立了Student Record数据源中的Student表(Student.db)与Student应用程序之间的关系。单击Next转到Step 3。
  7.接受缺省值,单击Next。
  8.在Step 4对话框中,关闭Printing and Print Preview选项,单击Next。
  9.接受Step 5中的缺省值,单击Next。在Step 6中只需单击Finish即完成创建Student应用程序所作的选择。随后出现New Project Information对话框。
  10.单击OK,AppWizard创建基本的Student应用程序。这时,可以单击Developer Studio工具栏上的Build按钮来编译应用程序,然后选择菜单栏上的Build下的Execute命令运行。如图3所示:
  四、1.在vc++5.0的 Developer Studio菜单栏选取File并选择File菜单下的Open Workspace&hellip;,选取步骤二中建立的项目文件夹 Student,进入此文件夹打开Student.dsw后,选择Resource View 标签来显示应用程序的资源。
  2.单击Student Resources文件夹旁的+号打开资源树,按同样的方法打开Dialog资源文件夹。双击IDD-STUDENT-FORM对话框ID,在资源编辑器中打开对话框。
  3.单击对话框中央的静态字符串以便选中该字符串,接着按Delete键从对话框中删除该字符串。
  4.用对话框编辑器的编辑工具(Controls)创建如图4所示的对话框,其中增加编辑框(ab|)和静态框(Aa)。给各编辑框的属性(properties)设定了各自的ID(对各框按鼠标右键选择属性,即可填写以下):IDC-STUDENT-ID,IDC-STUDENT-NAME,IDC-STUDENT-RATE,IDC-STUDENT-DEPT。其中IDC-STUDENT-ID编辑框为Read-Only样式(可在Edit Properties属性表单的Styles页面找到),各静态框(Aa)的ID号不变。
  5.选择View菜单下的ClassWizard,单击Member Variables标签。
  6.选择IDC-STUDENT-DEPT资源ID,单击Add Variable按钮,出现Add Member Variable对话框。
  7.单击Member Variables Name的箭头,出现一个下拉列表,选择m-pSet&rarr;m-DeptID,按OK键。
  8.同步骤7一样,选择m-pSet&rarr;m-StudentID,m-pSet&rarr;m-StudentName,m-pSet&rarr;m_StudentRate与对应的编辑控件相关联。
  9.单击MFC ClassWizard属性表单中的OK按钮,完成修改,在用Developer Studio工具栏上Build按钮来编译应用程序,然后选择菜单栏上的Build下的Execute命令如图5所示:
  五、由于篇幅有限,对于添加代码增加的一些用户功能省略,大家可参阅一些vc++方面的资料获取。
  (武汉 金文)

 

在ODBC 中 应 用DDX 和RFX

MFC 中ODBC 类 库 简 介

---- MFC 中 针 对ODBC 数 据 库 编 程 提 供 了 五 种 基 类。 这 些 类 封 装 了 有 关ODBC 的API 调 用, 使 用 户 能 够 利 用ODBC 完 成 不 同 类 型 的 数 据 库 编 程 工 作, 如 访 问Foxpro、dBASE 或Sybase 等 不 同 类 型 数 据 库 文 件, 从 而 避 开 各 种 类 型 数 据 库 文 件 的 复 杂 的 内 部 结 构。 这 五 种 基 类 是:

  • CDatabase 类 对 象 表 示 与 数 据 源 的 连 接。 用 户 正 是 基 于 此 连 接 实 现 对 数 据 源 的 操 作。
  • CRecordset 类 对 象 表 示 了 从 数 据 源 中 选 出 的 一 组 记 录。 该 对 象 使 用 户 能 完 成 在 记 录 间 的 滚 动、 更 新 记 录、 对 记 录 进 行 过 滤 排 序 等 操 作。
  • CRecordView 类 对 象 直 接 与 一CRecordset( 记 录 集) 类 对 象 连 接, 为 该 记 录 集 提 供 显 示 的 视 图。 它 是 数 据 库 操 作 的 界 面。
  • CFieldExchange 类 支 持 数 据 库 的 字 段 交 换 过 程, 即RFX 机 制。
  • CDBException 类 完 成 数 据 库 类 操 作 的 异 常 处 理。 用 户 可 根 据 其 中 公 用 成 员 变 量 的 取 值 来 分 析 出 现 错 误 的 原 因 或 显 示 出 相 应 的 文 本 信 息。

---- 以 下 我 们 利 用VC 中 的AppWizard 生 成 一 个 简 单 的 示 例 程 序 以 供 使 用:

---- 1. 选 择[File] 中 的[New], 在 弹 出 的 对 话 框 中 选 择 文 件 类 型 为[MFC AppWizard(EXE)]。 在[Project name] 中 键 入 文 件 名, 如:testodbc, 点 按[OK] 按 钮。

---- 2. 在 程 序 类 型 中 选 择[Single document], 点 按[Next] 按 钮。

---- 3. 在 数 据 库 支 持 中 选 择[Database view with file support], 点 按[Data source] 按 钮 进 入 数 据 库 选 择 对 话 框。

---- 4. 在[Datasource] 中 选 择[ODBC], 然 后 在 下 拉 列 表 中 选 择 [Visual Foxpro Tables]。[Recordset type] 设 定 为[Snapshot]。 点 按[OK] 按 钮 选 择 数 据 库 源 文 件:test.dbf。

---- 5. 选 择 了 数 据 库 源 文 件 后, 接 连 点 按[Next], 生 成 程 序 所 需 的 源 文 件。

---- 此 时 生 成 以 下 各 类 及 其 对 象:

  • CAboutDlg
  • CMainFrame
  • CTestodbcApp
  • CTestodbcDoc
  • CTestodbcSet
  • CTestodbcView

RFX 简 介

---- RFX 是Record Field Exchange 的 缩 写, 意 即 记 录 字 段 数 据 交 换。 它 在 用 户 选 择 的 记 录 集(Data set) 和 隐 藏 于 后 台 的 数 据 源(Data source) 之 间 建 立 对 应 关 系, 使 用 户 能 通 过 操 作 此 记 录 集 来 实 现 对 数 据 源 的 操 作。MFC 中 提 供 了 一 系 列RFX 调 用 函 数, 通 过 这 些 函 数, 可 以 随 时 在 记 录 集 和 数 据 源 之 间 进 行 数 据 交 换, 这 种 交 换 是 双 向 的。 这 些 函 数 有:

 函 数                   数 据 类 型
RFX_Bool                  BOOL
RFX_Byte                  BYTE
RFX_Binary                 CByteArray
RFX_Double                double
RFX_Single                 float
RFX_Int                    int
RFX_Long                  lonig
RFX_LongBinary             CLongBinary
RFX_Text                   CString
RFX_Date                   Ctime

---- 它 们 大 多 有 三 个 参 数( 个 别 会 有 四 或 五 个):

  • 一 个 指 向CFieldExchange 类 对 象 的 指 针;
  • 数 据 源 中 的 一 个 字 段 名;
  • 与 该 字 段 对 应 的 变 量 名。

---- 当 用AppWizard 生 成 代 码 时, 程 序 会 自 动 选 择 适 当 的 函 数 与 数 据 类 型 相 匹 配。

---- 记 录 字 段 数 据 交 换 的 核 心 是CRecordset 类 中 的 虚 函 数DoFieldExchange,RFX 函 数 都 在DoFieldExchange 中 调 用, 它 为RFX 函 数 提 供 了 一 个 指 向CFieldExchange 类 对 象 的 指 针, 其 原 型 为:

---- virtual void DoFieldExchange(CFieldExchang  pFX);

---- 上 文 示 例 中 生 成 的CTestodbcSet 类 是 从CRecordset 派 生 而 来 的, 为 了 利 用RFX 机 制, 它 声 明 了 两 个 成 员 变 量:Cstring m_name 和Cstring m_age, 分 别 对 应 数 据 库 表 文 件 中 的[name] 和[age] 字 段。 在CTestodbcSet 类 重 载 的 方 法DoFieldExchange(CFieldExchange * pFX) 中, 可 以 看 到 调 用 了 两 个RFX 函 数:

---- RFX_Text(pFX, _T(“[name]”), m_name);

---- RFX_Text(pFX, _T(“[age]”), m_age);

---- pFX 参 数 即 为DoFieldExchange 传 递 来 的CFieldExchange 类 对 象 指 针。

DDX 简 介

---- DDX 是Dialog Data Exchange 的 缩 写, 意 即 对 话 框 数 据 交 换。 它 在 对 话 框 的 可 视 控 件(Controls) 和 成 员 变 量(Member variables) 之 间 建 立 双 向 的 对 应 关 系, 使 用 户 能 通 过 对 话 框 上 的 控 件 浏 览 和 修 改 变 量 的 取 值。

---- 类 似 记 录 字 段 数 据 交 换, 对 话 框 数 据 交 换 的 核 心 是CRecordView 类 中 的 虚 函 数DoDataExchange,DDX 函 数 都 在DoDataExchange 中 调 用, 它 为DDX 函 数 提 供 了 一 个 指 向CDataExchange 类 对 象 的 指 针, 其 原 型 为:

---- virtual void DoDataExchange(CDataExchan  pDX);

---- 进 入 函 数Ctestodbc::DoDataExchange, 此 时 函 数 体 中 没 有 什 么 代 码, 因 为 我 们 并 没 有 在 对 话 框 中 创 建 控 件, 也 没 有 建 立DDX 机 制。

---- 打 开 示 例 程 序 的 对 话 框 资 源, 编 辑 自 动 生 成 的 对 话 框IDD_TESTODBC_FORM, 此 时, 该 对 话 框 中 仅 有 一 个 静 态 文 本 对 象“TODO: Place form controls on this dialog.”。 它 是 自 动 生 成 的 提 示 性 文 本, 表 示 可 在 此 对 话 框 上 添 加 控 件。 删 掉 此 文 本, 添 加 两 个 编 辑 框 对 象(Edit box)IDC_EDIT1 和IDC_EDIT2, 在IDC_EDIT1 上 按 鼠 标 右 键, 选 择 弹 出 菜 单 中 的[ClasWizard] 进 入 类 属 性 编 辑 对 话 框。 选 择[Member Variables] 属 性, 在[Control IDs] 中 选 择IDC_EDIT1, 然 后 按[Add Variable] 按 钮 以 选 择 与 它 对 应 的 变 量, 在 下 拉 列 表 框[Member Variable name] 中 选 择m_pSet ->m_name, 按[OK] 返 回。 用 同 样 方 法 为IDC_EDIT2 选 择m_pSet ->m_age。

---- 退 出 对 话 框 编 辑, 再 进 入 类CTestodbcView 源 文 件 中, 发 现 函 数DoDataExchange 中 增 加 了 两 个DDX 调 用 函 数:

---- DDX_FieldText(pDX, IDC_EDIT1, m_pSet ->m_name, m_pSet);

---- DDX_FieldText(pDX, IDC_EDIT2, m_pSet ->m_age, m_pSet);

---- DDX_FieldText 函 数 在 对 话 框 的 编 辑 控 件 和 记 录 的 字 段 之 间 建 立 联 系。 它 可 以 自 动 管 理 以 下 类 型 的 数 据:int、short、long、DWORD、Cstring、float、double、BOOL 和BYTE。 它 的 四 个 参 数 分 别 为:

  • 一 个 指 向CDataExchange 类 对 象 的 指 针;
  • 与 数 据 交 换 相 关 的 对 话 框 控 件 的ID 号;
  • 记 录 中 的 一 个 字 段;
  • 与 数 据 交 换 相 关 的 记 录 集 对 象 指 针。

---- 在DDX 双 向 数 据 交 换 中, 用 户 不 直 接 调 用DoFieldExchange 函 数, 而 是 调 用 函 数UpdateData(BOOL direct), 此 函 数 的direct 参 数 决 定 了 数 据 交 换 的 方 向:

---- direct=FALSE 用 记 录 的 字 段 值 更 新 控 件 值

---- direct=TRUE 用 控 件 值 更 新 记 录 的 字 段 值

---- 例 如 我 们 在 对 话 框 中 添 加 一 个[Save] 按 钮, 在 它 的 事 件 处 理 过 程 中 将 控 件 的 值 保 存:

void CTestodbcView::OnButtonSave() 
{
.. .. ..
UpdateData(TRUE);
.. .. ..
}

DDX 和RFX 的 关 系 及 比 较

---- 从 上 面 可 以 看 出,RFX 是 数 据 库 编 程 中 数 据 交 换 的 内 部 基 础, 它 更 紧 密 地 与 记 录 集 对 象(CRecordset) 相 联 系, 是 隐 于 后 台 的;DDX 是 数 据 库 编 程 中 数 据 交 换 在 对 话 框 界 面 上 实 现 的 基 础, 它 更 紧 密 地 与 视 图 对 象(CRecordView) 相 联 系, 是 显 现 于 前 台 的。 最 后 我 们 用 一 幅 图 更 直 观 地 展 示 它 们 的 关 系:

 

在VC++中建立自定义数据库类

哈尔滨工程大学计算中心 李健萍 李春艳 张积东

众所周知,VC++的MFC类库为编程者编制好了对数据库操作的类,编程者可以使用向导建立一个与数据库联结并对数据库进行操作的应用程序,不需要编制任何代码,这无疑为编程人员提供了一个捷径。但是,使用向导时只有选用基于单文档或多文档的项目才能选择数据源与指定的的数据库相连,对用向导生成的基于对话框的应用程序不提供数据库的支持。即使是基于单文档或多文档的应用程序,当需要一些特殊的操作,例如,打开一个表,要求返回满足一定条件的记录集时,MFC并没有提供完全符合要求的现成函数。如果能利用MFC所提供的数据库操作,再加上自己设计的函数,也就是说,设计一个对数据库操作的类,在程序中手工加入这个类,那么就可以在基于对话框的应用程序中实现对数据库的操作,而且,也可以针对自己应用程序的具体需要来设计类的函数,为特定功能的实现提供了很大的方便。

在一个涉及数据库操作的应用程序中,常用到的MFC类有CdaoDatabase类、CdaoTableDef类、CdaoRecordset类和 CdaoQueryDef类。当对数据库进行操作时,需要先打开数据库,然后打开数据库中的表,再得到查询集和记录集。在自己定义的类中综合这四个类的操作,设计一个打开表得到查询集和记录集的函数。以后,在应用程序中使用该类时只需包含该类的头文件,所设计的函数就可以直接调用了。

建立数据库类的过程可分为如下四步。

1 定义一个无基类的 CdataBaseOperate类

1. 在Workspace窗口选择ClassView选项卡,在树型类结构图的根部单击鼠标右键,选择New Class...,系统将弹出建立新类的对话框;

2. 在Class type中选择Generic Class;

3. 在Name中填写要建的新类的名称,要以大写字母C开头,系统会自动建立新类的头文件和实现文件,文件的名称为类名去掉第一个大写字母C,如果想改变文件的名称,可以单击change按钮;

4. 在填写好各项后,按OK按钮确定,一个无基类的新类建立成功。但他还是一个空类,下一步就要给类添加内容。

2 在自定义的类中加入有关的定义

1. 在本应用程序中,使用ODBC与SQL SERVER的数据库相连,因而,在类的实现文件构造函数前加入如下的定义:

#define SQL_DATABASE _T("ODBC;DSN=sql-database;UID=sa;PWD=pass;")

DSN=sql-database表示建立的ODBC联接的名称是sql-database,如果选用其他数据库,只需在此改变与所需数据库建立的联接,或是重新配置sql-database,使之联接新的数据库。UID=sa;PWD=pass表示登录数据库的用户名是sa,密码是pass,如果密码是空则表示为PWD=""。

2. 在该类中综合使用到了MFC类库提供的有关数据库的几个类:CdaoDatabase类、CdaoTableDef类、CdaoRecordset类和CdaoQueryDef类,而这四个类的定义和实现都包括在头文件afxdao.h中,因此,在新定义的类的头文件中一定要加上语句:

#include <afxdao.h>;

3. 对要用到的四个类各声明一个对象如下:

CDaoDatabase* loc_pDataBase;

CDaoTableDef* loc_pTable;

CDaoRecordset loc_pRecordset;

CDaoQueryDef* loc_pQueryDef;

其中CdaoDatabase类、CdaoTableDef类和CdaoQueryDef类定义了对象指针,在使用时要先new,最后要delete。以CdaoDatabase类为例,在CdataBaseOperate类的构造函数中初始化对象指针 loc_pDataBase=new CDaoDatabase;在析构函数中要释放该指针deleteloc_pDataBase。

3 在自定义的类中加入所需的函数和变量

手工加入函数包括两项工作,首先在头文件中加入函数的声明,然后在实现文件中加入函数的具体实现,声明与实现一定要统一。

使用向导加入函数和变量:

1. 在Workspace窗口选择ClassView选项卡;

2. 在树型类结构图的要添加函数和变量的类上单击鼠标右键,如果加入成员函数则单击Add Member Function,加入虚函数单击Add Virtual Function,加入成员变量单击 Add Member Variable;

3. 出现对话框后,填写成员函数或变量的名称、类型,系统会自动添加函数的声明与实现;

4. 添加函数的具体操作,可以通过编辑代码进一步填写。

这些操作将会在Workspace窗口的ClassView选项中立即体现出来,并且单击ClassView中的相应函数就可进入该函数的实现部分,进一步编写代码。如果做不到这一点,说明添加成员函数的操作有误。

下面以本应用程序为例,给出具体的表结构和几个主要函数的实现,读者可以根据自己的实际情况设计函数。

本应用程序中的一个典型表的结构是:

序号 正题内容 难度系数 分值  答案  备注

整型 字符型  长整型  双精度 字符型 字符型

打开数据库的函数实现如下:

if (!loc_pDataBase->IsOpen())

loc_pDataBase->Open( NULL, FALSE, FALSE, SQL_DATABASE);

该函数中用到了CdaoDatabase类的两个函数IsOpen()和Open(NULL, FALSE, FALSE,SQL_DATABASE),因为已经声明了该类的指针对象loc_pDataBase,所以可以直接调用CdaoDatabase类的函数。其中Open()函数中的最后一个参数SQL_DATABASE在前面已经介绍过,通过他打开相关的数据库。

由于程序中打开表后,不仅要返回所有的记录集,还用到返回满足一定条件的记录集,因此打开表的函数除了带入表名外还有一个参数难度系数,lNDXS=0时,选择表中全部数据, lNDXS=1~n时,表示选择难度系数=1~n的记录。

bool CDataBaseOperate::OpenTable(CString

strTableName,long lNDXS)

{

CString strFieldNumber;

loc_pTable=new CDaoTableDef(loc_pDataBase);

if (!loc_pTable->IsOpen())

loc_pTable->Open(strTableName); //打开指定的表名

strFieldNumber.Format("%d",loc_pTable->GetFieldCount()); //得到字段数

CString Sqlstr,Sqlstr1,Sqlstr2;

loc_pQueryDef=new CDaoQueryDef(loc_pDataBase);//得到查询集和记录集

if (lNDXS==0){ Sqlstr=_T("SELECT * FROM "+strTableName);}

else{ Sqlstr1="SELECT *FROM "+strTableName

Sqlstr2.Format(" WHERE 难度系数= %d",lNDXS);

Sqlstr=_T(Sqlstr1+Sqlstr2); }

loc_pQueryDef->Create(NULL,Sqlstr);

loc_pRecordset.Open(loc_pQueryDef);

m_nRecordNumber=0;

while(!loc_pRecordset.IsEOF()) {

m_nRecordNumber++;

loc_pRecordset.MoveNext();}

return TRUE;

}

为了维护数据库的安全,表用过后应该关闭,关闭表的同时,要释放在打开表的操作时初始化的对象指针,例如:delete loc_pQueryDef。同样要注意,在构造函数中初始化的对象指针,在析构函数中一定要释放。对象指针的初始化和释放是成对出现的。

loc_pDataBase=new CDaoDatabase; //在构造函数中初始化对象指针。

delete loc_pDataBase; //在析构函数中释放该对象指针。

4 CdataBaseOperate类的应用

1. 使用VC++的向导生成一个应用程序,可以根据需要选择基于对话框或是基于单、多文档,选择单文档或多文档时不要选择数据库支持。

2. 在应用程序的主头文件中加入#include "DataBaseOperate.h",并且还要声明一个

CdataBaseOperate类的对象,public:

CDataBaseOperate m_CDataBaseOperate。

3. 有了指向CdataBaseOperate类的对象后,刚刚在CdataBaseOperate类中编制的函数都可以通过"m_CdataBaseOperate.函数名"来调用。

在VC中使用ADO开发数据库应用程序

 

 

一、ADO概述

ADO是Microsoft为最新和最强大的数据访问范例 OLE DB 而设计的,是一个便于使用的应用程序层接口。ADO 使您能够编写应用程序以通过 OLE.DB 提供者访问和操作数据库服务器中的数据。ADO 最主要的优点是易于使用、速度快、内存支出少和磁盘遗迹小。ADO 在关键的应用方案中使用最少的网络流量,并且在前端和数据源之间使用最少的层数,所有这些都是为了提供轻量、高性能的接口。之所以称为 ADO,是用了一个比较熟悉的暗喻,OLE 自动化接口。

OLE DB是一组”组件对象模型”(COM) 接口,是新的数据库低层接口,它封装了ODBC的功能,并以统一的方式访问存储在不同信息源中的数据。OLE DB是Microsoft UDA(Universal Data Access)策略的技术基础。OLE DB 为任何数据源提供了高性能的访问,这些数据源包括关系和非关系数据库、电子邮件和文件系统、文本和图形、自定义业务对象等等。也就是说,OLE DB 并不局限于 ISAM、Jet 甚至关系数据源,它能够处理任何类型的数据,而不考虑它们的格式和存储方法。在实际应用中,这种多样性意味着可以访问驻留在 Excel 电子数据表、文本文件、电子邮件/目录服务甚至邮件服务器,诸如 Microsoft Exchange 中的数据。但是,OLE DB 应用程序编程接口的目的是为各种应用程序提供最佳的功能,它并不符合简单化的要求。您需要的API 应该是一座连接应用程序和 OLE DB 的桥梁,这就是 ActiveX Data Objects (ADO)。

二、在VC中使用ADO

1、引入ADO库文件

使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下所示:

代码1:用#import引入ADO库文件

#import "c:\program files\common files\system\ado\msado15.dll"

no_namespaces rename("EOF" adoEOF")

这行语句声明在工程中使用ADO,但不使用ADO的名字空间,并且为了避免常数冲突,将常数EOF改名为adoEOF。现在不需添加另外的头文件,就可以使用ADO接口了。

2、初始化OLE/COM库环境

必须注意的是,ADO库是一组COM动态库,这意味应用程序在调用ADO前,必须初始化OLE/COM库环境。在MFC应用程序里,一个比较好的方法是在应用程序主类的InitInstance成员函数里初始化OLE/COM库环境。

 

代码2:初始化OLE/COM库环境

BOOL CADOApp::InitInstance()

{

if(!AfxOleInit())

 

{

AfxMessageBox(“OLE初始化出错!”);

return FALSE;

}

……

 

}

函数AfxOleInit在每次应用程序启动时初始化OLE/COM库环境。

同DAO和CDatabase一样,ADO由几个接口组成:

_ConnectionPtr,_CommandPtr和_RecordsetPtr.

不同于DAO和Cdatabase的是,ADO基于COM的接口,因此,假如你没有接触过COM,你应该在使用ADO前先找有关书籍了解一下COM。

3、ADO接口简介

ADO库包含三个基本接口:_ConnectionPtr接口、_CommandPtr接口和_RecordsetPtr接口。

_ConnectionPtr接口返回一个记录集或一个空指针。通常使用它来创建一个数据连接或执行一条不返回任何结果的SQL语句,如一个存储过程。使用_ConnectionPtr接口返回一个记录集不是一个好的使用方法。通常同CDatabase一样,使用它创建一个数据连接,然后使用其它对象执行数据输入输出操作。

_CommandPtr接口返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储过程和SQL语句。在使用_CommandPtr接口时,你可以利用全局_ConnectionPtr接口,也可以在_CommandPtr接口里直接使用连接串。如果你只执行一次或几次数据访问操作,后者是比较好的选择。但如果你要频繁访问数据库,并要返回很多记录集,那么,你应该使用全局_ConnectionPtr接口创建一个数据连接,然后使用_CommandPtr接口执行存储过程和SQL语句。

_RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多的控制功能,如记录锁定,游标控制等。同_CommandPtr接口一样,它不一定要使用一个已经创建的数据连接,可以用一个连接串代替连接指针赋给_RecordsetPtr的connection成员变量,让它自己创建数据连接。如果你要使用多个记录集,最好的方法是同Command对象一样使用已经创建了数据连接的全局_ConnectionPtr接口,然后使用_RecordsetPtr执行存储过程和SQL语句。

 

4、使用_ConnectionPtr接口

_ConnectionPtr是一个连接接口,它类似于CDatabase和CDaoDatabase。它们的工作原理相似。首先创建一个_ConnectionPtr接口实例,接着指向并打开一个ODBC数据源或OLE DB数据提供者(Provider)。以下代码和CDaoDatabase分别创建一个基于DSN和非DSN的数据连接。

 

代码3:使用CDaoDatabase(基于DSN)

CDaoDatabase MyDb = new CDaoDatabase();

MyDb.Open(NULL,FALSE,FALSE,"ODBC;DSN=samp;UID=admin;PWD=admin");

 

 

代码4:使用CDaoDatabase(基于非DSN)

CDaoDatabase MyDb = new CDaoDatabase();

MyDb.Open(NULL,FALSE,FALSE,"ODBC;DRIVER={SQL Server};SERVER=server;

DATABASE=samp;UID=admin;PWD=admin");

 

 

代码5:使用_ConnectionPtr(基于DSN)

_ConnectionPtr MyDb;

MyDb.CreateInstance(__uuidof(Connection));

MyDb->Open("DSN=samp;UID=admin;PWD=admin","","",-1);

 

 

代码6:使用_ConnectionPtr (基于非DSN)

_ConnectionPtr MyDb;

MyDb.CreateInstance(__uuidof(Connection));

MyDb->Open("Provider=SQLOLEDB;SERVER=server;DATABASE=samp;UID=admin;

PWD=admin","","",-1);

 

5、使用_RecordsetPtr接口

_RecordsetPtr接口的使用方法和CDaoDatabase类似,通过以下代码的比较,你会发现使用_RecordsetPtr接口非常简单(以下代码使用上面已经创建的数据连接):

 

代码7:使用CDaoDatabase执行SQL语句

CDaoRecordset MySet = new CDaoRecordset(MyDb);

MySet->Open(AFX_DAO_USE_DEFAULT_TYPE,"SELECT * FROM t_samp");

Now using ADO:

 

 

代码8:使用_RecordsetPtr执行SQL语句

_RecordsetPtr MySet;

MySet.CreateInstance(__uuidof(Recordset));

MySet->Open("SELECT * FROM some_table",

MyDb.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText);

现在我们已经有了一个数据连接和一个记录集,接下来就可以使用数据了。从以下代码可以看到,使用ADO的_RecordsetPtr接口,就不需要象DAO那样频繁地使用大而复杂的数据结构VARIANT,并强制转换各种数据类型了,这也是ADO的优点之一。假定程序有一个名称为m_List的的ListBox控件,下面代码我们用_RecordsetPtr接口获取记录集数据并填充这个ListBox控件:

 

代码9:使用DAO访问数据

VARIANT * vFieldValue;

COleVariant covFieldValue;

CString Holder;

while(!MySet->IsEOF())

{

MySet->GetFieldValue("FIELD_1", covFieldValue);

vFieldValue = (LPVARIANT)covFieldValue;

if(vFieldValue->vt!-VT_NULL)

{

Holder.Format("%s",vFieldValue->pbVal);

m_List.AddString(Holder);

 

}

MySet.MoveNext();

 

}

 

代码10:使用ADO访问数据

_variant_t Holder

try{

while(!MySet->adoEOF)

{

Holder = MySet->GetCollect("FIELD_1");

if(Holder.vt!=VT_NULL)

m_List.AddString((char*)_bstr_t(Holder));

MySet->MoveNext();

}

}

catch(_com_error * e)

 

{

CString Error = e->ErrorMessage();

AfxMessageBox(e->ErrorMessage());

}

catch(...)

{

MessageBox("ADO发生错误!");

}

必须始终在代码中用try和catch来捕获ADO错误,否则ADO错误会使你的应用程序崩溃。当ADO发生运行时错误时(如数据库不存在),OLE DB数据提供者将自动创建一个_com_error对象,并将有关错误信息填充到这个对象的成员变量.

 

6、使用_CommandPtr接口

_CommandPtr接口返回一个Recordset对象,并且提供了更多的记录集控制功能,以下代码示例了使用_CommandPtr接口的方法:

 

代码11:使用_CommandPtr接口获取数据

_CommandPtr pCommand;

_RecordsetPtr MySet;

pCommand.CreateInstance(__uuidof(Command));

pCommand->ActiveConnection=MyDb;

pCommand->CommandText="select * from some_table";

pCommand->CommandType=adCmdText;

pCommand->Parameters->Refresh();

MySet=pCommand->Execute(NULL,NULL,adCmdUnknown);

_variant_t TheValue = MySet->GetCollect("FIELD_1");

CString sValue=(char*)_bstr_t(TheValue);

7、关于数据类型转换

由于COM对象是跨平台的,它使用了一种通用的方法来处理各种类型的数据,因此CString 类和COM对象是不兼容的,我们需要一组API来转换COM对象和C++类型的数据。_vatiant_t和_bstr_t就是这样两种对象。它们提供了通用的方法转换COM对象和C++类型的数据。

8、小结

数据访问发展的趋势是OLE DB.使用OLE DB最简单的方法是ADO.ADO的对象层次模型封装了数据库访问细节,为C++程序员提供了一种非常好的数据访问策略。

 

 

数据库中的ODBC编程(1)

 

发信人: cvisual (贝贝), 信区: VisualC

 标  题: 数据库中的ODBC编程(1)

 发信站: BBS 水木清华站 (Sun May 24 21:59:19 1998)

 

 摘要 本文具体介绍了ODBC

2.0的原理和运行机制,并结合一个企业MIS系统的实例,给 

 出Windows 95环境下用Visual C++ 2.0进行ODBC技术编程的具体方法和应用。 

 关 键 词 ODBC, 数 据 库 , 数 据 源 

 

 一 、 ODBC技 术 概 述 

 

 ODBC(Open DataBase

Connectivity,开放数据库互连)实际上是一个不同类型数据库的通用?

 口(API),用这些API编写的应?

 

应用程序独立于数据库管理系统,实现应用程序对不同DBMS的共享。应用程序对数据库

 

 的操作,是通过和各种DBMS相对应的ODBC驱动程序(Drive)来进行的,ODBC

2.0中包含了 

 

目前绝大多数数据库产品的驱动程序。在ODBC技术出现以前,由于不同数据库文件结构

 

 

都不一样,而且既不是文本文件,也不是二进制文件,应用程序对数据库的直接读写一

 

 

直是令人头痛的瓶颈问题。企业的MIS系统设计中,应用程序经常需要操作数据库。本文

 

 给出Windows 95下,在MIS系统设计中,用Visual C++ 

 2.0编写应用程序,操作用FoxPro 2.6 

 

编写的企业人员状况数据库member.dbf的具体方法和步骤。member.dbf的文件结构和内

 

 容见 

 表1。 

 表 1 企 业 人 员 状 况 数 据 库 

 姓名 年龄 性别 部门 工资 

 徐海燕 46 女 财务科 480.40 

 董单 38 男 技术科 390.70 

 王蕾 25 男 销售科 311.90 

 李萍 25 女 档案科 298.80 

 

               

 

 ※ 来源: BS 水木清华站 bbs.net.tsinghua.edu.cn FROM: bbs.nju.edu.cn]

 

数据库中的ODBC编程(2)

                        -===BBS水木清华站∶精华区===-

 发信人: cvisual (贝贝), 信区: VisualC

 标  题: 数据库中的ODBC编程(2)

 发信站: BBS 水木清华站 (Sun May 24 22:01:23 1998)

 

 二 、 ODBC管 理 器 的 作 用 

 

 ODBC管理器(Administrator)位于Windows 95控制面板(Control Panel) 

 中的32bit ODBC程序项中 。它 负 责 安 装 驱 动 程 序 , 管 理 数 

 据 源 , 并 帮 助 程 序 员 跟 踪 ODBC的 函 数 调 用 。 在 ODBC中 , 

 应 用 程 序 不 能 直 接 存 取 数 据 库 , 它 必 须 通 过 管 理 器 

 和 数 据 库 交 换 信 息 。 ODBC管 理 器 负 责 将 应 用 程 序 的 

 SQL语 句 及 其 他 信 息 传 递 给 驱 动 程 序 , 而 驱 动 程 序 则 

 负 责 将 运 行 结 果 送 回 应用 程 序 。 应 用 程 序 、 ODBC管 理 

 器 和 数 据 库 之 间 的 关 系 如 图 1 所 示 。 运 行 3 2 bit ODBC 

 管 理 器后 , 出 现 一 个 主 对 话 框 , 它 的 主 要 内 容 是 要 求 

 用 户 输 入 一 个 数 据 源 , 所 谓 数 据 源 就 是 数据 库 位 置 、 

 数 据 库 类 型 以 及 ODBC驱 动 程 序 等 信 息 的 集 成 。 数 据 源 

 在 使 用 之 前 必 须 通 过ODBC管 理 器 进 行 登 记 和 连 接 , 启 

 动 ODBC管 理 器 后 , 选 取 Add按 钮 , 根 据 自 己 的 数 据 库 类 

 型 , 选 择 相 应 的 ODBC驱 动 程 序 , 在 本 例 中 选 择 

 Microsoft FoxPro Driver, 然 后 输 入 数 据 源 名 ( Data Source Name) 

 和 数 据 库 文 件 名 ( Database Name) , 完 成 这 些 步 骤 后 , 

 以 后 的 应 用 程 序 就 能 够 通过 ODBC管 理 器 的 数 据 源 直 接 操 纵 

 数 据 库 。 本 例 中 数 据 源 名 为 member, 选 取 的 数 据 库 文 件 为 

 member. dbf。 

 

 图 1 应 用 程 序 、 ODBC管 理 器 和 数 据 库 之 间 的 关 系 

 

              

 

 

 

                       --===BBS水木清华站∶精华区===--

数据库中的ODBC编程(3)

                        -===BBS水木清华站∶精华区===-

 发信人: cvisual (贝贝), 信区: VisualC

 标  题: 数据库中的ODBC编程(3)

 发信站: BBS 水木清华站 (Sun May 24 22:13:30 1998)

 

 三 、 ODBC技 术 的 编 程 方 法 

 

 Visual C+ + 2 . 0 的 基 本 类 库 MFC 3 . 0 

 ( Microsoft Foundation Class) 定 义 了 一 个 数 据 库 类 。 

 在 利 用ODBC编 程 时 , 要 经 常 用 到 数 据 库 类 中 的 

 CDatabase类 ( 数 据 库 类 ) 、 CRecordView类 ( 可 视 记 录 类) 

 和 CRecordSet( 记 录 集 类 ) 。 其 中 CRecordSet类 是 通 过 

 MFC支 持 ODBC的 关 键 , 它 封 装 了 操 纵ODBC表 所 需 的 各 

 种 函 数 。 

 1 . 使 用 AppWizard自 动 生 成 一 个 ODBC应 用 程 序 框 架 

 Visual C+ + 2 . 0 中 , AppWizard通 过 创 建 一 个 

 新 的 项 目 ( Project) 而 被 激 活 , 选 择 File菜 单 中 的 

 New选 项 , 选 取 Project, 输 入 文 件 名 为 exodbc, 选 择 OK, 

 就 开 始 自 动 创 建 一 个 新 项 目 , 在 随 后的 步 骤 中 , 

 要 选 择 ? A database view, without file support? 选 项 或 

 ? Both a database view and file support? 选 项, 从 而 创 

 建 一 个 支 持 ODBC的 应 用 程 序 , 然 后 选 取 数 据 源 和 表 , 

 本 例 中 数 据 源 为 member, 表名 为 member. dbf, 其 他 的 步 骤 

 和 创 建 一 般 的 应 用 程 序 相 同 , 最 后 生 成 的 主 要 类 有 : 

 CExodbcSet( CRecordset的 派 生 类 ) 、 

 CExodbcView( CRecordView的 派 生 类 ) 、 

 CExodbcDoc( CDocument的派 生 类 ) 。 

 2 . 记 录 的 打 开 和 数 据 的 交 换 

 CExodbcView类 负 责 打 开 记 录 集 ; 它 包 含 一 个 指 向 

 CExodbcSet对 象 的 指 针 m- pSet, 该 对 象 自身 又 是 CExodbcDoc 

 文 档 类 的 一 个 成 员 函 数 , 它 们 之 间 的 具 体 关 系 为 : 

 Class CExodbcView: public CRecordView 

 { public: 

 CExodbcSet* m- pSet; 

 } 

 Class CExodbcDoc: public CDocument 

 { public: 

 CExodbcSet m- exodbcset; 

 } 

 void CExodbcView: : OnInitialUpdate( ) 

 { m- pSet= & GetDocument( ) - > m- exodbcset; 

 / * 指 向 记 录 的 指 针 * / 

 CRecordView: : OnInitialUpdate( ) ; 

 } 

 记 录 的 打 开 用 Open函 数 , 其 格 式 为 : 

 CExodbcSet: : Open( UINT nOpenType= snapshot, 

 LPCSTR lpszSql= NULL, DWORD dwOptions= none) 

 该 函 数 负 责 打 开 数 据 库 的 记 录 集 并 读 取 第 一 条 记 录 。 

 nOpenType参 数 的 取 值 为 : 

 dynaset: 允 许 双 向 翻 滚 的 动 态 记 录 集 , 即 对 记 

 录 集 进 行 的 修 改 立 即 生 效 ; 

 snapshot: 允 许 双 向 翻 滚 的 静 态 记 录 集 , 即 对 记 

 录 集 进 行 的 修 改 直 到 该 记 录 集 被 重 新 打 开或 查 询 时 

 才 生 效 ; 

 lpszSql: 是 一 个 包 含 一 条 SQL语 句 的 字 符 串 指 针 , 

 该 语 句 在 表 被 打 开 时 执 行 , 缺 省 值 为NULL; 

 dwOptions: 指 定 对 记 录 集 如 何 修 改 , 允 许 取 值 范 围 为 : 

 none: 允 许 添 加 、 更 新 和 删 除 操 作 , 缺 省 设 置 ; 

 appendOnly: 允 许 添 加 操 作 , 不 允 许 更 新 和 删 除 操 作 ; 

 readyOnly: 只 读 、 不 允 许 添 加 、 更 新 和 删 除 ; 

 记 录 被 打 开 后 , 可 以 用 下 列 函 数 遍 历 表 中 的 记 录 : 

 · Void CRecordset: : MoveFirst( ) ? ? 移 动 到 第 一 条 记 录 

 · Void CRecordset: : MoveLast( ) ? ? 移 动 到 最 后 一 条 记 录 

 · Void CRecordset: : MovePrev( ) ? ? 后 滚 一 条 记 录 

 · Void CRecordset: : MoveNext( ) ? ? 前 滚 一 条 记 录 

 · Void CRecordset: : Move( long lRows) ? ? lRows为 正 数 , 

 前 滚 lRows条 记 录 ; 否 则 , 后 滚 lRows条 记录 。 

 为 了 判 别 是 否 移 到 表 的 开 始 和 结 尾 , 可 以 用 下 列 

 函 数 来 判 别 : 

 · BOOL CRecordset: : IsBOF( ) ? ? 如 果 已 经 向 后 滚 

 动 到 第 一 条 记 录 前 , 或 者 记 录 集 中 已 没有 记 录 时 , 返 回 

 非 零 值 ; 否 则 , 返 回 零 值 。 

 · BOOL CRecordset: : IsEOF( ) ? ? 如 果 已 前 滚 到 最 

 后 一 条 记 录 后 , 或 者 记 录 集 中 没 有 记 录时 , 返 回 非 零 值 ; 

 否 则 , 返 回 零 值 。 

 记 录 被 成 功 打 开 后 , GetDefaultConnect函 数 返 回 一 个 

 字 符 串 , 字 符 串 中 包 含 记 录 集 数 据 源名 称 。 

 CString CExodbcSet: : GetDefaultConnect( ) 

 { return - T( ″ ODBC; DSN= member; ″ ) } 

 字 符 串 前 面 的 ? ODBC; ? 标 识 该 数 据 源 是 一 个 ODBC的 数 据 

 源 , DSN( Date Source Name) 后 面 是数 据 源 名 称 。 

 CString CExodbcSet: : GetDefaultSQL( ) 

 { return - T( ″ member″ ) ; } 

 DetDefaultSQL( ) 函 数 返 回 记 录 集 所 使 用 的 数 据 源 中 

 的 表 的 名 称 ( 本 例 中 表 为 member. dbf) 。 

 DoFieldExchange函 数 用 于 在 记 录 集 的 成 员 变 量 和 ODBC 

 驱 动 程 序 之 间 进 行 数 据 传 递 。 当 记录 被 读 取 时 , RFX… 函 

 数 将 数 据 从 数 据 库 中 传 递 到 成 员 变 量 中 。 当 添 加 和 更 新 

 记 录 时 ,同 样 用 这 些 函 数 把 应 用 程 序 中 的 数 据 传 递 到 数 

 据 库 中 。 

 Void CExodbcSet: : DoFieldExchange( CFieldExchange * pFX) 

 { / / { { AFX- FIELD- MAP( CExodbcSet) 

 pFX- > SetFieldType( cFieldExchange: : outputColumn) ; 

 RFX- Text( pFX, ″ NAME″ , m- NAME) ; 

 / * 和 姓 名 字 段 交 换 数 据 * / 

 RFX- Long( pFX, ″ AGE″ , m- AGE) ; 

 / * 和 年 龄 字 段 交 换 数 据 * / 

 RFX- Text( pFX, ″ SEX″ , m- SEX) ; 

 / * 和 性 别 字 段 交 换 数 据 * / 

 RFX- Text( pFX, ″ SECTION″ , m- SECTION) ; 

 / * 和 部 门 字 段 交 换 数 据 * / 

 RFX- Long( pFX, ″ SALARY″ , m- SALARY) ; 

 / * 和 工 资 字 段 交 换 数 据 * / 

 / / } } AFX- FIELD- MAP 

 } 

 

 3 . 增 加 记 录 

 当 CRecordSet: : Open( ) 函 数 是 用 允 许 增 加 的 方 式 

 ( 即 dwOptions= none) 打 开 数 据 库 时 , 记 录 中的 CRecordSet 

 对 象 将 标 记 那 些 被 绑 定 到 数 据 库 中 各 列 的 成 员 变 量 的 

 初 始 值 , 当 Update成 员 函数 被 调 用 时 , CRecordSet自 动 把 

 增 加 结 果 传 递 到 表 中 。 

 AddNew( ) 函 数 用 来 添 加 一 条 新 记 录 , 它 将 该 新 

 记 录 的 所 有 字 段 标 记 为 NULL和 未 修 改 ,然 后 调 用 UpdateData 

 函 数 , 更 新 屏 幕 上 的 字 段 , 再 调 用 CRecordSet: : Update 

 函 数 , 就 能 将 新 记录 写 入 数 据 库 中 , 具 体 的 函 数 为 : 

 void CExodbcDoc: OnRecordAdd( ) / * 增 加 记 录 的 响 应 函 数 * / 

 { 

 POSITION p; 

 CRecordView* view; 

 p= GetFirstViewPosition( ) ; 

 view= ( CRecordView* ) GetNextView( p) ; 

 m- exodbcset. AddNew( ) ; 

 / * 在 表 的 末 尾 处 增 加 一 个 新 的 记 录 * / 

 view- > UpdateData( FALSE) ; 

 / * 更 新 屏 幕 上 的 字 段 内 容 * / 

 view- > OnMove( ) ; 

 / * 将 当 前 处 理 的 记 录 写 入 数 据 库 中 * / 

 } 

 BOOL CExodbcView: : OnMove( UNIT nIDMoveCommand) 

 { CExodbcDoc* doc= GetDocument( ) ; 

 / * 指 向 文 档 类 的 指 针 * / 

 m- pSet- > Update( ) ; 

 / * 将 新 记 录 写 入 数 据 库 中 * / 

 m- pSet- > Requery( ) ; 

 / * 根 据 输 入 的 新 记 录 , 重 建 记 录 集 * / 

 UpdateData( FALSE) ; 

 / * 重 新 显 示 屏 幕 上 的 字 段 值 * / 

 return TRUE; 

 } 

 4 . 删 除 记 录 

 删 除 一 个 记 录 时 , 将 记 录 指 针 移 动 到 待 删 除 的 

 记 录 处 , 然 后 调 用 Delete( ) 函 数 , 并 且 在调 用 Delete后 

 不 需 调 用 Update函 数 , 源 程 序 为 : 

 Void CExodbcDOC: : OnRecordDelete( ) 

 / * 删 除 记 录 的 菜 单 响 应 函 数 * / 

 { try { m- exodbcset. Delete( ) } ; 

 / * 删 除 当 前 的 记 录 * / 

 catch( CDBException * e) { 

 / * 出 现 异 常 , 给 出 提 示 * / 

 AfxmessageBox( ″ 不 能 删 除 一 个 记 录 ″ ) ; 

 / * 指 出 删 除 操 作 失 败 * / 

 AfxmessageBox( e- > m- strError) ; 

 / * 指 出 错 误 的 原 因 * / 

 return; } 

 if( ! m- exodbcset. IsEOF( ) ) 

 / * 如 果 不 是 第 一 个 记 录 被 删 除 * / 

 m- exodbcSet. MoveNext( ) ; 

 / * 记 录 指 针 定 位 到 下 一 个 记 录 * / 

 else/ * 最 后 一 个 记 录 被 删 除 * / 

 m- exodbcset. MoveLast( ) ; 

 / * 记 录 指 针 定 位 到 上 一 个 记 录 * / 

 } 

 5 . 修 改 记 录 

 打 开 数 据 库 , 将 记 录 指 针 指 向 被 修 改 的 记 录 处 , 

 调 用 Edit成 员 函 数 , 就 可 以 修 改 该 记录 , 修 改 完 成 后 , 

 调 用 Update函 数 将 修 改 结 果 传 递 到 数 据 库 中 。 

 m- exodbcSet. Open( ) ; 

 if( ! mexodbcset. CanUpdate( ) ) { 

 / * 判 断 记 录 集 是 否 允 许 被 更 新 * / 

 m- exodbcset. Close( ) ; 

 return; 

 } 

 m- exodbcset. OnMove( ) ; / * 移 动 记 录 指 针 * / 

 m- exodbcset. Edit( ) ; / * 修 改 屏 幕 上 的 当 前 记 录 * / 

 m- exodbcset. Update( ) ; / * 将 修 改 的 结 果 写 入 数 据 库 中 * / 

 

              

 

 ※ 来源: BS 水木清华站 bbs.net.tsinghua.edu.cn FROM: bbs.nju.edu.cn]

 

                        -===BBS水木清华站∶精华区===-

 

 

 

Visual C++ 中的 ODBC 编程

 

华中理工大学电力工程系 董毅

 

摘要:ODBC(Open Database Connectivity,开放式数据库连接),是一种用来在相关或不相关的数据库管理系统(DBMS)中存取数据的标准应用程序接口(API)。本文给出Windows95环境下用VisualC++进行ODBC编程的具体方法及 技巧。

关键字:ODBC,VisualC++,Windows编程。

--------------------------------------------------------------------------------

 

一 概述

 

----ODBC是一种使用SQL的程序设计接口。使用ODBC让应用程序的编写者避免了与数据源相联的复杂性。这项技术目前已经得到了大多数DBMS厂商们的广泛支持。

 

----Microsoft Developer Studio为大多数标准的数据库格式提供了32位ODBC驱动器。这些标准数据格式包括有:SQL Server、Access、Paradox、dBase、FoxPro、Excel、Oracle以及Microsoft Text。如果用户希望使用其他数据格式,用户需要相应的ODBC驱动器及DBMS。

 

----用户使用自己的DBMS数据库管理 功能生成新的数据库模式后,就可以使用ODBC来登录数 据源。对用户的应用程序来说,只要安装有驱动程序,就能注册很多不同的数据库。登录数据库的具体操作参见有关ODBC的联机帮助。

 

 

--------------------------------------------------------------------------------

 

二 MFC提供的ODBC数据库类

 

----VisualC++的MFC基类库定义了几个数据库类。在利用ODBC编程时,经常要使用到CDatabase(数据库类),CRecordSet(记录集类)和CRecordView(可视记录集类)。 其中:

 

----CDatabase类对象提供了对数据源的连接,通过它你可以对数据源进行操作。

 

----CRecordSet类对象提供了从数据源 中提取出的记录集。CRecordSet对象通常用于两种形式: 动态行集(dynasets)和快照集(snapshots)。动态行集能保 持与其他用户所做的更改保持同步。快照集则是数据的一个静态视图。每一种形式在记录集被打开时都提供一组记录,所不同的是,当你在一个动态行集里滚 动到一条记录时,由其他用户或是你应用程序中的其他记录集对该记录所做的更改会相应地显示出来。

 

----CRecordView类对象能以控制的形式 显示数据库记录。这个视图是直接连到一个CRecordSet对象的表视图。

 

 

--------------------------------------------------------------------------------

 

三 应用ODBC编程

 

----应用VisualC++的AppWizard可以自动生 成一个ODBC应用程序框架。方法是:打开File菜单的New选 项,选取Projects,填入工程名,选择MFCAppWizard(exe),然后按AppWizard 的提示进行操作。当AppWizard询问是否包含数据库支持 时,如果你想读写数据库,那么选定Database view with file support;而如果你想访问数据库的信息而不想回写所做 的改变,那么选定Database view without file support选项就比较合 适了。选择了数据库支持之后Database Source按钮会激活, 选中它去调用Data Options对话框。在Database Options对话框中会 显示已向ODBC注册的数据库资源,选定你所要操作的数据库,如:Super_ES,单击OK后会出现Select Database Tables对话 框,其中列举了你所选中的数据库中包含的全部表,选择你希望操作的表后,单击OK。在选定了数据库和数据表之后,你可以按照惯例继续进行AppWizard操作。

 

----特别需要指出的是:在生成的应用程序框架View类(如:CSuper_ESView)中包含一个指向CSuper_ESSet 对象的指针m_pSet,该指针由AppWizard建立,目的是在视表单和记录集之间建立联系,使得记录集中的查询结果能够很容易地在视表单上显示出来。有关m_pSet的详细 用法可以参见VisualC++ OnlineBook。

 

----程序与数据语言建立联系,使 用CDatebase::OpenEx()或CDatabase::Open()函数来进行初始化。数据库 对象必须在你使用它构造一个记录集对象之前被初始化。

 

----下面举例说明在VisualC++环境中ODBC 的编程技巧:

 

----1 查询记录

----查询记录使用CRecordSet::Open()和 CRecordSet::Requery()成员函数。在使用CRecordSet类对象之前,必须使用 CRecordSet::Open()函数来获得有效的记录集。一旦已经使用过CRecordSet::Open() 函数,再次查询时就可以应用CRecordSet::Requery()函数。在调 用CRecordSet::Open()函数时,如果已经将一个已经打开的CDatabase 对象指针传给CRecordSet类对象的m_pDatabase成员变量,则使 用该数据库对象建立ODBC连接;否则如果m_pDatabase为空指 针,就新建一个CDatabase类对象并使其与缺省的数据源 相连,然后进行CRecordSet类对象的初始化。缺省数据源 由GetDefaultConnect()函数获得。你也可以提供你所需要的SQL 语句,并以它来调用CRecordSet::Open()函数,例如:

 

----Super_ESSet.Open(AFX_DATABASE_USE_DEFAULT,strSQL);

 

----如果没有指定参数,程序则使 用缺省的SQL语句,即对在GetDefaultSQL()函数中指定的SQL语 句进行操作:

 

CString CSuper_ESSet::GetDefaultSQL()

{return _T("[BasicData],[MainSize]");}

 

----对于GetDefaultSQL()函数返回的表名, 对应的缺省操作是SELECT语句,即:

----SELECT * FROM BasicData,MainSize

 

----查询过程中也可以利用CRecordSet的 成员变量m_strFilter和m_strSort来执行条件查询和结果排序。m_strFilter 为过滤字符串,存放着SQL语句中WHERE后的条件串;m_strSort 为排序字符串,存放着SQL语句中ORDERBY后的字符串。 如:

 

         Super_ESSet.m_strFilter="TYPE='电动机'";

         Super_ESSet.m_strSort="VOLTAGE";

         Super_ESSet.Requery();

         对应的SQL语句为:

         SELECT * FROM BasicData,MainSize

         WHERE TYPE='电动机'

         ORDER BY VOLTAGE

 

----除了直接赋值给m_strFilter以外,还 可以使用参数化。利用参数化可以更直观,更方便地 完成条件查询任务。使用参数化的步骤如下:

 

----(1) 声明参变量:

 

         CStringp1;

         floatp2;

 

----(2) 在构造函数中初始化参变量

 

         p1=_T("");

         p2=0.0f;

         m_nParams=2;

 

----(3) 将参变量与对应列绑定

 

         pFX->SetFieldType(CFieldExchange::param)

         RFX_Text(pFX,_T("P1"),p1);

         RFX_Single(pFX,_T("P2"),p2);

 

----完成以上步骤之后就可以利用 参变量进行条件查询了:

 

         m_pSet->m_strFilter="TYPE=?ANDVOLTAGE=?";

         m_pSet->p1="电动机";

         m_pSet->p2=60.0;

         m_pSet->Requery();

----参变量的值按绑定的顺序替换 查询字串中的???适配符。

----如果查询的结果是多条记录的 话,可以用CRecordSet类的函数Move(),MoveNext(),MovePrev(),MoveFirst() 和MoveLast()来移动光标。

 

----2 增加记录

----增加记录使用AddNew()函数,要求数据库必须是以允许增加的方式打开:

 

         m_pSet->AddNew();          //在表的末尾增加新记录

         m_pSet->SetFieldNull(&(m_pSet->m_type),FALSE);

        m_pSet->m_type="电动机";

                   ...                         //输入新的字段值

         m_pSet->  Update();  //将新记录存入数据库

         m_pSet->Requery(); //重建记录集

 

----3 删除记录

----直接使用Delete()函数,并且在调用Delete() 函数之后不需调用Update()函数:

 

         m_pSet->Delete();

         if(!m_pSet->IsEOF())

                   m_pSet->MoveNext();

         else

                   m_pSet->MoveLast();

 

----4 修改记录

----修改记录使用Edit()函数:

 

         m_pSet->Edit();                   //修改当前记录

         m_pSet->m_type="发电机";  //修改当前记录字段值

                   ...

         m_pSet->Update();             //将修改结果存入数据库

         m_pSet->Requery();

 

----5 撤消操作

----如果用户选择了增加或者修改记录后希望放弃当前操作,可以在调用Update()函数之前调用:

 

----CRecordSet::Move(AFX_MOVE_REFRESH);

 

----来撤消增加或修改模式,并恢复在增加或修改模式之前的当前记录。其中的参数AFX_MOVE_REFRESH 的值为零。

 

----6 数据库连接的复用

----在CRecordSet类中定义了一个成员变 量m_pDatabase:

 

----CDatabase *m_pDatabase;

 

----它是指向对象数据库类的指针。如果在CRecordSet类对象调用Open()函数之前,将一个已经打开的CDatabase类对象指针传给m_pDatabase,就能共享相同 的CDatabase类对象。如:

 

         CDatabase m_db;

         CRecordSet m_set1,m_set2;

         m_db.Open(_T("Super_ES"));  // 建 立ODBC 连 接

         m_set1.m_pDatabase=&m_db; //m_set1 复 用m_db 对 象

         m_set2.m_pDatabse=&m_db;   // m_set2 复 用m_db 对 象

 

----7 SQL语句的直接执行

----虽然通过CRecordSet类,我们可以完成 大多数的查询操作,而且在CRecordSet::Open()函数中也可以 提供SQL语句,但是有的时候我们还想进行一些其他操 作,例如建立新表,删除表,建立新的字段等等,这 时就需要使用到CDatabase类的直接执行SQL语句的机制。通 过调用CDatabase::ExecuteSQL()函数来完成SQL语句的直接执行:

 

         BOOL CDB::ExecuteSQLAndReportFailure(const CString& strSQL)

         {

                   TRY

                   {

                            m_pdb->ExecuteSQL(strSQL);  //直接执行SQL语句

                   }

                   CATCH (CDBException,e)

                   {

                            CString strMsg;

                            strMsg.LoadString(IDS_EXECUTE_SQL_FAILED);

                            strMsg+=strSQL;

                            return FALSE;

                   }

                   END_CATCH

                   return TRUE;

         }

 

----应当指出的是,由于不同DBMS提 供的数据操作语句不尽相同,直接执行SQL语句可能会 破坏软件的DBMS无关性,因此在应用中应当慎用此类操 作。

 

----8 动态连接表

----表的动态连接可以利用在调用CRecordSet::Open() 函数时指定SQL语句来实现。同一个记录集对象只能访 问具有相同结构的表,否则查询结果将无法与变量相 对应。

 

         void CDB::ChangeTable()

         {

                   if (m_pSet->IsOpen())        m_pSet->Close();

                   switch (m_id)

                   {

                            case 0:

                                     m_pSet->Open(AFX_DB_USE_DEFAULT_TYPE,

                                     "SELECT * FROM SLOT0");     // 连 接 表SLOT0

                                     m_id=1;

                                     break;

                            case 1:

                                     m_pSet->Open(AFX_DB_USE_DEFAULT_TYPE,

                                     "SELECT * FROM SLOT1");     // 连 接 表SLOT1

                            m_id=0;

                            break;

                   }

         }

 

----9 动态连接数据库

----由于与数据库的连接是通过CDatabase 类对象来实现的,所以我们可以通过赋与CRecordSet类对 象参数m_pDatabase以连接不同数据库的CDatabase对象指针,就 可以动态连接数据库。

 

         void CDB::ChangeConnect()

         {

                   CDatabase* pdb=m_pSet->m_pDatabase;

                   pdb->Close();

                  

                   switch (m_id)

                   {

                   case 0:

                            if (!pdb->Open(_T("Super_ES")))      //连接数据源Super_ES

                            {

                                     AfxMessageBox(" 数据源Super_ES打开失败,"

                            ---- "请检查相应的ODBC连接", MB_OK|MB_ICONWARNING);

                                     exit(0);

                            }

                            m_id=1;

                            break;

                   case 1:

                            if (!pdb->Open(_T("Motor")))   //连接数据源Motor

                            {

                                     AfxMessageBox("数据源Motor打开失败,"

                            ---- "请检查相应的ODBC连接", MB_OK|MB_ICONWARNING);

                                     exit(0);

                            }

                            m_id=0;

                            break;

                   }

         }

 

 

--------------------------------------------------------------------------------

 

四 总结

 

----VisualC++中的ODBC类库可以帮助程 序员完成绝大多数的数据库操作。利用ODBC技术使得程 序员从具体的DBMS中解脱出来,从而极大的减少了软件 开发的工作量,缩短开发周期,提高了效率和软件的 可靠性。本文总结的笔者从事软件开发的一些经验心 得希望对从事ODBC开发的工作者有所帮助。

 

 

--------------------------------------------------------------------------------

 

参考文献

 

《VisualC++技术内幕(第二版)》,[美]DavidJ.Kruglinski,清 华大学出版社,1996。

 

《VisualC++4.0教程》,Microsoft著,何晓刚等译,科学出版 社等出版,1997。

 

《ODBC深入剖析》,[美]KyleGeiger著,曹康等译,电子工 业出版社,1996。

 

--------------------------------------------------------------------------------

 

 

 

ODBC中的同步与异步执行模式

 

北京师范大学 刘永明----

    近年来,随着计算机局域网技术的不断发展,计算机体系结构已经发展到复杂 而开放的客户机/服务器模式。对于客户机/服务器应用的开发,目前常用的前端开 发工具有Visual Basic、Visual Foxpro、Delphi、PowerBuilder等,它们可以通过ODBC 接口访问服务器的SQL Server 数据库服务器,通常有三种方法(本文以ODBC2.0 为例):

 

使用数据控制项

使用数据库对象变量进行编程

直接调用ODBC 2.0 API

     ODBC 2.0 访问数据库时存在同步与异步执行模式之分,如果设计不当,则易发生系 统故障甚至系统死锁。下面笔者就实践经验,针对ODBC 2.0的同步与异步执行模式谈 一点使用经验和设置方法,欲与同行们商榷。

 

同步执行模式:

    所谓同步执行模式,是指语句在同步执行模式下,将始终保持对程序流的控制,直至 程序结束。如查询操作,客户机上的应用程序在向服务器发出查询操作的指令后,将 一直等待服务器将查询结果返回客户机端,然后才继续进行下一步操作。

    众所周知,应用程序要从一个大表中删除所有的记录将是非常耗时的,如果应用程序 采用的是单线程(thread)同步执行方式,某次删除工作很可能耽误其他重要工作的完 成。如果应用程序等待的是远程任务,那么远程服务器失败或网络故障或一些无法预 知的情况都可能使应用程序无限期地等下去,这是同步执行最大的缺陷。

    但是同步执行模式可以简化程序编制的复杂性。程序员可以不用过多地了解比较复杂 的ODBC 2.0 API 的使用,而只需使用 ODBC 的同步执行模式或使用数据控制项和数据库对象变量来编写应用程序,可以提高开发效率,但程序运行速度比不上异步执行 模式的速度。

 

异步执行模式:

    所谓异步执行模式,是指语句在异步执行模式下,各语句执行结 束的顺序与语句执行开始的顺序并不一定相同。例如 查询操作,客户机上的应用程序在向服务器发出了查 询操作的指令后,将立刻执行查询语句指令的下一条 语句,而不需要等到服务器将查询结果返回客户机端。 异步执行方式使应用程序能摆 脱单个任务的牵制,提高了灵活性和应用程序的执行 效率。但异步执行模式也存在一些问题,如它增加了 编程的复杂性,特别是编写互用性(interoperable)要求较高 的程序。

    在负荷很重的客户/服务器系 统中,适宜采用异步执行模式。在这种环境下,时间 延迟频繁且漫长,相比之下异步执行的开销微不足 道。但是,如果应用运行的环境比较复杂,则必须建 立一套完整的机制,周期性地检查函数执行的状态, 以决定下一步执行方案。进行周期的检查可以有多种方法,如在 应用中设置计时器并处理WM_TIMER信息等。

    虽然使用异步执行模式在编程 序时十分复杂,但可以实现多任务并行执行,使执行的效率大大提高。

    选择并设置执行模式 在应用程序开发中选择同步模式还是异步模式,是一个比 较复杂的层次。当查询或对数据库的修改相对简单时,同步执行模式是一种 很好的选择,它能够在几秒 或更少的时间内返回结果数据。另外,在应用程序获 得结果集前不能继续执行时,根本不必要使用异步执 行模式。在复杂查询情况下,特别是复杂的多行数据 库的UPDATE 或DELETE 操作,可能需要很长的时间才能完成, 需采用异步执行模式,让用户可以同时对程序的其他 部分进行操作。

对于一般程序员来说,如果他对同步执行模式与异步执行模式不了解, 他往往会在对服务器发出一个操作语句(查询或读取一条记录等 操作)后,立该引用服务器返回的执行结果,或者对该 结果进行下一步操作,这是很危险的。因为,在异步 执行模式下,客户机上的后续语句是在该操作语句发 出后接着执行的,但由于各种原因,服务器不一定能 执行完该操作语句,并在后续语句执行前将结果返回 客户机。因此,后续语句在引用前一操作语句的执行 结果时,往往会因为该执行结果并不存在而引用了错 误的值,造成系统错误或死锁,所以在实际应用中应 根据具体情况慎重选择执行模式。

 

      在ODBC 2.0 API 中,并非所有的驱动程序都支持异步执行方式,详细的情况请 参见有关文档。但如果编写的是互操作性要求较高的应用程序, 则必须在程序运行时动态地了解有关特性。了解一个 函数是否支持异步执行模式的具体方法很简单: 分配 一个语句按异步方式执行一次,若能成功,则具有异 步执行功能,否则便不具有。在ODBC 2.0 API 中,函数SQLSetStmtOption() 的功能是设置异步或异步执行模式,调用该函数的形式如下:

    retcode=SQLSetStmtOption(hstmt,SQL_ASYNC_ENABLE,1);

其中 hstmt 是一语句句柄,常数SQL_ASYNC_ENABLE是所设置的选项,参数'1' 是该选项开的标志,'0'表示该选项关)。如果函数返回SQL_SUCCESS,则表示驱动程 序支持该选项,并且 hstmt 现已被设置为异步执行方式;如果函数 返回SQL_ERROR则表示驱动程序不支持异步执行方式。ODBC 2.0 API 中共有20多个函数支持异步执行,如下所示。

 

 SQLColAttributes()  SQLColumnPrivileges() SQLColumns()

 SQLDescribeCol()   SQLDescribeParam()   SQLExecDirect()

 SQLExecute()      SQLExtendedFetch()   SQLFetch()

 SQLForeignKeys()   SQLGetData()        SQLGetTypeInfo()

 SQLMoreResults()   SQLNumParams()     SQLNumResultCols()

 SQLParamData()    SQLPrepare()         SQLPrimaryKeyS()

 SQLProcedureColumns()SQLProcedures()    SQLPutData()

 SQLSetPos()         SQLSpecialColumns() SQLStatistics()

 SQLTablePrivileges() SQLTables()

 

    这些函数第一次调用后,将返回值SQL_STILL_EXE_CUTING,这时应用程序将继续执 行后续语句。过一段时间后,应该再次调用原函数,而且要注 意:实参数应传入与第一次调用时相同的语句句柄, 其他参数也应一样(但会被忽略)。如果函数返回值为SQL_SUCCESS, 则表明该语句已经执行完毕;如果函数返回SQL_STILL_EXECUTING, 则表明该语句仍在执行中。下面我们用一个简单的例子来说明:

 

RetCode=SQLSetStmtoption(hStmt,SQL_ASYNC_ ENABLE,1)

(置语句执行模式为异步执行模式)

RetCode=SQLExecDirect(hStmt,?select * from employees?,23)

(执行其他操作)

… …

RetCode=SQLExecDirect(hStmt,?select * from employees?,23)

下面判断SQLExecDirect()是否已执行完毕:

if ( iRetCode = SQL_STILL_EXECUTING)

{

      … … 该语句未执行完,继续执行其他操作

}

else

{

      if (iRetCode=SQL_SUCCESS)

      {

         … … 该语句已执行完,可对语句操作结果进行处理

      }

}

 

      综上所述,我们在使用ODBC 2.0 API编制应用程序时,应根据自身情况,适当选 择同步和异步两种模式,以便提高程序运行的可靠性和执行效率。

 

 

从数据库中读大于32k的内容?

 

我在从数据库中读数据时碰到了问题.当数据栏包含超过32k的内容时,我就读不出来,我试过ODBC::SQLGetData()也不行.哪种类型的数据库?MS SQL,SYBASE... 试试设置一下大小:

BOOL CGetBlobStmt::Execute(LPCTSTR stmt)

{

m_cbSize = 0;

m_size = 0;

LPBYTE

  lpData;

lpData = (LPBYTE)GlobalLock(m_hData);

m_retcode = SQLSetStmtOption(GetHandle(),SQL_MAX_LENGTH,m_dwBytesLeft);

m_retcode = SQLExecDirect(GetHandle(),(UCHAR*)stmt,SQL_NTS);

if (m_retcode == SQL_SUCCESS)

{

  m_retcode = SQLFetch(GetHandle());

  if (m_retcode == SQL_SUCCESS ||m_retcode == SQL_SUCCESS_WITH_INFO)

  {

   m_retcode = SQLGetData(GetHandle(),1,SQL_C_BINARY,lpData,254,&m_cbSize);

   while(m_retcode == SQL_SUCCESS_WITH_INFO)

   {

    lpData+= 254;

    m_retcode = SQLGetData(GetHandle(),1,SQL_C_BINARY,lpData,254,&m_cbSize);

   }

   GetError();

  }

}

GlobalUnlock(m_hData);

#if TESTDATA

TRACE("%ld",m_size);

#endif

SaveFile();

return RETVALUE;

}

 

 

DAO的密码?

 

        我创建了一个使用数据库的mfc应用程序.用类模板生成CDaoRecordset直接打开数据库(不通过ODBC),但问题是我如何打开有密码保护的数据库?

方法一:试试下面的代码:

 DAODBEngine* pDBEngine = AfxDaoGetEngine();

 ASSERT(pDBEngine != NULL);

 COleVariant varUserName (strUserName, VT_BSTRT);

 COleVariant varPassword (strPassword, VT_BSTRT);

 DAO_CHECK(pDBEngine->put_DefaultUser (V_BSTR(&varUserName));

 DAO_CHECK(pDBEngine->put_DefaultPassword (V_BSTR(&varPassword));

方法二:你可以使用CDaoDatabase的Open方法来打开:

MyDaoDatabase->Open("C:\MyDatabaseFile.mdb",FALSE,FALSE,";PWD=MyPassWord");

btw:不要忘了PWD=前面的;号.

 

 

ODBC许可问题

 

我有个程序想通过ODBC来使用一个MS Access数据库,但是却碰上了错误,系统显示 "Records can't be read; no read permission on table SESSION".(记录不能读, 表单不允许读)

)首先我假设access数据库有一个缺省的用户为"admin",可以这样完成"ODBC;UID=admin". 然后,当你继承CRecordset类时你就不必带参数打开,但下面的方法可能更好些:

Open(CRecordset::dynaset, NULL,CRecordset::useBookmarks | CRecordset::skipDeletedRecords)

  当然你必须提供DSN表示连接名字的数据库在ODBC之下.

 

使用了CRecordset类

 

我建立了一个应用程序,并使用了CRecordset类。但是,当我运行该程序时,它试图要访问数据库,并给出?Internal Application Error?对话框。我应该怎样做?

通常情况下,当你的程序中向数据库发送信息的 SQL 语句出现问题时才出现该对话框。例如,参见下面的例子:  set.m_strFilter = "(ZipCode = '27111')";

如果 ZipCode 列被定义为字符串时不会出现问题,如果定义为 long,则会出现?Internal Application Error?对话框,这是由于类型不匹配的缘故。如果你删除 27111 的单引号,则不会出现问题。当你看到?Internal Application Error?时,最好检查一下试图要发送给数据库的 SQL 语句。

 

 

用VC 开 发 基 于ORACLE 数 据 库 应 用 程 序 的 两 种 方 法

北 京 航 空 航 天 大 学 计 算 机 科 学 与 工 程 系

黎 杰 麦 中 凡

---- 1. 引 言

 

---- ORACLE 公 司 自1979 年 推 出 基 于SQL 标 准 的 关 系 数 据 库 产 品 到1997 年 版 本8 的 推 出,ORACLE 数 据 库 以 其 支 持 大 数 据 库、 多 用 户 的 高 性 能 事 务 处 理, 对 业 界 各 项 工 业 标 准 的 支 持, 完 整 的 安 全 和 完 整 性 控 制, 支 持 分 布 式 数 据 库 和 分 布 处 理, 具 有 可 移 植 性、 可 兼 容 性 和 可 连 接 性 等 突 出 优 点 倍 受 用 户 喜 爱, 根 据IDG1992 年 全 球UNIX 数 据 库 的 市 场 报 告,ORACLE 占 市 场 销 售 量 的50%。 而 在 客 户 端 的 开 发 工 具 方 面,Visual C++ 也 因 其 强 大 的 功 能 和 高 度 的 灵 活 性 等 特 点 深 受 广 大 程 序 员 的 喜 爱, 因 此 本 文 旨 在 介 绍 使 用Visual C++ 开 发 基 于ORACLE 数 据 库 应 用 程 序 的 两 种 方 法。

 

---- 2. 使 用PRO*C 开 发 数 据 库 应 用

 

---- 2.1 PRO*C 工 作 原 理

 

---- PRO 系 列 是ORACLE 公 司 提 供 的 在 第 三 代 高 级 程 序 设 计 语 言 中 嵌 入SQL 语 句 来 访 问 数 据 库 的 一 套 预 编 译 程 序, 包 括PRO*Ada、PRO*C、PRO*COBOL、PRO*Fortran、PRO*Pascal 和PRO*PL/I 六 种。 程 序 员 用 相 应 的 高 级 语 言 编 写 嵌 入SQL 语 句 的PRO 源 程 序( 若 用C 语 言 则 称 为PRO*C 源 程 序) 后 运 行 相 应 的 预 编 译 程 序, 把 嵌 入 的SQL 语 句 转 换 为 标 准 的ORACLE 调 用 并 生 成 目 标 源 程 序, 即 纯 高 级 语 言 格 式 的 源 程 序, 然 后 就 可 以 将 这 些 源 程 序 加 入 用 户 的 程 序 中 调 用, 其 处 理 过 程 如 下 图。

 

---- ORACLE 预 编 译 程 序 提 供 如 下 功 能:

 

---- ⑴ 能 用 六 种 通 用 的 高 级 程 序 设 计 语 言 中 的 任 何 一 种 编 写 应 用 程 序。

 

---- ⑵ 遵 循ANSI 标 准, 在 高 级 语 言 中 嵌 入SQL 语 句。

 

---- ⑶ 可 采 用 动 态SQL 方 法, 让 程 序 在 运 行 时 接 受 或 构 造 一 个 有 效 的SQL 语 句。

 

---- ⑷ 实 现ORACLE 内 部 数 据 类 型 和 高 级 语 言 数 据 类 型 之 间 的 自 动 转 换。

 

---- ⑸ 可 通 过 在 应 用 程 序 中 嵌 入PL/SQL 事 物 处 理 块 来 改 进 性 能。

 

---- ⑹ 能 在 程 序 行 和 命 令 行 上 指 定 所 需 要 的 预 编 译 可 选 项, 并 可 在 预 编 译 的 过 程 中 改 变 它 们 的 值。

 

---- ⑺ 能 全 面 检 查 嵌 入 的SQL 数 据 操 纵 语 句 和PL/SQL 块 的 文 法 和 语 义。

 

---- ⑻ 可 用SQL*Net 并 行 存 取 多 个 地 点 的ORACLE 数 据 库。

 

---- ⑼ 可 把 数 组 作 为 输 入 和 输 出 程 序 变 量 使 用。

 

---- ⑽ 能 对 应 用 程 序 中 的 代 码 段 进 行 条 件 预 编 译。

 

---- ⑾ 提 供 了 较 强 的 异 常 处 理 功 能。

 

---- 由 此 可 见, 通 过 预 编 译 程 序 与 其 它 高 级 语 言 的 结 合, 既 可 以 利 用SQL 强 有 力 的 功 能 和 灵 活 性 为 数 据 库 应 用 系 统 的 开 发 提 供 强 有 力 的 手 段, 又 可 以 充 分 利 用 高 级 语 言 自 身 在 系 统 开 发 方 面 的 优 势, 从 而 提 供 一 个 完 备 的 基 于ORACLE 数 据 库 应 用 程 序 的 开 发 解 决 方 案。

 

---- 2.2 在VC 中 使 用PRO*C

 

---- 每 个PRO*C 源 文 件 一 般 由 程 序 头 和 程 序 体 两 部 分 组 成。 程 序 头 包 含 宿 主 变 量(SQL 语 句 中 所 包 含 的 变 量) 说 明、 通 讯 区 定 义 和C 外 部 表 示 符 的 说 明 等。 程 序 体 一 般 是 由 若 干 函 数 组 成, 这 些 函 数 内 含 有SQL 语 句( 以EXEC SQL 起 头 的 语 句)。

 

---- PRO*C 支 持 的 数 据 类 型 包 括VARCHAR2( 变 长 字 符 串)、NUMBER( 二 进 制 数)、INTGER( 有 符 号 整 数)、FLOAT( 浮 点 数)、STRING( 以NULL 结 尾 的 字 符 串)、VARNUM( 变 长 二 进 制 数)、LONG( 变 长 字 符 串)、VARCHAR( 变 长 字 符 串)、ROWID( 二 进 制 值)、DATE( 定 长 日 期/ 时 间 值)、VARRAW( 变 长 二 进 制 数 据)、RAW( 定 长 二 进 制 数 据) 、LONGRAW( 变 长 二 进 制 数 据)、UNSIGNED( 无 符 号 整 数)、LONGVARCHAR( 变 长 字 符 串)、LONGVARRAW( 变 长 二 进 制 数 据)、CHAR( 定 长 字 符 串)、CHARZ(C 中 定 长 以NULL 结 尾 的 字 符 串)、MLSLABEL( 变 长 二 进 制 数 据)。

 

---- 在PRO*C 中 不 能 使 用'l' 或'u' 作 词 尾 或'0x' 作 词 头 修 饰 常 量; 在SQL 语 句 中 使 用 单 引 号 来 定 义 字 符 串, 用 双 引 号 来 定 义 特 殊 的 或 小 写 字 符 的 标 识 符( 如 表 名 等);SQL 语 句 中 不 允 许 使 用C 中 的 寻 址、 间 接、 位 逻 辑、 复 合 赋 值、?=、-、++、%、<<、>> 操 作 符 并 且 用NOT、AND、OR、= 代 替!、&&、||、==。

 

---- 下 面 的 程 序 是 一 个 联 结 数 据 库 的PRO*C 源 程 序 例 子。

 

#include < sqlca.h >  //声明SQL通讯区

#include < string.h >

#include < afxwin.h >

EXEC SQL BEGIN DECLARE SECTION;

     VARCHAR  username[20];         //声明宿主变量

          VARCHAR  password[20];

          VARCHAR  dbname[20];

EXEC SQL END DECLARE SECTION;

void db_connect()

{

strcpy((char *)username.arr,"SCOTT");

         username.len = strlen((char *)username.arr);

         strcpy((char *)password.arr,"TIGER");

         password.len = strlen((char *)password.arr);

         strcpy((char *)dbname.arr,"SUNDB");

         dbname.len = strlen((char *)dbname.arr);

         EXEC SQL WHENEVER SQLERROR STOP;       //隐式异常处理

         EXEC SQL CONNECT :username

    IDENTIFIED BY :password USING :dbname;

         /*if (sqlca.sqlcode != 0)    //显式异常处理

{

                   AfxMessageBox("\n与 Oracle数据库连接失败!");

                   return;

   }*/

}

 

---- 在VC 中 使 用PRO*C 时, 先 用PRO*C 编 写 所 需 的 操 作 数 据 库 的 子 程 序, 再 运 行PRO*C 预 编 译 程 序 把PRO*C 源 程 序 转 成 相 应 的CPP 源 程 序, 将 该 程 序 插 入 到 用 户 工 程 文 件 中 并 在 需 要 对 插 入 函 数 进 行 调 用 的 模 块 中 说 明 函 数, 然 后 就 可 以 在 此 模 块 中 调 用 所 需 的 函 数。

 

---- 3. 使 用ODBC 中 间 件 访 问 数 据 库

 

---- 3.1 ODBC 工 作 原 理

 

---- ODBC 是Open Database Connect 即 开 放 数 据 库 互 连 的 简 称, 它 是 由Microsoft 公 司 于1991 年 提 出 的 一 个 用 于 访 问 数 据 库 的 统 一 界 面 标 准, 是 应 用 程 序 和 数 据 库 系 统 之 间 的 中 间 件。 它 通 过 使 用 相 应 应 用 平 台 上 和 所 需 数 据 库 对 应 的 驱 动 程 序 与 应 用 程 序 的 交 互 来 实 现 对 数 据 库 的 操 作, 避 免 了 在 应 用 程 序 中 直 接 调 用 与 数 据 库 相 关 的 操 作, 从 而 提 供 了 数 据 库 的 独 立 性。

 

---- ODBC 主 要 由 驱 动 程 序 和 驱 动 程 序 管 理 器 组 成。 驱 动 程 序 是 一 个 用 以 支 持ODBC 函 数 调 用 的 模 块( 在WIN95 下 通 常 是 一 个DLL), 每 个 驱 动 程 序 对 应 于 相 应 的 数 据 库, 当 应 用 程 序 从 基 于 一 个 数 据 库 系 统 移 植 到 另 一 个 时, 只 需 更 改 应 用 程 序 中 由ODBC 管 理 程 序 设 定 的 与 相 应 数 据 库 系 统 对 应 的 别 名 即 可。 驱 动 程 序 管 理 器( 包 含 在ODBC32.DLL 中) 可 链 接 到 所 有ODBC 应 用 程 序 中, 它 负 责 管 理 应 用 程 序 中ODBC 函 数 与DLL 中 函 数 的 绑 定。

 

---- ODBC 使 用 层 次 的 方 法 来 管 理 数 据 库, 在 数 据 库 通 信 结 构 的 每 一 层, 对 可 能 出 现 依 赖 数 据 库 产 品 自 身 特 性 的 地 方,ODBC 都 引 入 一 个 公 共 接 口 以 解 决 潜 在 的 不 一 致 性, 从 而 很 好 地 解 决 了 基 于 数 据 库 系 统 应 用 程 序 的 相 对 独 立 性, 这 也 是ODBC 一 经 推 出 就 获 得 巨 大 成 功 的 重 要 原 因 之 一。

 

---- 从 结 构 上 分,ODBC 分 为 单 束 式 和 多 束 式 两 类。

 

---- ⑴ 单 束 式 驱 动 程 序

 

---- 单 束 式 驱 动 程 序 介 于 应 用 程 序 和 数 据 库 之 间, 像 中 介 驱 动 程 序 一 样 数 据 提 供 一 个 统 一 的 数 据 访 问 方 式。

 

---- 当 用 户 进 行 数 据 库 操 作 时, 应 用 程 序 传 递 一 个ODBC 函 数 调 用 给ODBC 驱 动 程 序 管 理 器, 由ODBC API 判 断 该 调 用 是 由 它 直 接 处 理 并 将 结 果 返 回 还 是 送 交 驱 动 程 序 执 行 并 将 结 果 返 回。

 

---- 由 上 可 见, 单 束 式 驱 动 程 序 本 身 是 一 个 数 据 库 引 擎, 由 它 直 接 可 完 成 对 数 据 库 的 操 作, 尽 管 该 数 据 库 可 能 位 于 网 络 的 任 何 地 方。

 

---- ⑵ 多 束 式 驱 动 程 序

 

---- 多 束 式 驱 动 程 序 负 责 在 数 据 库 引 擎 和 客 户 应 用 程 序 之 间 传 送 命 令 和 数 据, 它 本 身 并 不 执 行 数 据 处 理 操 作 而 用 于 远 程 操 作 的 网 络 通 信 协 议 的 一 个 界 面。

 

---- 前 端 应 用 程 序 提 出 对 数 据 库 处 理 的 请 求, 该 请 求 转 给ODBC 驱 动 程 序 管 理 器, 驱 动 程 序 管 理 器 依 据 请 求 的 情 况, 就 地 完 成 或 传 给 多 束 驱 动 程 序, 多 束 式 驱 动 程 序 将 请 求 翻 译 为 特 定 厂 家 的 数 据 库 通 信 接 口( 如Oracle 的SQLNet) 所 能 理 解 的 形 式 并 交 于 接 口 去 处 理, 接 口 把 请 求 经 网 络 传 送 给 服 务 器 上 的 数 据 引 擎, 服 务 器 处 理 完 后 把 结 果 发 回 给 数 据 库 通 信 接 口, 数 据 库 接 口 将 结 果 传 给 多 束 式ODBC 驱 动 程 序, 再 由 驱 动 程 序 将 结 果 传 给 应 用 程 序。

 

---- 3.2 在VC 中 使 用ODBC

 

---- Visual C++ 中 提 供 了CDatabase、CRecordset、CRecordView、CDBException 和CFieldExchange 五 个 类, 这 些 类 封 装 了ODBC SDK 函 数, 从 而 使 用 户 可 以 无 需 了 解SDK 函 数 就 可 以 很 方 便 地 操 作 支 持ODBC 的 数 据 库。

 

---- CDatabase 类: 封 装 了 与 数 据 库 建 立 连 接, 控 制 事 务 的 提 交 和 回 滚 及 执 行SQL 语 句 的 方 法。

 

---- CRecordset 类: 封 装 了 大 部 分 操 纵 数 据 库 的 方 法, 包 括 浏 览、 修 改 记 录, 控 制 游 标 移 动, 排 序 等 操 作。

 

---- CRecordView 类: 提 供 了 与recordset 对 象 相 连 接 的 视, 可 以 建 立 视 中 的 控 件 与 数 据 库 数 据 的 对 应, 同 时 支 持 移 动 游 标, 修 改 记 录 等 操 作。

 

---- CDBException 类: 提 供 了 对 数 据 库 操 作 的 异 常 处 理, 可 以 获 得 操 作 异 常 的 相 关 返 回 代 码。

 

---- CFieldExchange 类: 提 供 了 用 户 变 量 与 数 据 库 字 段 之 间 的 数 据 交 换, 如 果 不 需 要 使 用 自 定 义 类 型, 你 将 不 用 直 接 调 用 该 类 的 函 数,MFC Wizard 将 自 动 为 程 序 员 建 立 连 接。

 

---- 4. 两 种 方 法 的 比 较

 

---- 综 上 所 述, 使 用 这 两 种 方 法 在Visual C++ 中 都 可 以 很 方 便 地 开 发 出 基 于ORACLE 数 据 库 的 应 用 程 序, 同 时, 这 两 种 方 法 又 各 有 其 优 缺 点。ODBC 由 于 有MFC 强 大 的 类 库 支 持 而 使 得 编 程 实 现 非 常 方 便, 同 时 可 移 植 性 也 很 强, 在 异 构 的 数 据 库 之 间 移 植 也 只 需 更 改 很 少 的 一 部 分 程 序, 但 是, 由ODBC 的 实 现 机 制 我 们 可 以 看 到, 与PRO*C 相 比, 应 用 程 序 需 要 经 过ODBC 驱 动 程 序 管 理 器 和ODBC 驱 动 程 序 两 层 才 能 和 数 据 库 通 信 接 口 建 立 联 系, 而PRO*C 是 直 接 与 通 信 接 口 联 系, 因 此 建 立 在ODBC 上 应 用 程 序 的 执 行 效 率 会 相 对 低 一 些。PRO*C 具 有 执 行 效 率 高, 支 持 嵌 入 式PL/SQL 块 等ORACLE 自 身 特 有 的 优 点, 但 正 因 为 有 了 这 些 优 点, 使 得 用PRO*C 开 发 出 的 应 用 程 序 无 法 向 异 构 数 据 库 平 台 移 植。

 

基于MFC的大型数据文件处理方法

 

在Visual C++中,MFC(微软基础类库)提供了CFile和CStdio

File两个类来进行程序中的文件输入输出操作。Cfile类提供了基于二

进制流的文件操作,功能类似于C语言中的fread()和fwrite()函

数。CStdioFile提供了基于字符串流的文件操作,功能类似于C语言中

的fgets()和fputs()函数。但是,使用这两个类进行文件操作时

,对于一次文件读写的数据量的大小必须限制在65535字节以内。其原

因是在VC中访问大于65535字节的缓冲区需要Huge型指针,而在CFile

和CStdioFile类中,使用的是Far型的指针。由于Far型指针不具有跨

段寻址的能力,因此限制了一次文件读写的长度小于65535字节。如果

传递给CFile和CStdioFile两个类的成员函数的数据缓冲区的大小大于

65535字节的时候,VC就会产生ASSERT错误。

笔者在使用Visual C++进行多媒体程序设计的时候,由于程序

处理的数据量非常大,所以需要频繁地读写大于65535字节的数据。在

使用CFile和CStdioFile类处理巨型数据的时候一般是分段读写,笔者

感到这样的处理方法非常地繁琐,同时容易导致程序编制错误。笔者

在查阅了相关的文献以后,找到了使用Visual C++直接读写巨型数

据的方法。

在MFC的CFile类中提供了两个未载入文档的函数,其原型声明在

AFX H中。函数原型如下:

DWORD CFile::ReadHuge(void FAR *lpBuffer,DWORD dwCo

unt);

void CFile::WriteHuge(const void FAR*lpBuffer,DWORD

dwCount);

在这两个函数内部使用的都是Huge型指针来对传递的缓冲区进行

寻址,因此可以读写大于65535字节的巨型数据。

对于ReadHuge()和WriteHuge()函数需要的巨型缓冲区可以使

用Windows的API函数GobalAlloc()来创建。

作为一个例子,下面的程序段演示了通过使用ReadHuge()和

WriteHuge()函数使用一次读写复制一个大型文件的过程。

{ CString Namel(?data1 dat?);

CString Name2(?data2 dat?);

CFile MyFilel(Namel,CFile::modeRead);

CFile MyFile2(Name2,CFile::modeCreate|CFile::mode

Write);

DWORD Length=MyFile1 GetLength();

void far*p=GlobalAlloc(0,Length);

if(p=NULL)

AfxMessageBox(?Alloc memory error!?);

MyFile1 ReadHuge(p,Length);

MyFile2 ReadHuge(p,Length);

MyFile1 Close();

MyFile2 Close();

AfxMessageBox(?File Copy Succeed!?);

□成都 卢军

 

 

利用ODBC管理数据库

 

 

--------------------------------------------------------------------------------

 

  利用开放数据库互连(Open Database Connectivity,简称ODBC)实现。使用ODBC 可以避免应用程序随数据库的改变而改变,针对不同类型数据库使用不同的API。采用ODBC可以大大缩短开发应用程序的时间。例如:在开发Turbo C 与FoxPro的接口时,需要研究DBF文件的结构,并在设计界面(API)上占用许多时间。针对另一个数据库,就要重新研究相应的API。使用ODBC,更改数据库只需要在应用程序中调整相应的驱动程序。ODBC通过使用驱动程序来提供数据库的独立性。因此,对于不同的数据库就要求使用不同的数据库驱动程序。驱动程序实际上是一个支持ODBC函数调用的模块,通常就是一个动态链接库DLL,应用程序就是通过调用动态链接上的驱动程序所支持的函数来操作数据库的。目前,很多应用程序需要用到数据库,幸好,微软提供ODBC, Windows9x 的控制面板中都有出现。它非常方便地解决了不同数据库需要的连接。这里笔者用VC++5.0编写了一个简单的利用ODBC管理数据库的应用程序Student。需要完成如下一些步骤:

  1. 用Access97创建StuRecord.mdb数据库(其中包含一个表Student.db),作为数据源。

  2. 在操作系统中通过ODBC注册数据库StuRecord.mdb。

  3. 用vc++5.0中的向导AppWizard创建基本的数据库应用程序。

  4.创建数据库显示。

  5.向基本数据库添加一些代码来实现一些AppWizard不能支持的功能,如删除,添加等功能。

  具体实现如下:

  一、用Access97创建StuRecord.mdb数据库,其中表Student.db中内容如下图1所示:

  二、1.在Windows 98的控制面板Control Panel中用鼠标点击32-Bit ODBC图标。此时,出现Data Sources对话框。如图2所示:

  2.单击Add按钮,出现Create New Data Source对话框。从驱动程序列表中选择Microsoft Access Driver并单击Finish按钮。

  3.当ODBC Microsoft Access 97 Setup对话框出现以后,在Data Source Name文本框中输入Student Record,在Description文本框输入Student Record Sample,数据源的名字是一种用于识别读者正在创建的特定数据源的一种简便方法。Description字段使读者能够包含关于数据源的更多的特定信息。

  4.单击Select按钮。出现Select Database文件选择对话框。利用该文件选择对话框定位并选择StuRecord.mdb文件。

  5.单击OK按钮完成数据库选择,接下来在ODBC Microsoft Access97 Setup对话框中单击OK按钮,完成数据源的创建过程。最后,单击ODBC Data Source Administrator对话框中的OK按钮。

  三、1.从vc++5.0的 Developer Studio菜单栏选取File并选择File菜单下的New,单击Project标签。

  2.选择MFC AppWizard(exe)并在Name文本框中键入Student(即项目文件),单击OK按钮,出现Step 1对话框。

  3.选择Single Document,单击Next。

  4.选择Data View Without File Support选项,AppWizard将生成浏览数据库内容的类。该应用程序除了使用数据库之外不使用任何附加的文件,所以,它不需要文件支持,单击Data Source按钮将该应用程序与数据源相连接。

  5.在Database Options对话框中,下拉ODBC列表并选择Student Record数据源,单击OK。

  6.在Select Database Tables对话框中,选择Student表(Student.db),单击OK。Step 2对话框出现,现在,已经建立了Student Record数据源中的Student表(Student.db)与Student应用程序之间的关系。单击Next转到Step 3。

  7.接受缺省值,单击Next。

  8.在Step 4对话框中,关闭Printing and Print Preview选项,单击Next。

  9.接受Step 5中的缺省值,单击Next。在Step 6中只需单击Finish即完成创建Student应用程序所作的选择。随后出现New Project Information对话框。

  10.单击OK,AppWizard创建基本的Student应用程序。这时,可以单击Developer Studio工具栏上的Build按钮来编译应用程序,然后选择菜单栏上的Build下的Execute命令运行。如图3所示:

  四、1.在vc++5.0的 Developer Studio菜单栏选取File并选择File菜单下的Open Workspace&hellip;,选取步骤二中建立的项目文件夹 Student,进入此文件夹打开Student.dsw后,选择Resource View 标签来显示应用程序的资源。

  2.单击Student Resources文件夹旁的+号打开资源树,按同样的方法打开Dialog资源文件夹。双击IDD-STUDENT-FORM对话框ID,在资源编辑器中打开对话框。

  3.单击对话框中央的静态字符串以便选中该字符串,接着按Delete键从对话框中删除该字符串。

  4.用对话框编辑器的编辑工具(Controls)创建如图4所示的对话框,其中增加编辑框(ab|)和静态框(Aa)。给各编辑框的属性(properties)设定了各自的ID(对各框按鼠标右键选择属性,即可填写以下):IDC-STUDENT-ID,IDC-STUDENT-NAME,IDC-STUDENT-RATE,IDC-STUDENT-DEPT。其中IDC-STUDENT-ID编辑框为Read-Only样式(可在Edit Properties属性表单的Styles页面找到),各静态框(Aa)的ID号不变。

  5.选择View菜单下的ClassWizard,单击Member Variables标签。

  6.选择IDC-STUDENT-DEPT资源ID,单击Add Variable按钮,出现Add Member Variable对话框。

  7.单击Member Variables Name的箭头,出现一个下拉列表,选择m-pSet&rarr;m-DeptID,按OK键。

  8.同步骤7一样,选择m-pSet&rarr;m-StudentID,m-pSet&rarr;m-StudentName,m-pSet&rarr;m_StudentRate与对应的编辑控件相关联。

  9.单击MFC ClassWizard属性表单中的OK按钮,完成修改,在用Developer Studio工具栏上Build按钮来编译应用程序,然后选择菜单栏上的Build下的Execute命令如图5所示:

  五、由于篇幅有限,对于添加代码增加的一些用户功能省略,大家可参阅一些vc++方面的资料获取。

  (武汉 金文)

 

 

 

--------------------------------------------------------------------------------

 

 

一种专家数据库的开发与实现

    齐玉东 李逸波

    传统数据库系统缺乏知识,只能处理静态数据;而专家系统的狭窄

应用领域及不能访问现存数据库,又防碍了专家系统的有效应用。数

据库和人工智能这两个领域单独发展的局限性,促使了两者取长补短,

共同发展。这就是专家数据库EDS(Expert Database Syst em)产生和

发展的原因。通常,我们把既具有数据库管理功能及演绎能力、又提

供专家系统中若干良好性能的数据库系统,称为专家数据库。EDS的基

本思想是把以知识表达和知识处理为主的专家系统ES(Expert System

)技术引进传统数据库,使二者有机结合,以开发出能共享信息的面向

知识处理的问题求解系统。目前,EDS主要采用系统耦合--"紧耦合"

及"松耦合"来实现。紧耦合指将规则管理系统集成到DBMS之中,使DBM

S既管理数据库又管理规则库。这种方法实现难度较大。而松耦合是

指将一个现成的专家系统外壳和一个现成的DBMS作为两个独立的子系

统结合在一起,它们分别管理规则库和数据库。采取松耦合实现策略

可以充分发挥原有两个系统的全部功能,而不需对原系统进行任何改

动。它只需设计一个连接ES/DBMS的高效、灵活的接口模块,以协调二

者的工作,所以实现起来时间短、见效快。

    一、故障诊断专家系统的系统结构

    在故障诊断系统HF-2000的研制中,我们采取松耦合策略建立了一

个故障诊断专家数据库系统。该系统是一个产生式系统,采用深度优

先策略作为其控制策略。系统根植于W indows平台,采用了面向对象

的程序设计技术及先进的数据库技术;在数据库端,我们采用了基于Se

rver/Client机制的MS SQL的数据库技术。在推理控制端,利用Visual

 C++进行编程,实现了一个推理机。推理机与数据库之间的接口则通

过ODBC API直接调用来实现对数据库的访问。本系统的构造模型是以

数据库为载体的构模形式,系统机构图如1所示。图1

    图1中知识获取结构负责建立、修改与扩充各个数据库;解释机构

用于对求解过程作

    出说明,指出求解成功或失败的原因,并回答用户提出的问题。事

实库用来存放输入的原始事实及中间结果;字典库用来存放规则中事

实的基本定义和说明;规则库用来存放规则;垃圾桶用来存放推理中失

败的推理路径。

    二、规则与数据库的设计

    1. 产生式规则的模型

    规则的一般形式是:

    if〈前提〉then〈结论〉

    它表示当〈前提〉成立时,得出〈结论〉的可信度为。其中〈前

提〉是事实或断言的合取形式。本系统中的规则模型请参考图2。

    2. 事实库

    结构:FACT_DB(Fact_ID,Rank,No)

    用途:存放输入的原始事实,中间结果及最后结果。

    其中:Fact_ID是事实Fact的编码;Rank用来表示系统特定部分,比

如说"放大级"、"槽路"等;No表示特定部分中的部件的编号;如"1"表

示"槽路"部分1号管、"2"表示"槽路"部分2号管等。

    3. 字典库

    结构:DICT_DB(Fac_ID,Component,Appear,Why,Known)

    用途:存放规则库中的前提条件和结论及其编码。

    其中:Fact_ID为事实编码;Component为部件名称;Appear是对Fac

t_ID的自然语言解释;Known用来表示该事实已知或未知,以防止该断

言的重复求证。

    4. 规则库

    规则库中包括四个表(TABLE),它们是规则前件库(PRE_TABLE)、

已激活的规则前件库(ACTIVE_PRE TABLE)、规则后件库(ACT_TABLE)

和已激活的规则后件库(ACTIVE_ACT_TAB LE)。

图2

    (1)规则前件库

    结构:PRE_TABLE (Rule_Name, Fact_ID)

    用途:存放各条规则对应的前提条件。

    其中:Rule_Name为规则名;Fact_ID为Rule_Name规则的一个与条

件;一条规则的n个与条件在该库中就有n条对应该规则的记录。

    (2)已激活的规则前件库

    结构:ACTIVE_PRE_TABLE(Fact_ID,Rank,No)

    用途:存放已激活的前提条件,以避免规则各前提条件的重复匹配

    其中:Fact_ID为Rule_Name规则的一个与条件;Rank用来表示系统

特定部分;No表示特定部分中的部件的编号。

    (3)规则后件库

    结构:ACT_TABLE (Rule_Name, Fact_ID, Num, Num2)

    用途:存放规则对应的结果。

    其中:Rule_Name为规则名;Fact_ID为Rule_Name规则的结果;Num

表示该规则前提条件的个数;Num2为Num字段的辅助值。

    (4)已激活的规则后件库

    结构:ACTIVE_ACT_TABLE(Rule_Name, Rank,No)

    用途:存放已激活的后件,以避免规则各结论的重复匹配。

    其中:Rule_Name为规则名;Rank用来表示系统特定部分;No表示特

定部分中的部件的编号。

    5. 垃圾桶

    结构:GARBAGE_BIN_DB(Fact_ID, Rule_Name, Pre_Num)

    用途:记录剪去枯死枝叶的原因。

    其中:Fact_ID为事实编码;Rule_Name为应用于该结点的规则名,P

re_Num为实际匹配的前提条件数。

    三、控制机构的设计

    我们用C++语言实现了一个采用深度优先策略的反向推理机。整

个推理过程。就是一棵搜索树边长枝边修枝的过程推理机的源程序如

下:

    int CCause::Reason(RTree*rTree)

    {

    RULE prule,rule;

    int ruleFlag;

    while(1){

    while(true){

    if(rTree- choose rule(&prule)==1

    {

    //对当前叶节点选择适用的规则

    rTree- SctAct(prule);

    //把当前应用规则放入激活的规则表ar

    ruleFlag=1;

    break;

    }

    else{

    ruleFlag=0;//若无规则可用,设置失败标志

    if(depth return 0;

    else{

    rTree- ClipBranch(depth--;path++);//剪去枯枝

    if(depth!=0)

    return 0;

    //若不是树根,返回失败标志;否则继续推理

    }}}

    rTree- SetLeaf(&prule);

    //设置第一个前提为当前叶节点

    depth++;

    while(1){

    int iSucc=0;

    iSucc=rTree- SuccessDirect ();

    //事实是否立即成立

    if(iSucc==0)

    {

    //若事实库中没有对应事实且该节点不是元件,则搜索规则

    while(1){

    if(rTree- choose rule(&rule)==1)

    {

    //搜索对应于当前叶节点的规则

    ruleFlag=1;

    if(BackReason(rTree)==1)

    {

    //若事实成立,则对下一前提进行求证;

    否则选用下一条规则推理

    rTree UnderSuccess();

    //事实成立,重新设置当前前提为当前叶节点

    break;}}

    else{

    ruleFlag=0;//若无规则可选,设置失败标志

    break;}}}

    if(iSucc==1){//若事实立即不成立,则设置失败标志

    ruleFlag=0;

    break;}

    if(ruleFlag==0)

    //当前应用规则失败,选择下一条规则推理

    break;

    else{

    if(prule.num==prule.num2)

    //上一个前提求证成功,判断是否还有未求证的前提

    break://若没有则退出循环

    else

    rTree- SetLeaf(&prule);

    //若有则设置此前提为当前叶节点

    }}

    if(ruleFlag==1){

    rTree- ProduceNewFact();

    //若事实求证成功则往事实库中添加此事实

    return 1;

    //返回成功标志

    }}}

    (作者地址:山东烟台海军航空工程学院研究生队,264001)  

 

--------------------------------------------------------------------------------

 

利用VC++获取异构型数据库库结构信息

 

 

空军电讯工程学院计算机室 万映辉 邸晓奕

摘 要:本文在介绍ODBC技术的基础上,将MFC和ODBC API结合起来创建了两个自定义 类,

实现了对任意异构型数据库库结构信息的获取。

关键字:ODBC,MFC,异构型数据库,记录集

 

一. 问题的提出

  随着数据库技术在各个应用领域的迅速发展,市场上推出了多种数据库系统,为了充分利用资源,实现信息共享,以便用户能对异构型数据库实现透明的访问(包括数据查询、更新和交换等功能),作者开发了异构型数据库通信平台。在平台的研制过程中,获取各种异构型数据库的结构信息是进行数据访问的前提。作者以VC++5.0为开发语言,利用ODBC实现了这一关键技术。

 

二. ODBC技术介绍

  ODBC技术是指开放性数据库连接技术,该技术使应用程序无需关心数据源来自何种DBMS,利用其标准接口实现与数据源之间的数据交换。传统的ODBC编程是利用高级语言(如C语言)调用ODBC的API来实现。应用程序要求驱动程序管理器和每个驱动程序为ODBC环境、每个连接以及每个SQL语句分配信息存储空间,并返回指向各个存储区的句柄供其调用。ODBC接口定义了三种句柄类型:

环境句柄:为全程信息标识内存存储,包括有效连接句柄及当前活动连接句柄。ODBC将环境句柄定义为HENV类型的变量。应用程序使用单一环境句柄,它必须在连接到数据源前请求该句柄。

  连接句柄:为特定连接的信息标识了内存存储。ODBC将连接句柄定义为HDBC类型。应用程序必须在连接到数据源前请求连接句柄。每个连接句柄与环境句柄有关。然而,环境句柄可以有多个与其有关的连接句柄。

语句句柄:为SQL语句信息标识内存存储。ODBC将语句句柄定义为HSTMT类型变量。应用程序必须在提交SQL请求之前请求语句句柄。每个语句句柄与一个连接句柄有关。然而,每个连接句柄可以有多个与其相关的语句句柄。

下面以C语言为例说明传统ODBC编程的一般过程。

1、 环境申请,分配环境句柄

HENV henv;

SQLAllocEnv(&henv);

说明:分配一个环境句柄,支持一个或多个数据源连接。

2、 连接申请,分配连接句柄

HDBC hdbc;

SQLAllocConnect(henv,&hdbc);

说明:一个连接句柄对应一个数据源,可以有多个连接句柄。

3、 连接数据源,用连接句柄连接到数据源

SQLDriverConnect(hdbc,...);

说明:以对话框方式获取注册信息,并连接数据源。

4、 语句申请,分配语句句柄

SQLAllocStmt(hdbc,&hstmt);

说明:获得语句句柄,以便执行SQL语句。

5、 执行SQL语句

SQLExecDirect(hstmt,SQLStatement,..);

说明:利用语句句柄,执行SQL语句。

6、 释放所有资源

SQLfreeStemt(hstmst,...); //释放语句句柄

SQLDisconnect(hdbc); //断开连接

SQLFreeConnect(hdbc); //释放当前数据库连接句柄

SQLFreeEnv(henv); //释放环境句柄

 

三. 利用VC++和ODBC技术获取异构型数据库结构信息

传统的ODBC编程过程比较复杂,各种参数不易理解,且直接获取返回的数据较困难。VC++ 5.0的MFC类库对ODBC的API进行封装,部分简化了ODBC编程(尤其是对数据库记录集的操作),但单纯利用MFC类获取异构型数据库的结构信息仍然比较困难,因此需要将MFC和传统ODBC API编程结合起来。作者利用ODBC接口函数重载了MFC中CRecordset类的部分成员函数,创建CTable和CColumns类。利用这两个新创建的类,可以很方便的获取异构型数据库结构信息。

下面就是关于CTable和Ccolumns类的定义:

class CTable : public CRecordset

{

    virtual CString GetDefaultConnect() { return ""; }

    virtual CString GetDefaultSQL() { return ""; }

public:

                    CTable(CDatabase* pDatabase);

    BOOL             Open(LPCSTR pszTableQualifier = NULL,

                        LPCSTR pszTableOwner = NULL,

                        LPCSTR pszTableName = NULL,

                        LPCSTR pszTableType = NULL,

                        UINT nOpenType = forwardOnly);

    CString             m_strTableQualifier;

    CString             m_strTableOwner;

    CString             m_strTableName;

    CString             m_strTableType;

    CString             m_strRemarks;

    virtual void    DoFieldExchange(CFieldExchange*);

};

 

class CColumns : public CRecordset

{

    virtual CString GetDefaultConnect() { return ""; }

    virtual CString GetDefaultSQL() { return ""; }

public:

                    CColumns(CDatabase* pDatabase);

    BOOL             Open(LPCSTR pszTableQualifier = NULL,

                        LPCSTR pszTableOwner = NULL,

                        LPCSTR pszTableName = NULL,

                        LPCSTR pszColumnName = NULL,

                        UINT nOpenType = forwardOnly);

    CString             m_strTableQualifier;

    CString             m_strTableOwner;

    CString             m_strTableName;

    CString             m_strColumnName;

    int                 m_nDataType;

    CString             m_strTypeName;

    long             m_nPrecision;

    long             m_nLength;    

    int                 m_nScale;

    int                 m_nRadix;

    int                 m_fNullable;

    CString             m_strRemarks;

    virtual void    DoFieldExchange(CFieldExchange*);

};

BOOL CColumns::Open(LPCSTR pszTableQualifier,

    LPCSTR pszTableOwner,LPCSTR pszTableName,LPCSTR pszColumnName,

    UINT nOpenType)

{

    RETCODE    nRetCode;

    UWORD    bFunctionExists;

    //检验是否支持SQLColumns函数

    AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,

        SQL_API_SQLCOLUMNS,&bFunctionExists));

    if (!Check(nRetCode) || !bFunctionExists)

    {

        if (!bFunctionExists)

            TRACE(_T("SQLColumns 不支持\n"));

        return FALSE;

    }

    //设置缓冲区状态,分配语句句柄

    SetState(nOpenType,NULL,readOnly);

    if (!AllocHstmt())

        return FALSE;

    TRY

    {

        OnSetOptions(m_hstmt);

        AllocStatusArrays();

        // 调用ODBC的SQLColumns函数

        AFX_ODBC_CALL(::SQLColumns(m_hstmt,

            (UCHAR FAR*)pszTableQualifier,SQL_NTS,

            (UCHAR FAR*)pszTableOwner,SQL_NTS,

            (UCHAR FAR*)pszTableName,SQL_NTS,

            (UCHAR FAR*)pszColumnName,SQL_NTS));

        if (!Check(nRetCode))

            ThrowDBException(nRetCode,m_hstmt);

        // 分配内存,填写信息

        AllocAndCacheFieldInfo();

        AllocRowset();

        MoveNext();

        m_bBOF = m_bEOF;

    }

//异常信息的捕获

    CATCH_ALL(e)

    {

        Close();

        THROW_LAST();

    }

    END_CATCH_ALL

    return TRUE;

}

//获取记录集信息

void CColumns::DoFieldExchange(CFieldExchange* pFX)

{

    pFX->SetFieldType(CFieldExchange::outputColumn);

    RFX_Text(pFX,_T("TABLE_QUALIFIER"),m_strTableQualifier);

    RFX_Text(pFX,_T("TABLE_OWNER"),m_strTableOwner);

    RFX_Text(pFX,_T("TABLE_NAME"),m_strTableName);

    RFX_Text(pFX,_T("COLUMN_NAME"),m_strColumnName);

    RFX_Int(pFX,_T("DATA_TYPE"),m_nDataType);

    RFX_Text(pFX,_T("TYPE_NAME"),m_strTypeName);

    RFX_Long(pFX,_T("PRECISION"),m_nPrecision);

    RFX_Long(pFX,_T("LENGTH"),m_nLength);

    RFX_Int(pFX,_T("SCALE"),m_nScale);

    RFX_Int(pFX,_T("RADIX"),m_nRadix);

    RFX_Int(pFX,_T("NULLABLE"),m_fNullable);

    RFX_Text(pFX,_T("REMARKS"),m_strRemarks);

}

CColumns::CColumns(CDatabase* pDatabase): CRecordset(pDatabase)

{

    m_strTableQualifier    = _T("");

    m_strTableOwner        = _T("");

    m_strTableName        = _T("");

    m_strColumnName        = _T("");

    m_nDataType             = 0;

    m_strTypeName        = _T("");

    m_nPrecision        = 0;

    m_nLength             = 0;

    m_nScale             = 0;

    m_nRadix             = 0;

    m_fNullable             = 0;

    m_strRemarks        = _T("");

    m_nFields = 12;

}

 

CTable::CTable(CDatabase* pDatabase): CRecordset(pDatabase)

{

    m_strTableQualifier    = _T("");

    m_strTableOwner        = _T("");

    m_strTableName        = _T("");

    m_strTableType        = _T("");

    m_strRemarks        = _T("");

    m_nFields = 5;

}

 

BOOL CTable::Open(LPCSTR pszTableQualifier,

    LPCSTR pszTableOwner,LPCSTR pszTableName,LPCSTR pszTableType,

    UINT nOpenType)

{

    RETCODE    nRetCode;

    UWORD    bFunctionExists;

    //检验是否支持SQLTables 函数

    AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,

        SQL_API_SQLTABLES,&bFunctionExists));

    if (!Check(nRetCode) || !bFunctionExists)

    {

        if (!bFunctionExists)

            TRACE(_T("SQLTables 不支持\n"));

        return FALSE;

    }

    //设置缓冲区状态,分配语句句柄

    SetState(nOpenType,NULL,readOnly);

    if (!AllocHstmt())

        return FALSE;

    TRY

    {

        OnSetOptions(m_hstmt);

        AllocStatusArrays();

        //调用 ODBC的SQLTables函数

        AFX_ODBC_CALL(::SQLTables(m_hstmt,

            (UCHAR FAR*)pszTableQualifier,SQL_NTS,

            (UCHAR FAR*)pszTableOwner,SQL_NTS,

            (UCHAR FAR*)pszTableName,SQL_NTS,

            (UCHAR FAR*)pszTableType,SQL_NTS));

        if (!Check(nRetCode))

            ThrowDBException(nRetCode,m_hstmt);

        // 分配内存,填写信息

        AllocAndCacheFieldInfo();

        AllocRowset();

        MoveNext();

        m_bBOF = m_bEOF;

    }

//异常信息的捕获

    CATCH_ALL(e)

    {

        Close();

        THROW_LAST();

    }

    END_CATCH_ALL

    return TRUE;

}

void CTable::DoFieldExchange(CFieldExchange* pFX)

{

    pFX->SetFieldType(CFieldExchange::outputColumn);

    RFX_Text(pFX,_T("TABLE_QUALIFIER"),m_strTableQualifier);

    RFX_Text(pFX,_T("TABLE_OWNER"),m_strTableOwner);

    RFX_Text(pFX,_T("TABLE_NAME"),m_strTableName);

    RFX_Text(pFX,_T("TABLE_TYPE"),m_strTableType);

    RFX_Text(pFX,_T("REMARKS"),m_strRemarks);

}

以上两个类对CRecordset的Open和DoFieldExchange函数进行了重载。应用程序可以在需要时创建CTable或Ccolumns类,并调用OPEN成员函数建立相应的表结构和字段结构记录集。接下来就可以通过下列函数来遍历异构型数据库的结构信息了。

    Void CRecordset::MoveFirst(); //移到第一条记录

Void CRecordset::MoveLast(); //移到最后一条记录

Void CRecordset::MovePrev(); //移到前一条记录

Void CRecordset::MoveNext(); //移到后一条记录

BOOL CRecordset::IsBOF(); //判断是否到达第一条记录前

BOOL CRecordset::IsEOF(); //判断是否到达最后一条记录后

 

四、结束语

利用自定义的CTable和Ccolumns类,应用程序能获取任何异构型数据库库结构信息。根据获得的信息可以方便的对未知数据库进行相应的操作。若将CTable和Ccolumns类与文档类、视类结合起来,就可以在窗口里以一定的方式显示结构信息。作者利用以上技术在异构型数据库通信平台上成功实现了对各种异构型数据库库结构信息的获取。

 

参考文献:

Mircosoft 《ODBC 2.0 Programmer's Reference and SDK Guide》

利用VC++获取异构型数据库库结构信息

 

    7

 

 

 

Visual C++中的ODBC编程实例

 

  ODBC是一种使用SQL的程序设计接口,使用ODBC使数据库应用程序 的编写者避免了与数据源相连接的复杂性。这项技术目前已经得到了 大多数DBMS厂商的广泛支持。

  Microsoft Developer Studio为大多数标准的数据库格式提供了 32位ODBC驱动器。这些标准数据格式包括有:SQL Server、Access、P aradox、dBase、FoxPro、Excel、Or acle以及Microsoft Text。如 果用户希望使用其他数据格式,则需要安装相应的ODBC驱动器及DBMS 。

  用户使用自己的DBMS数据库管理功能生成新的数据库模式后,就 可以使用ODBC来登录数据源。对用户的应用程序来说,只要安装有驱 动程序,就能注册很多不同的数据库。登录数据库的具体操作参见有 关ODBC的联机帮助。

  MFC提供的ODBC数据库类

  Visual C++的MFC基类库定义了几个数据库类。在利用ODBC编程 时,经常要使用到 CDatabase(数据库类)、CRecordSet(记录集类)和 CRecordView(可视记录集类)。

  CDatabase类对象提供了对数据源的连接,通过它可以对数据源进 行操作。

  CRecordSet类对象提供了从数据源中提取出的记录集。CRecordS et对象通常用于两种形式:动态行集(dynasets)和快照集(snapshots) 。动态行集能与其他用户所做的更改保持同步,快照集则是数据的一 个静态视图。每种形式在记录集被打开时都提供一组记录,所不同的 是,当在一个动态行集里滚动到一条记录时,由其他用户或应用程序中 的其他记录集对该记录所做的更改会相应地显示出来。

  CRecordView类对象能以控件的形式显示数据库记录,这个视图是 直接连到一个CRec ordSet对象的表视图。

  应用ODBC编程

  应用Visual C++的AppWizard可以自动生成一个ODBC应用程序框 架,步骤是:打开Fil e菜单的New选项,选取Projects,填入工程名,选 择MFC AppWizard (exe),然后按AppWiza rd的提示进行操作。

  当AppWizard询问是否包含数据库支持时,如果想读写数据库,那 么选定Database vi ew with file support;如果想访问数据库的信 息而不想回写所做的改变,那么选定Data base view without file s upport。

  选择了数据库支持之后,Database Source 按钮会被激活,选中它 去调用Data Optio ns对话框。在Database Options对话框中会显示 出已向ODBC注册的数据库资源,选定所要操作的数据库,如:Super_ES, 单击OK后出现Select Database Tables对话框,其中列举了选中的数 据库包含的全部表;选择要操作的表后,单击OK。在选定了数据库和数 据表之后,就可以按照惯例继续进行AppWizard操作。

  特别需要指出的是:在生成的应用程序框架View类(如:CSuper_ES View)中,包含一个指向CSuper_ESSet对象的指针m_pSet,该指针由App Wizard建立,目的是在视表单和记录集之间建立联系,使得记录集中的 查询结果能够很容易地在视表单上显示出来。

  要使程序与数据源建立联系,需用CDatebase::OpenEx()或CDatab ase::Open()函数来进行初始化。数据库对象必须在使用它构造记录 集对象之前初始化。

  下面举例说明在Visual C++环境中ODBC的编程应用。

  1.查询记录

  查询记录使用CRecordSet::Open()和CRecordSet::Requery()成 员函数。在使用CRe cordSet类对象之前,必须使用CRecordSet::Open ()函数来获得有效的记录集。一旦已经使用过CRecordSet::Open()函 数,再次查询时就可以应用CRecordSet::Requery()函数。

  在调用CRecordSet::Open()函数时,如果将一个已经打开的CData base对象指针传给CRecordSet类对象的m_pDatabase成员变量,则使用 该数据库对象建立ODBC连接;否则如果m_pDatabase为空指针,就新建 一个CDatabase类对象,并使其与缺省的数据源相连,然后进行CRecord Set类对象的初始化。缺省数据源由GetDefaultConnect()函数获得。 也可以提供所需要的SQL语句,并以它来调用CRecordSet::Open()函数 ,例如:Super_ESSet.Open(A FX_DATABASE_USE_DEFAULT,strSQL);

  如果没有指定参数,程序则使用缺省的SQL语句,即对在GetDefaul tSQL()函数中指定的SQL语句进行操作:

  CString CSuper_ESSet::GetDefaultSQL()

  {return _T("[BsicData],[MinSize]");}

  对于GetDefaultSQL()函数返回的表名,对应的缺省操作是SELECT 语句,即:

  SELECT * FROM BasicData,MainSize

  在查询过程中,也可以利用CRecordSet的成员变量m_strFilter和 m_strSort来执行条件查询和结果排序。m_strFilter为过滤字符串, 存放着SQL语句中WHERE后的条件串;m_s trSort为排序字符串,存放着 SQL语句中ORDER BY后的字符串。如:

  Super_ESSet.m_strFilter="TYPE=?电动机?";

  Super_ESSet.m_strSort="VOLTAGE";

  Super_ESSet.Requery();

  对应的SQL语句为:

  SELECT * FROM BasicData,MainSize

  WHERE TYPE=?电动机?

  ORDER BY VOLTAGE

  除了直接赋值给m_strFilter以外,还可以使用参数化。利用参数 化可以更直观、更方便地完成条件查询任务。使用参数化的步骤如下 :

  S声明参变量:

  CString p1;

  float p2;

  S在构造函数中初始化参变量:

  p1=_T("");

  p2=0.0f;

  m_nParams=2;

  S将参变量与对应列绑定:

  pFX->SetFieldType(CFieldExchange::param)

  RFX_Text(pFX,_T("P1"),p1);

  RFX_Single(pFX,_T("P2"),p2);

  完成以上步骤后就可以利用参变量进行条件查询:

  m_pSet->m_strFilter="TYPE=? AND VOLTAGE=?";m_pSet->p1=" 电动机";

  m_pSet->p2=60.0;

  m_pSet->Requery();

  参变量的值按绑定的顺序替换查询字串中的"?"适配符。

  如果查询的结果是多条记录,可以用CRecordSet类的函数Move() 、MoveNext()、Mov ePrev()、MoveFirst()和MoveLast()来移动光标 。

  2.增加记录

  增加记录使用AddNew()函数,要求数据库必须是以允许增加的方 式打开:

  m_pSet->AddNew(); //在表的末尾增加新记录

  m_pSet->SetFieldNull(&(m_pSet->m_type), FALSE);

  m_pSet->m_type="电动机";

  ……

   //输入新的字段值

  m_pSet->Update();

   //将新记录存入数据库

  m_pSet->Requery();

  //重建记录集

  3.删除记录

  可以直接使用Delete()函数来删除记录,并且在调用Delete()函 数之后不需调用Upd ate()函数:

  m_pSet->Delete();

  if (!m_pSet->IsEOF())

  m_pSet->MoveNext();

  else

  m_pSet->MoveLast();

  4.修改记录

  修改记录使用Edit()函数:

  m_pSet->Edit();

  //修改当前记录

  m_pSet->m_type="发电机";

   //修改当前记录字段值

  ……

  m_pSet->Update(); //将修改结果存入数据库

  m_pSet->Requery();

  5.撤消操作

  如果用户选择了增加或者修改记录后希望放弃当前操作,可以在 调用Update()函数之前调用:

  CRecordSet::Move(AFX_MOVE_REFRESH);来撤消增加或修改模式, 并恢复在增加或修改模式之前的当前记录。其中,参数AFX_MOVE_REFR ESH的值为零。

  6.数据库连接的复用

  在CRecordSet类中定义了一个成员变量m_pDatabase:

  CDatabase* m_pDatabase;

  它是指向对象数据库类的指针。如果在CRecordSet类对象调用Op en()函数之前,将一个已经打开的CDatabase类对象指针传给m_pDatab ase,就能共享相同的CDatabase类对象。如:

  CDatabase m_db;

  CRecordSet m_set1,m_set2;

  m_db.Open(_T("Super_ES")); //建立ODBC连接

  m_set1.m_pDatabase=&m_db;

  //m_set1复用m_db对象

  m_set2.m_pDatabse=&m_db;

  // m_set2复用m_db对象

  7.SQL语句的直接执行

  虽然我们可以通过CRecordSet类完成大多数的查询操作,而且在C RecordSet::Open( )函数中也可以提供SQL语句,但是有时候我们还是 希望进行一些其他操作,例如建立新表、删除表、建立新的字段等,这 时就需要使用CDatabase类直接执行SQL语句的机制。通过调用CDatab ase::ExecuteSQL()函数来完成SQL语句的直接执行:

  BOOL CDB::ExecuteSQLAndReportFailure(const CString& strS QL)

  {

  TRY

  {

  m_pdb->ExecuteSQL(strSQL);

  //直接执行SQL语句

  }

  CATCH (CDBException,e)

  {

  CString strMsg;

  strMsg.LoadString(IDS_EXECUTE_SQL_FAILED);

  strMsg+=strSQL;

  return FALSE;

  }

  END_CATCH

  return TRUE;

  }

  应当指出的是,由于不同的DBMS提供的数据操作语句不尽相同,直 接执行SQL语句可能会破坏软件的DBMS无关性,因此在应用中应当慎用 此类操作。

  8.动态连接表

  表的动态连接可以利用在调用CRecordSet::Open()函数时指定SQ L语句来实现。同一个记录集对象只能访问具有相同结构的表,否则查 询结果将无法与变量相对应。

  void CDB::ChangeTable()

  {

  if (m_pSet->IsOpen()) m_pSet->Close();

  switch (m_id)

  {

  case 0:

   m_pSet->Open(AFX_DB_USE_DEFAULT_TYPE,

  "SELECT * FROM SLOT0");

  //连接表SLOT0

  m_id=1;

  break;

  case 1:

  m_pSet->Open(AFX_DB_USE_DEFAULT_TYPE,

  "SELECT * FROM SLOT1"); //连接表SLOT1

  m_id=0;

  break;

  }

  }

  9.动态连接数据库

  可以通过赋与CRecordSet类对象参数m_pDatabase来连接不同数 据库的CDatabase对象指针,从而实现动态连接数据库。

  void CDB::ChangeConnect()

  {

  CDatabase* pdb=m_pSet->m_pDatabase;

  pdb->Close();

  switch (m_id)

  {

  case 0:

  if (!pdb->Open(_T("Super_ES")))

   //连接数据源Super_ES

   {

  AfxMessageBox("数据源Super_ES打开失败,""请检查相应的ODBC 连接", MB_OK|MB_ ICONWARNING);

   exit(0);

   }

  m_id=1;

  break;

  case 1:

  if (!pdb->Open(_T("Motor")))

   //连接数据源Motor

  {

  AfxMessageBox("数据源Motor打开失败,""请检查相应的ODBC连 接", MB_OK|MB_ICO NWARNING);

  exit(0);

  }

  m_id=0;

  break;

  }

  }

  总结

  Visual C++中的ODBC类库可以帮助程序员完成绝大多数的数据库 操作。利用ODBC技术使得程序员从具体的DBMS中解脱出来,从而可以 减少软件开发的工作量,缩短开发周期,并提高效率和软件的可靠性。

 

 

calling stored procedures

 

--------------------------------------------------------------------------------

 

this article was contributed by craig lucas.

 

this article goes out to all mfc programmers who wish to improve their applications performance and those who have tried calling stored procedures and failed.

 

your first question may be, why use stored procedures when i can just say,

 

 

rs.open( crecordset::snapshot, "select * from clients where

account_number = '1234567'");.

 

well theres 2 answers that i can think of.  first off, stored procedures are tremendously fast. secondly, stored procedures are more reliable.

 

from what i've tested on a 200 record table, after the initial connection stored procedures return instantly!! where an sql statement returns in 500ms on the initial connection and 100ms thereafter. this can be very helpful in the client/server environment and in any application that is database intensive.

 

this example calls a stored procedure with the following definition:

 

 

create procedure outputparams @inputparam char(20) , @outputparam

char(20) = '' output as

 

select @outputparam = @inputparam

return 1

 

basically all it does is return the same string you send it.  you can define all the paramaters you like.  the reason for this example was to show how to return a character output parameter. mfc does not support it. so you have to create your own rfx_text function to support it.

 

if you would like to call a stored procedure that returns a recordset, its not much different. the vc help files tell you exactly how to do it.  you can do a searh on "stored procedures and recordsets". 

 

 

// storedprocedure.h : header file

//

 

/*

craig lucas, coderman@netrox.net

*/

#include "afxdb.h"

/////////////////////////////////////////////////////////////////////////////

// storedprocedure recordset

 

class storedprocedure : public crecordset

{

public:

         storedprocedure(cdatabase* pdatabase = null);

         declare_dynamic(storedprocedure)

                  

         // field/param data

         //{{afx_field(storedprocedure, crecordset)

         long m_retreturn_value;

         cstring m_paraminputparam;  //the input param

         cstring m_paramoutputparam; //the output param

         //}}afx_field

        

         // overrides

         // classwizard generated virtual function overrides

         //{{afx_virtual(storedprocedure)

public:

         virtual cstring getdefaultconnect();    // default connection string

         virtual cstring getdefaultsql();    // default sql for recordset

         virtual void dofieldexchange(cfieldexchange* pfx);  // rfx support

         virtual void move( long nrows, word wfetchtype = sql_fetch_relative );

         //}}afx_virtual

        

         // implementation

#ifdef _debug

         virtual void assertvalid() const;

         virtual void dump(cdumpcontext& dc) const;

#endif

};

 

 

 

// storedprocedure.cpp : implementation file

//

 

#include "stdafx.h"

#include "storedprocedure.h"

 

#ifdef _debug

#define new debug_new

#undef this_file

static char this_file[] = __file__;

#endif

 

 

void afxapi rfx_textout(cfieldexchange* pfx, lpctstr szname,

cstring& value, int nmaxlength, int ncolumntype, short nscale);

/////////////////////////////////////////////////////////////////////////////

// storedprocedure

 

implement_dynamic(storedprocedure, crecordset)

 

storedprocedure::storedprocedure(cdatabase* pdb)

: crecordset(pdb)

{

         //{{afx_field_init(storedprocedure)

         m_retreturn_value = 0;

         m_paraminputparam = _t("");

         m_paramoutputparam = _t("");

         m_nfields = 0;

         //}}afx_field_init

         m_nparams = 3;

         m_ndefaulttype = snapshot;

}

 

 

cstring storedprocedure::getdefaultconnect()

{

         return _t("odbc;dsn=codeguru;uid=sa;pwd=;");

}

 

cstring storedprocedure::getdefaultsql()

{

         return _t("{? = call outputparams;1 (?,?)}");

         // this is the sql string to call a stored procedure

         // 1 question mark for every parameter in the

         // stored procedure

}

 

void storedprocedure::dofieldexchange(cfieldexchange* pfx)

{

         //{{afx_field_map(storedprocedure)

        

         //}}afx_field_map

        

         //make sure these are put outside the afx_field_map comments

         pfx->setfieldtype(cfieldexchange::outputparam); //set the field type to

         outputparam for the return value

                   rfx_long(pfx, "return_value", m_retreturn_value); //bind the return value

         to the variable

                   pfx->setfieldtype(cfieldexchange::inputparam); //reset the field type to

         inputparam

                   rfx_textout(pfx, "@inputparam", m_paraminputparam,255,sql_char,0); //call

         the new rfx_text to get the character output params

                   pfx->setfieldtype(cfieldexchange::inoutparam); // reset the field

         type to receive the output param

                   rfx_textout(pfx, "@outputparam", m_paramoutputparam,255,sql_char,0);

         //bind the output parameter to the variable

}

 

/////////////////////////////////////////////////////////////////////////////

// storedprocedure diagnostics

 

#ifdef _debug

void storedprocedure::assertvalid() const

{

         crecordset::assertvalid();

}

 

void storedprocedure::dump(cdumpcontext& dc) const

{

         crecordset::dump(dc);

}

#endif //_debug

 

void storedprocedure::move( long nrows, word wfetchtype )

{

         // protection so that if the procedure returns no result sets, no

         // fetch operations are attempted.

         if (m_nfields)

                   crecordset::move(nrows, wfetchtype);

         else

                   m_bbof = m_beof = true;

}

 

void afxapi rfx_textout(cfieldexchange* pfx, lpctstr szname,

                                                        cstring& value, int nmaxlength, int ncolumntype, short nscale)

{

         //this is mfc's rfx_text function with 2 modifications:

         //   1. all unicode definitions are removed for brevity.

         //   2. a fixed version of sqlbindparamaters() was inserted

        

         assert(afxisvalidaddress(pfx, sizeof(cfieldexchange)));

         assert(afxisvalidstring(szname));

         assert(afxisvalidaddress(&value, sizeof(cstring)));

        

         retcode nretcode;

         uint nfield;

         if (!pfx->isfieldtype(&nfield))

                   return;

        

         long* pllength = pfx->m_prs->getfieldlengthbuffer(

                   nfield - 1, pfx->m_nfieldtype);

         switch (pfx->m_noperation)

         {

         default:

                   pfx->default(szname, value.getbuffer(0), pllength,

                            sql_c_char, value.getlength(), nmaxlength);

                   value.releasebuffer();

                   return;

                  

         case cfieldexchange::bindparam:

                   {

                            // preallocate to nmaxlength and setup binding address

                            value.getbuffersetlength(nmaxlength);

                            void* pvparam = value.lockbuffer(); // will be overwritten if unicode

                            *pllength = pfx->m_prs->isparamstatusnull(nfield - 1) ?

                                                                           sql_null_data : sql_nts;

                           

                            // this is the new version of sqlbindparamaters with the 2nd to last

                            param fixed.

                                     afx_sql_sync(::sqlbindparameter(pfx->m_hstmt, (uword)nfield,

                                     (sword)pfx->m_nfieldtype, sql_c_char, (sword)ncolumntype,

                                     nmaxlength, nscale, pvparam, nmaxlength, pllength));

                           

                            //the reason character output params can not be returned was because

                            //ms had hardcoded the max_buffer length to 0.

                           

                            /*   this was the old version of sqlbindparamaters.

                            afx_sql_sync(::sqlbindparameter(pfx->m_hstmt, (uword)nfield,

                            (sword)pfx->m_nfieldtype, sql_c_char, (sword)ncolumntype,

                            nmaxlength, nscale, pvparam, 0, pllength));

                            */

                           

                            value.releasebuffer();

                           

                            if (nretcode != sql_success)

                                     pfx->m_prs->throwdbexception(nretcode, pfx->m_hstmt);

                           

                            // add the member address to the param map

                            pfx->m_prs->m_mapparamindex.setat(&value, (void*)nfield);

                   }

                   return;

         case cfieldexchange::bindfieldtocolumn:

                   {

                            // assumes all bound fields before unbound fields

                            codbcfieldinfo* podbcinfo =

                                     &pfx->m_prs->m_rgodbcfieldinfos[nfield - 1];

                            uint cbcolumn = podbcinfo->m_nprecision;

                           

                            switch (podbcinfo->m_nsqltype)

                            {

                            default:

#ifdef _debug

                                     // warn of possible field schema mismatch

                                     if (afxtraceflags & tracedatabase)

                                               trace1("warning: cstring converted from sql type %ld.\n",

                                               podbcinfo->m_nsqltype);

#endif // _debug

                                    

                                     // add room for extra information like sign, decimal point, etc.

                                     cbcolumn += 10;

                                     break;

                                    

                            case sql_longvarchar:case sql_char: case sql_varchar:

                                     break;

                                    

                            case sql_float: case sql_real:case sql_double:

                                     // add room for sign, decimal point and " e +xxx"

                                     cbcolumn += 10;

                                     break;

                                    

                            case sql_decimal:case sql_numeric:

                                     // add room for sign and decimal point

                                     cbcolumn += 2;

                                     break;

                                    

                            case sql_timestamp: case sql_date:case sql_time:

                                     // may need extra space, i.e. "{ts mm/dd/yyyy hh:mm:ss}"

                                     cbcolumn += 10;

                                     break;

                                    

                            case sql_tinyint:case sql_smallint: case sql_integer:case sql_bigint:

                                     // add room for sign

                                     cbcolumn += 1;

                                     break;

                            }

                           

                            // constrain to user specified max length, subject to 256 byte min

                            if (cbcolumn > (uint)nmaxlength || cbcolumn <256) cbcolumn="nmaxlength;" // set up binding addres

                            void* pvdata;

                            value.getbuffersetlength(cbcolumn+1);

                            pvdata = value.lockbuffer();    // will be overwritten if unicode

                           

                            afx_sql_sync(::sqlbindcol(pfx->m_prs->m_hstmt, (uword)nfield,

                                     sql_c_char, pvdata, cbcolumn+1, pllength));

                            value.releasebuffer();

                            if (!pfx->m_prs->check(nretcode))

                                     pfx->m_prs->throwdbexception(nretcode);

                           

                            // add the member address to the field map

                            pfx->m_prs->m_mapfieldindex.setat(&value, (void*)nfield);

                   }

                   return;

                  

         case cfieldexchange::fixup:

                   if (*pllength == sql_null_data)

                   {

                            pfx->m_prs->setnullfieldstatus(nfield - 1);

                            value.getbuffersetlength(0);

                            value.releasebuffer();

                   }

                   else

                   {

                            lptstr lpsz = value.getbuffer(0);

                            if (pfx->m_prs->m_pdatabase->m_bstriptrailingspaces)

                            {

                                     // find first trailing space

                                     lptstr lpszfirsttrailing = null;

                                     while (*lpsz != '\0')

                                     {

                                              if (*lpsz != ' ')

                                                        lpszfirsttrailing = null;

                                               else

                                               {

                                                        if (lpszfirsttrailing == null)

                                                                 lpszfirsttrailing = lpsz;

                                               }

                                               lpsz = _tcsinc(lpsz);

                                     }

                                     // truncate

                                     if (lpszfirsttrailing != null)

                                               *lpszfirsttrailing = '\0';

                                    

                            }

                            value.releasebuffer();

                            *pllength = value.getlength();

                   }

                   return;

                  

         case cfieldexchange::setfieldnull:

                   if ((pfx->m_pvfield == null &&

                            pfx->m_nfieldtype == cfieldexchange::outputcolumn) ||

                            pfx->m_pvfield == &value)

                   {

                            if (pfx->m_bfield)

                            {

                                     // mark fields null

                                     pfx->m_prs->setnullfieldstatus(nfield - 1);

                                     // set string 0 length

                                     value.getbuffersetlength(0);

                                     value.releasebuffer();

                                     *pllength = sql_null_data;

                            }

                            else

                            {

                                     pfx->m_prs->clearnullfieldstatus(nfield - 1);

                                     *pllength = sql_nts;

                            }

#ifdef _debug

                            pfx->m_nfieldfound = nfield;

#endif

                   }

                   return;

                  

         case cfieldexchange::markforaddnew:

                   // can force writing of psuedo-null value (as a non-null) by setting

                   field dirty

                            if (!value.isempty())

                            {

                                     pfx->m_prs->setdirtyfieldstatus(nfield - 1);

                                     pfx->m_prs->clearnullfieldstatus(nfield - 1);

                            }

                            return;

                           

         case cfieldexchange::markforupdate:

                   if (value.isempty())

                            pfx->m_prs->setnullfieldstatus(nfield - 1);

                   else

                            pfx->m_prs->clearnullfieldstatus(nfield - 1);

                   pfx->default(szname, &value, pllength,

                            sql_c_char, value.getlength(), nmaxlength);

                   return;

                  

         case cfieldexchange::loadfield:

                   {

                            // get the field data

                            cfieldinfo* pinfo = &pfx->m_prs->m_rgfieldinfos[nfield - 1];

                            cstring* pstrcachedvalue = (cstring*)pinfo->m_pvdatacache;

                           

                            // restore the status

                            pfx->m_prs->setfieldstatus(nfield - 1, pinfo->m_bstatus);

                           

                            // if not null, restore the value and length

                            if (!pfx->m_prs->isfieldstatusnull(nfield - 1))

                            {

                                     value = *pstrcachedvalue;

                                     *pllength = value.getlength();

                            }

                            else

                            {

                                     *pllength = sql_null_data;

                            }

                           

#ifdef _debug

                            // buffer address must not change - odbc's sqlbindcol depends upon this

                            void* pvbind;

                           

#ifdef _unicode

                            pvbind = pfx->m_prs->m_pvfieldproxy[nfield-1];

#else // !_unicode

                            pvbind = value.getbuffer(0);

                            value.releasebuffer();

#endif

                           

                            if (pvbind != pinfo->m_pvbindaddress)

                            {

                                     trace1("error: cstring buffer (column %u) address has changed!\n",

                                               nfield);

                                     assert(false);

                            }

#endif // _debug

                   }

                   return;

                  

         case cfieldexchange::storefield:

                   afxstorefield(*pfx->m_prs, nfield, &value);

                   return;

                  

         case cfieldexchange::alloccache:

                   {

                            cfieldinfo* pinfo = &pfx->m_prs->m_rgfieldinfos[nfield - 1];

                            pinfo->m_pvdatacache = new cstring;

                            pinfo->m_ndatatype = afx_rfx_text;

                   }

                   return;

                  

#ifdef _debug

         case cfieldexchange::dumpfield:

                   *pfx->m_pdcdump << "\n" << szname << "=" << value;

                   <font color=" #0000ff">return;

#endif // _debug

                  

         }

}

 

 

classes for direct sql calls with odbc

 

--------------------------------------------------------------------------------

 

this article was contributed by dave merner.

 

during my current project, i found that i needed more functionality with my sql calls than crecordset or cdaorecordset had to offer. these classes wrap the low-level odbc api calls and act simular to a crecordset, but allow me to execute any sql statement without having to bind variables,etc.

 

the main class for doing this is csqldirect. csqldirect has the following attributes/functions:

 

csqldirect::connect - connects to a datasource.

csqldirect::executesql - this is the main function that is used for handling the sql statement.

csqldirect::getcol - will return a column from a table in the resulting cursor.

csqldirect::geterror - provides detailed error messages in case something went wrong.

csqldirect::getcolumntype - provides information about a cursor's column.

csqldirect::fetch - will properly execute a sqlfetch command with error handling.

csqldirect::close - closes the connection to the datasource.

the other class csqlcolumn is a support class for csqldirect. since multiple queries to a cursor's column will result in a null value being returned, i found it necessary to keep track of the columns as they are used. this is stored in a cptrarray within csqldirect and is cleaned up after each time the cursor is requeried/closed.

 

an example of using this class to make sql calls:

 

 

csqldirect sqldirect( "testdata" );

 

if( sqldirect.executesql( "select * from employee" )==sql_success ) {

         while( sqldirect.fetch()==sql_success ) {

                   .

                   // do some stuff here

                   .

         }

}

 

that's it!

 

the great thing about this class is you no longer have need for a huge assortment of crecordset classes for every table/query.

 

anyways i hope this can be of help to anyone that uses the site. don't hesitate to give me a shout if anyone has any questions/comments.

 

thanks for all the help that codeguru has given my over the last few months!

 

download source 3k

 

posted on: march 8,98

 

sample database program using dialog interface

 

--------------------------------------------------------------------------------

 

this article was contributed by joel mcalduff.

 

[this assumes the user has installed the necessary dao files, included on the visual c++ cd, and that you are using vc++ version 5.0 or later]

 

overview:

occasionally on the discussion board there are requests for examples of how to access database information. one common method is a dialog-based retrieval of data from a record, based on search information provided by the user.

 

this is a sample program i wrote that allows you to access an employee's record based on their ssn. i created a small test database (db1.mdb) using microsoft access 2.0 that has one table called "employees". social security numbers are used as the primary key. there are 3 records in this database:

 

123-45-6789 (john doe)

987-65-4321 (jane smith)

456-78-9123 (jack jones)

 

data fields include:

name [text field]

address [memo field]

title [text field]

hire date [date/time field]

social security number (ssn) [number field (double), primary key].

 

the demo program:

the test program is called "database". it will open as a centered, small, single-document view, based on the cformview class. the user opens the desired database (in this case, db1.mdb) using the standard file open dialog. after the database is successfully opened, the user enters the employee ssn in the edit box provided in the formview window (do not include the dashes). click on the "find record" button and a dialog will be displayed with the matching employee's data. changes can be made to the data and will be saved to the database if the user clicks the ok button. clicking the cancel button will not save any changes to the database.

 

i chose the data access object (dao) architecture to open the database and create the recordset. i've found that dao seems to work better when interfacing exclusively with microsoft access (*.mdb) databases. you should, however, be able to use almost identical code/techniques applying the odbc method. for example, a recordset class using the dao system would be based on the cdaorecordset class. a recordset using odbc would be based on the crecordset class. when you generate a dao class without using the "new project" wizard (as is demonstrated in this tutorial), be sure to include the dao header file "afxdao.h" in your stdafx.h file.

 

creating the database program:

the next several sections will take you through the method i used to create the sample program. first, create a new workspace using the "mfc appwizard (exe)". i named my program "database". scroll through the wizard, choose single document interface, "none" for database support (that's right, you don't need the wizard to do this!), whatever other features you want, then at step 6, select your view class, press the down arrow of the "base class" combobox, and choose "cformview". then click ok and let developer studio do the rest. go ahead and compile your program to make sure everything is working (so far).

 

center the main program window:

in the initinstance() function of your app class, add m_pmainwnd->centerwindow(); just before the calls to m_pmainwnd->showwindow(sw_show) and m_pmainwnd->updatewindow(); this will center your main window each time the program is run. if you want the window size maximized each time the program is run, change sw_show to sw_maximized in the showwindow() function. if you want to specify exactly how large your main program window should be, add the following code to your program's cmainframe class, in the precreatewindow() function before the return call:

cs.cy = 300; // main window height

cs.cx = 300; // main window width (substitute whatever values you need).

 

create the database recordset class:

right click on the classes header in the workspace window of developer studio (the area where your classes are displayed in a tree-control view). this is the top listing in bold font (in the sample program it's called "database classes"). choose "new class" and the new class dialog box appears. class type defaults to "mfc class" which is what we want. under class information, type in the name of your new class in the "name:" field. in the sample program i typed in: cdaorecordsetemployees . now click the down arrow of the "base class:" combobox, scroll down and select "cdaorecordset". check your spelling, then click the ok button to create your new class. a series of dialog boxes will follow, asking you to locate the database. click the " ... " box to the right of the dao datasource field, locate your database file (in the sample program this is the "db1.mdb" file), click the "open" button, accept the defaults of "dynaset", "detect dirty columns" and "bind all columns", and click the ok button. choose your database table (or tables) and click ok. your new recordset should now appear in the workspace window on your program's class tree. later, you will link this recordset with the dialog you create in order to display and edit the database data.

 

create the dialog-based display:

i won't go into too much detail here. you need to create a new dialog resource using the resource editor. design this dialog box to display the database information in whatever form you need. the sample program is very simplistic: the data are displayed in edit boxes with static text labels. you can certainly display the data using other methods (perhaps fill a listbox, combobox or tree-view).

 

when we created our program, we set up the base view class as a cformview class. this makes it easy to add dialog components to the main program's view window. in the sample program, i modified the formview the same way you edit your dialog: using the resource editor. i added an edit box for the user to type in the employee's ssn, and a button to perform the "find" function. using class wizard, i associated a member variable called m_ssn with the edit box. finally, i created a windows message member function in the view class based on the "find" button, called onbuttonfind(), that will be used to create the dialog object, link the dialog and database, and find records that match the user supplied ssn's (described in more detail below).

 

associating database data with dialog controls and variables:

linking the database data to their respective dialog box components can be accomplished in a couple of ways: you can read, buffer, and write the data yourself, using setfieldvalue() functions, or you can use the dialog data exchange mechanism (ddx) provided by mfc. i chose the "easy" approach and used ddx. however, you need to set up a couple of things after creating your dialog class to link it with your database file (just to clarify, we are "linking" our database file and our dialog box using the daorecordset class that we created earlier).

 

after you create your dialog box using the resource editor and have arranged all of its controls where you want them, leave your dialog box open in the resource editor.

 

select -- view | class wizard -- from the menu bar. class wizard recognizes the dialog as a new potential class and asks if you want to create a new class based on this dialog. select ok and you will now be in the new class window. give your new class a name -- in the sample program i chose cdialogdata . the base class should have defaulted to cdialog (which is what we want) and the dialog id should have defaulted to your new dialog's id (usually something like idd_dialog1, if you didn't specify an id name yourself in the dialog's "properties" box). click ok to create your new dialog class.

 

now open the class wizard again (if it closed, select -- view | class wizard -- again) and click on the "class info" tab on the far right side. in the "class name" list box at the top, scroll down and select the name of the new dialog class you just created (again, in the sample program this was named cdialogdata). at the bottom is another listbox named "foreign variable". choose this, scroll down, and select the name of your recordset class that was created earlier (remember, in the sample program this is called cdaorecordsetemployees). type in a variable name in the "foreign variable" box. i usually use the same variable name created by the appwizard: m_pset . (this is actually a pointer variable ; eventually it will point to the address of your recordset class, after that class has been created and initialized with data from the database file).

 

with class wizard still open, click on the "member variables" tab. you will see all of the control id's listed for your dialog box on the left side of the display window. now when you want to add member variables for these controls, you will be able to select any of the fields available in your recordset (i.e., in your database). these steps are necessary in order to link your dialog's variables with the recordset (database) fields and use ddx to handle transferring and saving the data from the database. when you have finished assigning your dialog control variables, click the ok button to close the class wizard and save your changes. later, you will need to assign the foreign variable pointer (m_pset) to the address of your database recordset.

 

opening the database file and recordset:

we need to open the database file and pass its address to our recordset class so that we can access its data using our dialog box. this is accomplished within the onopendocument() function of the doc class in the sample program. we will also need a pointer to the recordset object. this is created in the doc header file, so don't forget to include your recordset's header file in the document class's header file. in the sample program, look in the databasedoc.h file under // custom member functions and variables -- you'll see a protected pointer variable cdaorecordsetemployees * m_pset , and a public member function that can be used to retrieve it called getrecset() [trying to be strictly object-oriented here]. we will use this function later in the view class to obtain the address of the recordset and assign it to our dialog's foreign m_pset variable (see the linking the dialog and database: section). here's the onopendocument() function:

 

bool cdatabasedoc::onopendocument(lpctstr lpszpathname)

{

         if (!cdocument::onopendocument(lpszpathname))

         return false;

 

         // if user selected a file then try to open it as a database

         m_pdb = new cdaodatabase;

         assert(m_pdb != null);

         try

         {

                   m_pdb->open(lpszpathname);

         }

         catch (cdaoexception* e)

         {

                   delete m_pdb;

                   m_pdb = null;

 

                   tchar szcause[255];

                   cstring strformatted = _t("the data file could not be opened because of this error: \n");

                   e->geterrormessage(szcause, 255);

                   strformatted += szcause;

                   afxmessagebox(strformatted, mb_ok | mb_iconexclamation);

                   e->delete();

                   m_bfileopen = false;

                   return false;

         }

 

         // if database successfully opened then open employees recordset

         m_pset = new cdaorecordsetemployees(m_pdb);

         assert(m_pset != null);

         try

         {

                   m_pset->open();

         }

         catch (cdaoexception* e)

         {

                   delete m_pset;

                   m_pset = null;

 

                   tchar szcause[255];

                   cstring strformatted = _t("the data file could not be opened because of this error: \n");

                   e->geterrormessage(szcause, 255);

                   strformatted += szcause;

                   afxmessagebox(strformatted, mb_ok | mb_iconexclamation);

                   e->delete();

                   m_bfileopen = false;

                   return false;

         }

 

         // boolean variable to indicate database is open

         m_bfileopen = true;

         return true;

}

 

 

 

[note: the catch statements which display dao exceptions were modified from a sample database application included on the visual c++ cd]

 

the user first sees the common "file open" dialog to locate the correct database file (db1.mdb). if the user has selected a file, we create a new cdaodatabase object and assign our member pointer variable to it. we then try to open this object as a cdaodatabase file. if this is successful, we create a new cdaorecordsetemployees object and assign our member pointer variable to it. we then try to open our recordset, passing it the pointer to the database. if this is successful, we update the boolean "fileopen" variable (used to keep track of the open recordset in the view class, but could be done using the isopen() function as well) and allow the onopendocument() function to return. since we created our database and recordset objects using the "new" function, we need to add appropriate "delete" functions as well. take a look around the databasedoc.cpp file and note where the "delete" functions are used. (this is to prevent those pesky memory leaks!)

 

linking the dialog and database:

look at the onbuttonfind() function in the cdatabaseview.cpp file.

 

void cdatabaseview::onbuttonfind()

{

         cdatabasedoc* pdoc = getdocument();

         assert_valid(pdoc);

 

         // if the database file has been opened view the data

         if ( pdoc->isfileopen() )

         {

                   // record ssn user entered

                   updatedata(true);

 

                   // create the data dialog object

                   cdialogdata dlg;

                   assert(&dlg != null);

 

                   // set the ptr to the open database set

                   dlg.m_pset = pdoc->getrecset();

 

                   // create search string

                   dlg.m_pset->m_strfilter.format("[ssn] = %9.0f", m_ssn);

 

                   // requery the database

                   dlg.m_pset->requery();

 

                   // allow editing of database fields

                   dlg.m_pset->edit();

 

                   // if user edited data, update the database

                   if (dlg.domodal() == idok)

                   dlg.m_pset->update();

         }

         else

                   afxmessagebox("please open the database file first", mb_ok | mb_iconexclamation);

}

 

 

 

if the user has clicked the find record button, we first make sure that the database file has been opened, otherwise remind the user to do this with a messagebox. if the database file is open, we save the ssn the user entered, create the dialogdata dialog box and set its m_pset variable equal to the recordset's pointer using the following statement: dlg.m_pset = pdoc->getrecset(); since we create our dialog object in the view class, don't forget to include the dialog class's header file at the top of the view class source file (somethingview.cpp).

 

searching the database:

we then format our search string (a.k.a.: find the record that has the same ssn as that entered by the user), requery the database and display the dialog box, hopefully filled with all the correct data. a call to the edit() function is made so that if the user changes the data displayed in one of the dialog controls and clicks the ok button, the database data can be updated (using the update() function) with the new changes. (you'll get an error message if you try to use update() without calling edit() or new() first). to search through the database and find the employee record that matches the ssn entered by the user, we use the m_strfilter function and requery() the database file. m_strfilter , however, is just as it appears -- a string value. therefore, we have to convert our "double" ssn value into a "string" ssn value using the following function: dlg.m_pset->m_strfilter.format("[ssn] = %9.0f", m_ssn); this sets the value of the m_strfilter variable to a complete string value of "[ssn] = 123456789" when the user enters 123456789 as the employee's ssn.

 

when i created the test database (db1.mdb), i set up the ssn field as a number field of type "double" (i.e: double precision floating point). it could have just as easily been a text field or long integer. i wanted a number value so that i could designate it as the primary key (see the ms access help if you don't know about primary keys). anyway, when visual c++ interprets the *.mdb file during creation of the daorecordset class, it assigns type "double" to the ssn field. when i created the ssn edit box in the cformview window (the edit box the user enters the employee's ssn number into before pressing the "find record" button), i created a member variable (called m_ssn) using class wizard and assigned it to a type of "double" as well.

 

by the way, "search" is probably the wrong term to use here. what we are really doing is "filtering" the recordset/database to return only those files that match the m_strfilter variable. since we are basing this filter on a unique database element, the ssn, we know that the requery() function should only return one record. it is possible, therefore, to create a filter that can return more than one record. you would then need to set up your data viewing window so that it contains some method for displaying each matching record.

 

conclusion:

these are some of the major areas to address when creating your own database application. hopefully this has provided some basic steps to creating at least one type of database application. i hope you find the test program helpful as a template in designing your own custom interface program. it has room for infinite modifications (such as adding new records, deleting old ones, other types of searches/filters, etc.). although i used a simple database with only one table, multiple related tables can be used just as easily. i recommend creating a new recordset for each table in the database, however, then perform your searches/filters using unique keys or on each recordset separately. believe it or not, most of this information can be found using the online help and documentation (that's how i've found most of it, along with trial and error - i am not a professional programmer!). the problem is finding it and, unfortunately, that's not very straightforward. most of the vc++ review books seem to spend very little time on database programming. developer central has a tutorial on database programming that i found helpful. look at the sample database programs as well (under samples | mfc | database) on the visual c++ cd. they provide some good code to dissect and learn from. good luck!

 

 

 

download demo project - 162 kb

 

download source - 62 kb

 

date updated: december 14, 1998

 

 

 

 

在VC中使用ADO开发数据库应用程序

张立、张如

1.引入ADO库文件
使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文件,
以使编译器能正确编译。代码如下所示:
#import "c:\program files\common files\system\ado\msado15.dll"
no—namespaces rename("EOF" adoEOF")
这行语句声明在工程中使用ADO,但不使用ADO的名字空间,并且为了避免冲突,将EOF改
名为adoEOF。

2.初始化OLE/COM库环境
必须注意的是,ADO库是一组COM动态库,这意味应用程序在调用ADO前,必须初始
化OLE/COM库环境。在MFC应用程序里,一个比较好的方法是在应用程序主类的
InitInstance成员函数里初始化OLE/COM库环境。

  //初始化OLE/COM库环境

  BOOL CADOApp::InitInstance()
  { 
if(!AfxOleInit())

AfxMessageBox("OLE初始化出错!");
return FALSE; 
}
......
}

函数AfxOleInit在每次应用程序启动时初始化OLE/COM库环境。

3.ADO接口简介
ADO库包含三个基本接口:
__ConnectionPtr接口、
__CommandPtr接口和、
__RecordsetPtr接口,

__ConnectionPtr接口返回一个记录集或一个空指针。通常使用它来创建一个数据连接或
执行一条不返回任何结果的SQL语句,如一个存储过程。用__ConnectionPtr接口返回一
个记录集不是一个好的使用方法。通常同CDatabase一样,使用它创建一个数据连接,然
后使用其它对象执行数据输入输出操作。

__CommandPtr接口返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储
过程和SQL语句。在使用__CommandPtr接口时,可以利用全局__ConnectionPtr接口,也
可以在__CommandPtr接口里直接使用连接串。如果只执行一次或几次数据访问操作,后
者是比较好的选择。但如果要频繁访问数据库,并要返回很多记录集,那么,应该使用
全局__ConnectionPtr接口创建一个数据连接,然后使用__CommandPtr接口执行存储过程
和SQL语句。

__RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多
的控制功能,如记录锁定,游标控制等。同__CommandPtr接口一样,它不一定要使用一
个已经创建的数据连接,可以用一个连接串代替连接指针赋给__RecordsetPtr的
connection成员变量,让它自己创建数据连接。如果要使用多个记录集,最好的方法是
同Command对象一样使用已经创建了数据连接的全局—ConnectionPtr接口,然后使用
__RecordsetPtr执行存储过程和SQL语句。

4.使用__ConnectionPtr接口

__ConnectionPtr是一个连接接口,它类似于CDatabase和CDaoDatabase。首先创建一个
__ConnectionPtr接口实例,接着指向并打开一个ODBC数据源或OLE DB数据提供者
(Provider)。以下代码分别创建一个基于DSN和非DSN的数据连接。

  //使用__ConnectionPtr(基于DSN)

  __ConnectionPtr MyDb;

  MyDb.CreateInstance(__uuidof(Connection));

  MyDb-〉Open("DSN=samp;UID=admin;PWD=admin","","",-1);

  //使用—ConnectionPtr (基于非DSN)

  __ConnectionPtr MyDb;

  MyDb.CreateInstance(__uuidof(Connection));

  MyDb-〉Open("Provider=SQLOLEDB;SERVER=server;DATABASE=samp;UID=admin;

  PWD=admin","","",-1);


5.使用__RecordsetPtr接口
__RecordsetPtr接口的使用方法和CDaoDatabase类似,通过以下代码的比较,你会发现
使用—RecordsetPtr接口非常简单(以下代码使用上面已经创建的数据连接):

  //使用CDaoDatabase执行SQL语句

  CDaoRecordset MySet = new CDaoRecordset(MyDb);

  MySet-〉Open(AFX__DAO__USE__DEFAULT__TYPE,"SELECT  FROM t__samp");

  Now using ADO:

  //使用__RecordsetPtr执行SQL语句

  __RecordsetPtr MySet;

  MySet.CreateInstance(__uuidof(Recordset));

  MySet-〉Open("SELECT  FROM some__table",

  MyDb.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText);

  现在我们已经有了一个数据连接和一个记录集,接下来就可以使用数据了。从以下
代码可以看到,使用ADO的__RecordsetPtr接口,就不需要像DAO那样频繁地使用大而复
杂的数据结构VARIANT,并强制转换各种数据类型了,这也是ADO的优点之一。假定程序
有一个名称为m__List的ListBox控件,下面代码我们用__RecordsetPtr接口获取记录集
数据并填充这个ListBox控件:

  //使用ADO访问数据

  __variant__t Holder

  try{while(!MySet-〉adoEOF)

  { Holder = MySet-〉GetCollect("FIELD__1");

  if(Holder.vt!=VT__NULL)

  m__List.AddString((char)__bstr__t(Holder));

  MySet-〉MoveNext();} }

  catch(__com__error  e)

  { CString Error = e-〉ErrorMessage();

   AfxMessageBox(e-〉ErrorMessage());

  } catch(...)

  { MessageBox("ADO发生错误!");}

  必须始终在代码中用try和catch来捕获ADO错误,否则ADO错误会使你的应用程序崩
溃。当ADO发生运行错误时(如数据库不存在),OLE DB数据提供者将自动创建一个
__com__error对象,并将有关错误信息填充到这个对象的成员变量。

6.使用__CommandPtr接口
__CommandPtr接口返回一个Recordset对象,并且提供了更多的记录集控制功能,以下代
码示例使用__CommandPtr接口的方法:

  //使用__CommandPtr接口获取数据

  __CommandPtr pCommand;

  __RecordsetPtr MySet;

  pCommand.CreateInstance(__uuidof(Command));

  pCommand-〉ActiveConnection=MyDb;

  pCommand-〉CommandText="select  from some—table";

  pCommand-〉CommandType=adCmdText;

  pCommand-〉Parameters-〉Refresh();

  MySet=pCommand-〉Execute(NULL,NULL,adCmdUnknown);

  __variant__t TheValue = MySet-〉GetCollect("FIELD__1");

  CString sValue=(char)__bstr__t(TheValue);

7.关于数据类型转换
  由于COM对象是跨平台的,它使用了一种通用的方法来处理各种类型的数据,因此
CString 类和COM对象是不兼容的,我们需要一组API来转换COM对象和C++类型的数
据。__vatiant__t和__bstr__t就是这样两种对象。它们提供了通用的方法转换COM对象
和C++类型的数据。

 

 

 

 

 

 

用ISAPI和DAO实现Access数据库Web通用查询

 

一、ISAPI简介:

 

  ISAPI(Internet Server Application Prgramming Interface)是微软公司提供的,和其IIS紧密结合的API。用它可以编制Internet/Intranet的数据库、网络管理和BackOffice等应用。

 

二、DAO简介:

 

数据访问对象(DAO即Data Access Objects),原来是微软为Visual Basic和Access Basic设计的可编程接口。后来有了OLE Automation(自动控制)技术,它就可以用于C++编程了。

 

程序员可以用DAO的三种类型编制C++代码:

 

DAO OLE Automation接口

dbDAO类

MFC DAO类

其中dbDAO类和Visual Basic的DAO类非常相似,易于使用,笔者就是用它来进行数据库编程。

 

三、编程背景:

 

现在有不少中小企业或部门的数据库都基于Access,且其一般用Client/Server模式组建MIS。在当今流行Internet/Intranet且其成为今后的主流的形势下,有必要把原先的数据库搬上Internet/Intranet。

 

不少人正使用ASP、IDC、VB Script、Java Script、Perl甚至Shell Script来编制Web应用,虽然它们比ISAPI编制起来较容易,但其安全性较差,而且不如用C、C++等灵活强大,所以,作为一名较专业的程序员,选择用C++等高级语言来编制Web应用是更明智的选择。

 

Microsoft公司的系统和开发软件正处于流行的时候,基于上述因素,笔者用Visual C++(4.2以上)提供的ISAPI和DAO开发工具,设计了Access数据库的通用Internet/Intranet查询程序,可运行于IIS 2.0、Peer Web Server和Personal Web Server等Web服务器。

 

四、程序使用方法和功能介绍:

 

  程序编译连接后,生成search.dll,置于Web服务器的可执行目录中(如/cgi-bin)。  WEB主页制作人员可以把以下HTML片断插入页面:

 

<form action="/cgi-bin/search.dll" method="GET">

 

<input type="hidden" name="DATABASENAME" value="Northwind.mdb">

 

<input type="hidden" name="TABLENAME" value="产品">

 

<input type="hidden" name="FIELDNAME" value="产品名称">

 

<p><input type="text" name="FIELDVALUE" size="30"></p>

 

<p><input type="submit" value="按此查询"></p>

 

</form>

 

以上三个"hidden"类型的输入数据由主页制作人员输入,在浏览器中不显示出来,以求界面美观,FIELDVALUE则由客户由浏览器填入数据。四个数据域分别解释如下:

 

DATABASENAMEAccess数据库名,缺省在%system%\system32目录,如输入全路径名,则用C语言格式,如:c:\\data\\mydata.mdb

TABLENAME表名或查询名,可以自定义一个符合输出的查询

FIELDNAME要以之为查询条件的字段名,为文本型

FIELDVALUE字段值,由客户输入

客户在浏览器中输入数据后,按下查询按钮,则由服务器输出以下查询结果:

 

select * from TABLENAME where FIELDNAME like "*FIELDVALUE*"

 

因为*为Access查询语句的通配符,所以本程序能实现基于字段FIELDNAME的模糊查询。

 

五、程序流程和主要函数说明:

 

(一)在头文件MyTable.h中:

 

1、定义了4个宏变量分别和FORM中的四个输入数据域对应,用于解析参数以取得值:

 

宏变量名

 FORM中对应数据域

 类中的属性变量

 解释

 

SZTXTDATABASENAME DATABASENAME m_szDatabaseName 数据库名

 

SZTXTTABLENAME TABLENAME m_szTableName 表名或查询名

 

SZTXTFIELDNAME FIELDNAME m_szFieldName 搜索字段名

 

SZTXTFIELDVALUE FIELDVALUE m_szFieldValue 字段值

 

 

表1

 

2、定义了CHttpServer类的派生类CMyTableExtension:

 

class CMyTableExtension: public CHttpServer

 

{

 

public:

 

CString m_stReadError;

 

LPSTR m_szDatabaseName;

 

LPSTR m_szTableName;

 

LPSTR m_szFieldName;

 

LPSTR m-szFieldValue;

 

void cdbSetupPage(CString& stPage);

 

CHAR atoiHex(CHAR ch);

 

//ParseInput函数解析客户输入数据

 

BOOL ParsetInput(LPSTR szInput,LPSTR szItem,LPSTR *pszData);

 

//strVARIANT函数把变体变量值转换成一般数据类型值

 

CString strVARIANT(const COleVariant& var);

 

CMyTableExtension();

 

~CMyTableExtension();

 

//{{AFX_VIRTUAL(CMyTableExtension)

 

public:

 

virtual BOOL GetExtensionVersion(HSE_VERSION_INFO* pVer);

 

virtual DWORD HttpExtensionProc(EXTENSION_CONTROL_BLOCK* PECB);

 

//}}AFX_VIRTUAL

 

void Default(CHttpServerContext* pCtxt);

 

DECLARE_PARSE_MAP()

 

//{{AFX_MSG(CMyTableExtension)

 

//}}AFX_MSG

 

};

 

  其中定义的数据库查询函数(cdbSetupPage)和重构的Http服务器扩展处理函数HttpExtensionProc为其中核心。以下重点讲述MyTable.cpp文件中的这两个函数。

 

(二)函数cdbSetupPage,根据四个属性变量(见表1)输出数据查询结果,存放于stPage并返回:

 

  1、打开数据库OpenDatabase:

 

CdbDBEngine dbeng;

 

CdbDatabase db;

 

db=dbeng.OpenDatabase(m_szDatabaseName);

 

2、创建查询结果记录集:

 

CdbRecordset rstMyTable;

 

CString strQuery;

 

strQuery.Format("select * from %s where %s like \"*%s*\"",

 

m_szTableName,

 

m_szFieldName,

 

m_szFieldValue);

 

rstMyTable=db.OpenRecordset(strQuery,dbOpenSnapshot);

 

以上strQuery为查询语句,dbOpenSanpshot表示打开一快照类型的记录集。

 

3、用记录集的Fields集合(Collection)的GetCount()和GetName()分别取得字段数和各字段值:

 

long i;

 

CString stPage,stTemp;

 

for (i=0;i<rstMyTable.Fields.GetCount();i++) {

 

stTemp.Format("<TH>%s</TH>\n",

 

rstMyTable.Fields[i].GetName());

 

stPage += stTemp; }

 

4、输出记录集中所有记录的值:

 

for (i=0;i<rstMyTable.Fields.GetCount();i++) {

 

stTemp.Format("<TD>%s</TD>\n",

 

strVARIANT(rstMyTable.Fields[i].GetValue));

 

stPage += stTemp; }

 

(三)重构基类CHttpServer的成员函数HttpExtensionProc:

 

1、从EXTENSION_CONTROL_BLOCK* pECB取得查询字串,再由函数ParseInput解析此字符串,得出客户端的各数据域的输入值:数据库名,表名,字段名,字段值;

 

2、由函数cdbSetupPage输出数据库查询结果字符串stPage;

 

3、用EXTENSION_CONTROL_BLOCK对象的ServerSupportFunction方法向客户浏览器输出查询结果字符串stPage。

 

六、两个主要函数HttpExtensionProc和cdbSetupPage的源程序并解释:

 

1、HttpExtensionProc函数:

 

DWORD CMyTableExtension::HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)

 

{

 

DWORD cbQuery;

 

LPSTR szData;

 

LPSTR lpszTemp = NULL;

 

CString stPage;

 

//Get the data

 

if (!stricmp(pECB->lpszMethod, SZGET))

 

szData = pECB->lpszQueryString;

 

else

 

{

 

if(pECB->cbTotalBytes)

 

{

 

lpszTemp = new CHAR[pECB->cbTotalBytes];

 

if (!lpszTemp)

 

return HSE_STATUS_ERROR;

 

strcpy(lpszTemp, (const CHAR *) pECB->lpbData);

 

if((cbQuery = pECB->cbTotalBytes - pECB->cbAvailable) > 0)

 

pECB->ReadClient(pECB->ConnID, (LPVOID) (lpszTemp + pECB->cbAvailable), &cbQuery);

 

szData = lpszTemp;

 

}

 

}

 

m_stReadError = "";

 

if (ParseInput(szData,SZTXTDATABASENAME,&m_szDatabaseName) == FALSE)

 

m_stReadError += "[数据库名称]";

 

if (ParseInput(szData,SZTXTTABLENAME,&m_szTableName) == FALSE)

 

m_stReadError += "[表名称]";

 

if (ParseInput(szData,SZTXTFIELDNAME,&m_szFieldName) == FALSE)

 

m_stReadError += "[字段名称]";

 

if (ParseInput(szData,SZTXTFIELDVALUE,&m_szFieldValue) == FALSE)

 

m_stReadError += "[字段值]";

 

cdbSetupPage(stPage);

 

DWORD dwLen=stPage.GetLength();

 

CHAR *szPage=stPage.GetBuffer(dwLen);

 

if (!pECB->ServerSupportFunction( pECB->ConnID,

 

HSE_REQ_SEND_RESPONSE_HEADER,

 

"200 OK", &dwLen, (LPDWORD) szPage))

 

{

 

stPage.ReleaseBuffer();

 

return HSE_STATUS_ERROR;

 

}

 

stPage.ReleaseBuffer();

 

if (lpszTemp)

 

delete[] lpszTemp;

 

return HSE_STATUS_SUCCESS;

 

}

 

2、cdbSetupPage函数:

 

void CMyTableExtension::cdbSetupPage(CString& stPage)

 

{

 

CdbDBEngine dbeng;

 

CdbDatabase db;

 

CdbRecordset rstMyTable;

 

long i;

 

CString stTemp;

 

CString strQuery;

 

strQuery.Format(_T("select * from %s where %s like \"*%s*\""),

 

m_szTableName,m_szFieldName,m_szFieldValue);

 

stPage = "Content-Type: text/html\r\n\r\n<html>\n<head>";

 

stPage += "<title>查阅结果</title>\n";

 

if (m_stReadError.GetLength() != 0)

 

{

 

stTemp.Format("读取错误: %s", m_stReadError);

 

stPage += stTemp;

 

}

 

else if (m_stWriteError.GetLength() != 0)

 

stPage += m_stWriteError;

 

stPage += "<p><p><h1 align=center>查阅如下</h1>\n";

 

stPage += "<div align=center><center>";

 

stPage += "<TABLE border=4 cellspacing=0>\n";

 

try {

 

db=dbeng.OpenDatabase(m_szDatabaseName);

 

rstMyTable=db.OpenRecordset(strQuery,dbOpenSnapshot);

 

stPage += _T("<TR>");

 

for (i=0;i<rstMyTable.Fields.GetCount();i++)

 

{

 

stTemp.Format(_T("<TH ALIGN=CENTER>%s</TH>\n"),

 

rstMyTable.Fields[i].GetName());

 

stPage += stTemp;

 

}

 

stPage += _T("</TR>");

 

rstMyTable.MoveFirst();

 

do {

 

stPage += "<TR>\n";

 

for (i=0;i<rstMyTable.Fields.GetCount();i++)

 

{

 

if (i==0)

 

stTemp.Format("<TD ALIGN=LEFT>%s</TD>\n",

 

strVARIANT(rstMyTable.Fields[i].GetValue()));

 

else

 

stTemp.Format("<TD ALIGN=RIGHT>%s</TD>\n",

 

strVARIANT(rstMyTable.Fields[i].GetValue()));

 

stPage += stTemp;

 

}

 

stPage += "</TR>\n";

 

rstMyTable.MoveNext();

 

}

 

while (!rstMyTable.GetEOF());

 

rstMyTable.Close();

 

db.Close();

 

stPage += "</TABLE>\n";

 

}

 

catch (CdbException e) {

 

CdbLastOLEError err;

 

stTemp.Format("Unexpected Error:%s\n",

 

(LPCTSTR) err.GetDescription());

 

stPage += "</TABLE>\n";

 

stPage += stTemp;

 

stPage += "</center></div>";

 

}

 

stPage += "<p align=center>";

 

stPage += <a href=../default.htm target=_top>[返回]</a></p>\n";

 

stPage += "</body>";

 

stPage += _T("</html>");

 

}

 

七、后记:

 

由于用Visual C++(4.2以上)编制程序时,有很多环境设置,所以笔者把整个项目(包括源程序和项目设置等)和文档打了个包,需要的读者可以E-mail给我,地址:zhugeming@263.net。

 

作者: 诸葛明 侯志华

 

通信地址:苏州市三香路180号统计局(215004)

VC++5.0下CGridCtrl类的建立与应用

 

 一、前言

 

      在数据库的操作系统中,经常要遇到对数据库的内容进行显示、

修改、添加和删除等功能,而一般情况下数据库的容量都是很大的,为

此我们通常选用以电子表格的形式来显示数据库的内容,并在其上完成

对数据库的修改、添加和删除,使对数据库的操作既直观又方便;如用

VB5.0下DB Grid Control来实现以上功能是较为方便的,但由于VB的通

信速度和数据的处理速度较慢,特别对于要处理和显示的数据量较大时,

其速度较慢表现得尤为明显,为此我们选用在VC++5.0下用CGridCtrl[1]

类来实现。

 

  二、CGridCtrl类的建立

 

CGridCtrl[1]类派生于CWnd类,该类主要包含以下八个方面的函数,其

函数形式如下:

 

class CGridCtrl : public CWnd

 

{

 

//  1. CGridCtrl类的构造函数

 

CGridCtrl(int nRows = 0, int nCols = 0, int nFixedRows = 0, int nFixedCols = 0);

 

    BOOL Create(const RECT& rect, CWnd* parent, UINT nID,

 

      DWORD dwStyle = WS_CHILD | WS_BORDER | WS_TABSTOP | WS_VISIBLE);

 

  //  2. 表格行、列数方面的函数

 

BOOL SetRowCount(int nRows);                              /设置表的行数

 

BOOL SetColumnCount(int nCols);                            //设置表的列数

 

BOOL SetFixedRowCount(int nFixedRows = 1);                //设置表的固定行数

 

BOOL SetFixedColumnCount(int nFixedCols = 1);              //设置表的固定列数

 

int  GetFixedRowCount() const;                            //  取表的固定行数

 

int  GetFixedColumnCount() const;                          //取表的固定列数

 

……

 

//  3. 表格尺寸大小方面的函数

 

BOOL SetRowHeight(int row, int height);                 //设置表格单元的高度

 

BOOL SetColumnWidth(int col, int height);               //设置表格单元的宽度

 

int GetRowHeight(int nRow) const;                      //获取表格单元的高度

 

int GetColumnWidth(int nCol) const;                    //获取表格单元的宽度

 

void AutoSize();                         //对表格单元的高度与宽度进行自动设置

 

……

 

//  4. 表格显示与特征方面的函数

 

void SetImageList(CImageList* pList);                      //设置列表图标

 

void SetEditable(BOOL bEditable = TRUE);                  //设置表格的编辑状态

 

BOOL SetColumnType(int nCol, int nType);                  //设置表格的列状态

 

……

 

// 5. 颜色方面的函数

 

void SetTextColor(COLORREF clr);                    //设置输入表格的文本颜色

 

void SetTextBkColor(COLORREF clr);              //设置可供输入文本的表格颜色

 

void SetFixedTextColor(COLORREF clr);           //设置输入固定表格的文本颜色

 

void SetFixedBkColor(COLORREF clr);             //设置固定表格颜色

 

……

 

// 6. 表格信息函数

 

BOOL SetItem(const GV_ITEM* pItem);              // 向表格中输入信息

 

BOOL SetItemText(int nRow, int nCol, LPCTSTR str);   //向某一单元表格中输入文本

 

BOOL SetItemImage(int nRow, int nCol, int iImage);    //在某一单元表格中设置图标

 

……

 

// 7. 编辑方面的函数

 

virtual void OnEditCell(int nRow, int nCol, UINT nChar)     //开始对表格进行编辑

 

virtual void OnEndEditCell(int nRow, int nCol, CString str)   //结束对表格编辑

 

    ……

 

// 8. 表格打印函数

 

void Print();                                       //打印表格

 

……

 

}

 

CGridCtrl[1]类的构造函数形式如下:

 

CGridCtrl::CGridCtrl(int nRows, int nCols, int nFixedRows, int nFixedCols)

 

{

 

    m_crWindowText       = ::GetSysColor(COLOR_WINDOWTEXT);

 

    m_crWindowColour     = ::GetSysColor(COLOR_WINDOW);

 

    m_cr3DFace           = ::GetSysColor(COLOR_3DFACE);

 

    m_nRows              = 0;         //初始电子表格的行数

 

    m_nCols               = 0;         //初始电子表格的列数

 

    m_nFixedRows          = 0;        //初始化固定表格的行数

 

    m_nFixedCols           = 0;       //初始化固定表格的列数

 

    m_bEditable            = TRUE;    //初始化表格为可编辑状态

 

     ……

 

       //初始化设置表格的行列数

 

    SetRowCount(nRows);

 

    SetColumnCount(nCols);

 

    SetFixedRowCount(nFixedRows);

 

    SetFixedColumnCount(nFixedCols);

 

     // 初始化表格的背景颜色及输入表格的文本颜色

 

    SetTextColor(m_crWindowText);

 

    SetTextBkColor(m_crWindowColour);

 

    SetFixedTextColor(m_crWindowText);

 

    SetFixedBkColor(m_cr3DFace);

 

    ……                                                                                                                                                                                                              }

 

对表格中所输入信息属性的描述,是通过定义一结构体函数来实现,该结构体函数形式如下:

 

typedef struct _GV_ITEM {

 

        int     row,col;            // 输入信息的位置

 

        UINT    mask;            //输入信息的灰度值

 

        UINT    state;            // 表格单元的状态

 

        UINT    nFormat;         // 信息的输入形式

 

        CString szText;          // 输入表格单元的文本

 

     } GV_ITEM;

 

 

 

将CGridCtrl类与以下类结合起来,即可构造成在其上可进行编辑和修改的电子

表格,这些类分别为:

 

1.用于单元表格范围的两个类:CCellRange[2]类和CCellID[3]类;

 

2.单元表格状态属性的类:CGridCell[4]类;

 

3.对表格进行编辑的两个类:CInplaceEdit[5]类和CInplaceList[6]类;

 

 

 

三、CGridCtrl类的应用

 

1.CGridCtrl类实现对数据库内容的显示

 

 

     在我们建立的一单文档界面应用程序(MyGridCtrlTest)之上,

加上CGridCtrl[1]类,再结合CCellRange[2]类、CCellID[3]类、CGridCell[4]

类、CInplaceEdit[5]类、CInplaceList[6]类以及用于数据库读写的文件,即

可在CMyGridCtrlTestView类的OnDraw()函数将Main.mdb数据库中的工资表以

电子表格的形式显示出来,如图1,

 

                图1 在单文档的界面上以电子表格形式对一工资表的显示

 

 

 

   在MyGridCtrlTestView.h中,我们将CGridCtrlEx类(该类为CGriCtrl类的

派生)嵌入到CMyGridCtrlTestView类中,其形式如下:

 

  class CMyGridCtrlTestView : public CView

 

{

 

       class CGridEx : public CGridCtrl

 

       {

 

              // Override this function to fill InPlaceListBoxes

 

              void FillListItems(int nCol, LPARAM cltList);

 

                     ……

 

       };

 

public:

 

       CDemo_ui_devstudioDoc* GetDocument();

 

       CGridEx m_Grid;

 

       CSize m_OldSize;

 

       int *Iarray;

 

       CString  *Csarray;

 

       CImageList m_ImageList;

 

     ……   

 

       public:

 

       virtual void OnDraw(CDC* pDC);  // overridden to draw this view

 

       ……

 

protected:

 

              afx_msg void OnGridCtrlDelete();         //对表格进行删除的函数

 

              afx_msg void OnGridCtrlPrint();          //对表格进行打印的函数

 

       ……

 

       };

 

 

 

   在CMyGridCtrlTestView类的OnDraw()函数中,我们实现对工资表的电子表格显示,该函数的形式如下:

 

  void CMyGridCtrlTestView ::OnDraw(CDC* pDC)

 

{

 

       CMyGridCtrlTestDoc* pDoc = GetDocument();

 

       ASSERT_VALID(pDoc);

 

       // TODO: add draw code for native data here

 

       int s,r;

 

   // 建立一显示CGridCtrl类的矩形

 

       RECT rect;

 

       GetClientRect(&rect);

 

       m_OldSize = CSize(rect.right-rect.left, rect.bottom-rect.top);

 

       m_Grid.Create(rect, (CWnd*)this, 111);

 

       // 打开工资表并将工资表的内容读入数组中

 

     ……

 

      s:为工资表的行数;

 

      r:为工资表的列数;

 

     申请两个数组空间,用于存放工资表的内容,分别为:

 

          Csarray=new CString[(s+2)*1];        //用于存放姓名

 

             Iarray=new int[(s+2)*9];              //用于存放工资表中的其他项目 

 

     所申请两个数组空间比实际所需的空间要多两行,对于多出的最末两行中,

如为字符串型变量,将以“0”代替,如为整型变量将以0代替,以供操作员在

最末的两行实现对工资表的添加,如图1所示,

 

//创造列表图标

 

       m_ImageList.Create(MAKEINTRESOURCE(IDB_IMAGES), 16, 1, RGB(255,255,255));

 

       m_Grid.SetImageList(&m_ImageList);

 

//设置表格的属性特征

 

       m_Grid.SetEditable(TRUE);             //设置表格为可编辑状态

 

       m_Grid.SetTextBkColor(RGB(0xFF, 0xFF, 0xE0));     //设置表格的背景颜色

 

 //设置表格的行列数

 

       try {

 

              m_Grid.SetRowCount(s+3);

 

              m_Grid.SetColumnCount(r+1);

 

              m_Grid.SetFixedRowCount(1);

 

              m_Grid.SetFixedColumnCount(1);

 

       }

 

       catch (CMemoryException* e)

 

       {

 

              e->ReportError();

 

              e->Delete();

 

              }

 

// 将工资表的内容填入表格中

 

              for (int row = 0; row < (s+3); row++)

 

              for (int col = 0; col < (r+1); col++)

 

              {

 

                     GV_ITEM Item;            //输入信息结构体变量的声明

 

                     Item.mask = GVIF_TEXT|GVIF_FORMAT;        //所输入信息的灰度

 

                     Item.row = row;                               //输入信息的行位置

 

                     Item.col = col;                                 //输入信息的列位置

 

                        if (row < 1) {

 

                            Item.nFormat = DT_LEFT|DT_WORDBREAK;   //输入信息的格式  

 

                      if (col == 0 ) {

 

                 m_Grid.SetColumnType(col, GVET_NOEDIT); //设置表格列为可编辑状态

 

                        Item.szText.Format(_T("不能编辑"));        //设置表格的文本形式

 

                            }

 

                            else if (col == 1 ) {

 

                                   m_Grid.SetColumnType(col, GVET_EDITBOX);

 

                                   Item.szText.Format(_T("姓名"));

 

                            }                 

 

                            else if (col == 2 ) {

 

                                   m_Grid.SetColumnType(col, GVET_EDITBOX);

 

                                   Item.szText.Format(_T("固定工资"));

 

                            }

 

                            else if (col == 3 ) {

 

                  m_Grid.SetColumnType(col, GVET_LISTBOX); //设置表格列为下拉可选状态

 

                                   Item.szText.Format(_T("住房补贴"));

 

                            }                            

 

 //以下均为对表格第一行标题的设定, 情况与以上相类似,在此将col==4,5,6,7,8,9时略去。

 

                ……                          

 

                            else if(col==10){

 

                                   m_Grid.SetColumnType(col, GVET_EDITBOX);

 

                                   Item.szText.Format(_T("公积金"));

 

                            }

 

                            else

 

                            {

 

                            }

 

                     } else if (col < 1) {

 

              Item.nFormat = DT_RIGHT|DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS;

 

                            Item.szText.Format(_T("行 %d"),row);

 

                     } else {

 

          Item.nFormat = DT_CENTER|DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS;    if(col==1)

 

                                   Item.szText.Format(_T("%s"),Csarray[(row-1)]);

 

                            else if(col==2)

 

                            {                               

 

                                   Item.szText.Format(_T("%d"),*(Iarray+(row-1)*9));

 

                            }                               

 

                            else if(col==3)

 

                            {                               

 

                                   Item.szText.Format(_T("%d"),*(Iarray+1+(row-1)*9));

 

                            }                                                           

 

              //以下为col==4,5,6,7,8,9时的填写,情况与以上相类似,在此省略。

 

                            else if(col==10)

 

                            {                 

 

                                   Item.szText.Format(_T("%d"),*(Iarray+8+(row-1)*9));

 

                            }

 

                else

 

                {

 

                 }         

 

                     }

 

                            m_Grid.SetItem(&Item);                    //将输入信息填入表格

 

       if (col == (m_Grid.GetFixedColumnCount()-1) && row >= m_Grid.GetFixedRowCount())

 

                            m_Grid.SetItemImage(row, col, row%2);   //将图标填入表格的第一列中

 

                            }

 

          m_Grid.AutoSize();                //对单元表格的高度与宽度进行自动设置

 

       m_Grid.SetRowHeight(0, 3*m_Grid.GetRowHeight(0)/2);   //设置第一行表格的高度

 

  delete []Csarray;

 

  delete []Iarray;

 

}

 

 

 

2.表格的编辑、添加与删除

 

我们可以对表格的内容直接进行编辑,对于表格中的某一项值是固定的几个取值

时,我们以一下拉的方式让操作员选择某一值进行编辑,这样更有利于操作员的

操作,如图1的第四列,该功能的实现是通过CGridCtrlEx类中的

 FillListItems()函数来实现,该函数的形式如下:

 

void CMyGridCtrlTestView ::CGridEx::FillListItems(int nCol, LPARAM cltList)

 

{

 

     CListBox*      pList = (CListBox*)cltList;

 

     if (nCol == 3)

 

     {

 

            pList->AddString("0");

 

            pList->AddString("20");

 

            pList->AddString("30");

 

            pList->AddString("40");

 

            pList->AddString("50");

 

            pList->AddString("60");

 

            }

 

     }

 

我们可以在表格的最末两行对工资表的内容进行添加,如图1所示,对表格中

的内容进行修改和添加的结果都将自动地保存在工资表中,这是通过调用

CGridCtrl[1]类的OnEndEditCell()函数来实现,该函数的形式如下:

 

void CGridCtrl::OnEndEditCell(int nRow, int nCol, CString str)

 

{

 

    SetItemText(nRow, nCol, str);

 

//打开工资表,读取工资表的总行数为 T

 

……

 

     if(nRow==(T+1)) //所操作表格的行位置数大于工资表的总行数,是对

表进行添加操作

 

    {

 

   //对工资表进行添加操作

 

}

 

   else if(nRow<=T) //所操作表格的行位置数不大于工资表的总行数,是对

表进行编辑操作

 

{

 

//对工资表进行编辑操作

 

}

 

else

 

{

 

MessageBox(“nRow error!”);

 

}

 

 }

 

我们在编辑菜单(Edit)下设置一对表格进行删除的子菜单 “删除(工资表)”,

单击该子菜单,即可弹出一对话框以供操作员输入要删除人的姓名,在确认无

误以后,即可在工资表中将该人的工资信息自动删除,最后将删除后的工资表

以电子表格的形式重新显示出来,该功能的实现是在CMyGridCtrlTestView类

中OnGridCtrlDelete()函数来完成的。

 

3. 表格的打印

 

     在文件菜单(File)下设置一对表格进行打印的子菜单 “打印(工资表)

”,单击该子菜单,将调用CMyGridCtrlTestView类中OnPrintGridCell()函数,

实现对表格的打印,该函数的形式如下:

 

void CMyGridCtrlTestView :: OnGridCtrlPrint ()

 

{

 

       // TODO: Add your command handler code here

 

       m_Grid.Print();

 

}

 

     

 

四、小结

 

     本篇是对VC++5.0下CGridCtrl[1]类建立与应用的一个初步探讨,

CGridCtrl[1]类本身是很复杂,其所能实现的功能也是很强大的,除了

我们给大家介绍CGridCtrl[1]类几个基本而又常用的函数之外,

CGridCtrl[1]类还有很多用于其他方面的功能函数,在此我们不可

能给大家一一介绍,我们利用CGridCtrl[1]类这些基本而又常用的函数,

实现了对数据库内容的显示、修改、添加、删除及打印等功能,基本上满足

了数据库操作人员的需求。

 

 

 

  参考文献

 

1、http://users.aol.com/chinajoe, Written by Chris Maunder.

 

2、http://users.aol.com/chinajoe,written by Joe Willcoxson.

 

3、http://users.aol.com/chinajoe,written by Joe Willcoxson.

 

4、http://users.aol.com/chinajoe, Written by Chris Maunder.

 

5、http://www.codeguru.com/listview/edit_subitems.shtml, Written by Chris Maunder.

 

6、http://www.codeguru.com/listview/edit_subitems.shtml, Written by Motty Cohen.

 

 

用ADO进行数据库编程

 

 

使用ADO

6.1 概述

ADO是ActiveX数据对象(ActiveX Data Object),这是Microsoft开发数据库

应用程序的面向对象的新接口。ADO访问数据库是通过访问OLE DB数据提供程

序来进行的,提供了一种对OLE DB数据提供程序的简单高层访问接口。

ADO技术简化了OLE DB的操作,OLE DB的程序中使用了大量的COM接口,而ADO封

装了这些接口。所以,ADO是一种高层的访问技术。

ADO技术基于通用对象模型(COM),它提供了多种语言的访问技术,同时,

由于ADO提供了访问自动化接口,所以,ADO可以用描述的脚本语言来访问

VBScript,VCScript等。

 

6.2 在VC中使用ADO

可以使用VC6提供的ActiveX控件开发应用程序,还可以用ADO对象开发应用程序。

使用ADO对象开发应用程序可以使程序开发者更容易地控制对数据库的访问,从

而产生符合用户需求的数据库访问程序。

使用ADO对象开发应用程序也类似其它技术,需产生与数据源的连接,创建记

录等步骤,但与其它访问技术不同的是,ADO技术对对象之间的层次和顺序关系

要求不是太严格。在程序开发过程中,不必选建立连接,然后才能产生记录

对象等。可以在使用记录的地方直接使用记录对象,在创建记录对象的同时,

程序自动建立了与数据源的连接。这种模型有力的简化了程序设计,增强了程

序的灵活性。下面讲述使用ADO对象进行程序设计的方法。

6.21 引入ADO库文件

使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文

件,以使编译器能正确编译。代码如下所示:

#define INITGUID

#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","EndOfFile")

#include "icrsint.h"

这行语句声明在工程中使用ADO,但不使用ADO的名字空间,并且为了避免

冲突,将EOF改名为EndOfFile。

6.22 初始化ADO环境

在使用ADO对象之前必须先初始化COM环境。初始化COM环境可以用以下代码完成:

::CoInitialize(NULL);

在初始化COM环境后,就可以使用ADO对象了,如果在程序前面没有添加此代

码,将会产生COM错误。

在使用完ADO对象后,需要用以下的代码将初始化的对象释放:

::CoUninitialize();

此函数清除了为ADO对象准备的COM环境。

6.23 接口简介

ADO库包含三个基本接口:

__ConnectionPtr接口、

__CommandPtr接口、

__RecordsetPtr接口,

 

__ConnectionPtr接口返回一个记录集或一个空指针。通常使用它来创建一个

数据连接或执行一条不返回任何结果的SQL语句,如一个存储过程。

用__ConnectionPtr接口返回一个记录集不是一个好的使用方法。通常

同CDatabase一样,使用它创建一个数据连接,然后使用其它对象执行数

据输入输出操作。

 

__CommandPtr接口返回一个记录集。它提供了一种简单的方法来执行返回记录

集的存储过程和SQL语句。在使用__CommandPtr接口时,可以利用全

局__ConnectionPtr接口,也可以在__CommandPtr接口里直接使用连接串。如

果只执行一次或几次数据访问操作,后者是比较好的选择。但如果要频繁访问

数据库,并要返回很多记录集,那么,应该使用全局__ConnectionPtr接口创

建一个数据连接,然后使用__CommandPtr接口执行存储过程和SQL语句。

 

__RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了

更多的控制功能,如记录锁定,游标控制等。同__CommandPtr接口一样,它不

一定要使用一个已经创建的数据连接,可以用一个连接串代替连接指针赋

给__RecordsetPtr的connection成员变量,让它自己创建数据连接。如果要使

用多个记录集,最好的方法是同Command对象一样使用已经创建了数据连接的全

局—ConnectionPtr接口,然后使用__Recordse7tPtr执行存储过程和SQL语句。

6、24 使用ADO访问数据库

__ConnectionPtr是一个连接接口,首先创建一个__ConnectionPtr接口实例,

接着指向并打开一个ODBC数据源或OLE DB数据提供者(Provider)。以下代码分

别创建一个基于DSN和非DSN的数据连接。

 

  //使用__ConnectionPtr(基于DSN)

  __ConnectionPtr MyDb;

  MyDb.CreateInstance(__uuidof(Connection));

  MyDb-〉Open("DSN=samp;UID=admin;PWD=admin","","",-1);

  //使用—ConnectionPtr (基于非DSN)

  __ConnectionPtr MyDb;

  MyDb.CreateInstance(__uuidof(Connection));

MyDb.Open("Provider=SQLOLEDB;SERVER=server;DATABASE=samp;UID=admin;PWD=admin","","",-1);

 

//使用__RecordsetPtr执行SQL语句

  __RecordsetPtr MySet;

  MySet.CreateInstance(__uuidof(Recordset));

MySet-〉Open("SELECT * FROM some__table",  MyDb.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText);

现在我们已经有了一个数据连接和一个记录集,接下来就可以使用数据了。

从以下代码可以看到,使用ADO的__RecordsetPtr接口,就不需要像DAO那样频

繁地使用大而复杂的数据结构VARIANT,并强制转换各种数据类型了,这也是

ADO的优点之一。假定程序有一个名称为m__List的ListBox控件,下面代码

我们用__RecordsetPtr接口获取记录集数据并填充这个ListBox控件:

__variant__t Holder

  try{while(!MySet-〉adoEOF)

  { Holder = MySet-〉GetCollect("FIELD__1");

  if(Holder.vt!=VT__NULL)

  m__List.AddString((char)__bstr__t(Holder));

  MySet-〉MoveNext();} }

  catch(__com__error  e)

  { CString Error = e-〉ErrorMessage();

   AfxMessageBox(e-〉ErrorMessage());

  } catch(...)

  { MessageBox("ADO发生错误!");}

 

必须始终在代码中用try和catch来捕获ADO错误,否则ADO错误会使你的应用程

序崩溃。当ADO发生运行错误时(如数据库不存在),OLE DB数据提供者将自动

创建一个__com__error对象,并将有关错误信息填充到这个对象的成员变量。

 

6.25 类型转换

由于COM对象是跨平台的,它使用了一种通用的方法来处理各种类型的数据,

因此CString 类和COM对象是不兼容的,我们需要一组API来转换COM对象和

C++类型的数据。__vatiant__t和__bstr__t就是这样两种对象。它们提

供了通用的方法转换COM对象和C++类型的数据。

6.3 在VB中使用ADO

ADO 提供执行以下操作的方式:

1、连接到数据源。同时,可确定对数据源的所有更改是否已成功或没有发生。

2、指定访问数据源的命令,同时可带变量参数,或优化执行。

3、执行命令。

3、如果这个命令使数据按表中的行的形式返回,则将这些行存储在易于检查、

操作或更改的缓存中。

4、适当情况下,可使用缓存行的更改内容来更新数据源。

5、提供常规方法检测错误(通常由建立连接或执行命令造成)。

在典型情况下,需要在编程模型中采用所有这些步骤。但是,由于 ADO 有很

强的灵活性,所以最后只需执行部分模块就能做一些有用的工作。

 

以下元素是 ADO 编程模型中的关键部分:

6.31 连接

通过“连接”可从应用程序访问数据源,连接是交换数据所必需的环境。对象

模型使用 Connection 对象使连接概念得以具体化。

“事务”用于界定在连接过程中发生的一系列数据访问操作的开始和结束。

ADO 可明确事务中的操作造成的对数据源的更改或者成功发生,或者根本没有

发生。如果取消事务或它的一个操作失败,则最终的结果将仿佛是事务中的操

作均未发生,数据源将会保持事务开始以前的状态。对象模型无法清楚地体现

出事务的概念,而是用一组 Connection 对象方法来表示。ADO 从 OLE DB 提

供者访问数据和服务。Connection 对象用于指定专门的提供者和任意参数。

 

6.32 命令

通过已建立的连接发出的“命令”可以某种方式来操作数据源。一般情况下,

命令可以在数据源中添加、删除或更新数据,或者在表中以行的格式检索数

据。对象模型用 Command 对象来体现命令概念。使用 Command 对象可使

 ADO 优化命令的执行。

1、 参数

通常,命令需要的变量部分即“参数”可以在命令发布之前进行更改。例如,

可重复发出相同的数据检索命令,但每一次均可更改指定的检索信息。

参数对与函数活动相同的可执行命令非常有用,这样就可知道命令是做什么

,但不必知道它如何工作。例如,可发出一项银行过户命令,从一方借出贷

给另一方。可将要过户的款额设置为参数。

对象模型用 Parameter 对象来体现参数概念。

6.33 记录集

如果命令是在表中按信息行返回数据的查询(行返回查询),则这些行将会存

储在本地。

对象模型将该存储体现为 Recordset 对象。但是,不存在仅代表单独一个

 Recordset 行的对象。

记录集是在行中检查和修改数据最主要的方法。

6.34 字段

一个记录集行包含一个或多个“字段”。如果将记录集看作二维网格,字段将

排列构成“列”。每一字段(列)都分别包含有名称、数据类型和值的属性,

正是在该值中包含了来自数据源的真实数据。

对象模型以 Field 对象体现字段。

要修改数据源中的数据,可在记录集行中修改 Field 对象的值,对记录集的

更改最终被传送给数据源。作为选项,Connection 对象的事务管理方法能够

可靠地保证更改要么全部成功,要么全部失败。

6.35 错误

错误随时可在应用程序中发生,通常是由于无法建立连接、执行命令或对某

些状态(例如,试图使用没有初始化的记录集)的对象进行操作。

对象模型以 Error 对象体现错误。

任意给定的错误都会产生一个或多个 Error 对象,随后产生的错误将会放弃

先前的 Error 对象组。

6.36 属性

每个 ADO 对象都有一组唯一的“属性”来描述或控制对象的行为。

属性有内置和动态两种类型。内置属性是 ADO 对象的一部分并且随时可用。

动态属性则由特别的数据提供者添加到 ADO 对象的属性集合中,仅在提供者

被使用时才能存在。

 

对象模型以 Property 对象体现属性。

6.37 集合

ADO 提供“集合”,这是一种可方便地包含其他特殊类型对象的对象类型。

使用集合方法可按名称(文本字符串)或序号(整型数)对集合中的对象进

行检索。

ADO 提供四种类型的集合:

Connection 对象具有 Errors 集合,包含为响应与数据源有关的单一错误而

创建的所有 Error 对象。

Command 对象具有 Parameters 集合,包含应用于 Command 对象的所有

 Parameter 对象。

Recordset 对象具有 Fields 集合,包含所有定义 Recordset 对象列的

Field 对象。

此外,Connection、Command、Recordset 和 Field 对象都具有

Properties 集合。它包含所有属于各个包含对象的 Property 对象。

ADO 对象拥有可在其上使用的诸如“整型”、“字符型”或“布尔型”这样的

普通数据类型来设置或检索值的属性。然而,有必要将某些属性看成是数据类

型“COLLECTION OBJECT”的返回值。相应的,集合对象具有存储和检索适合

该集合的其他对象的方法。

6.38 事件

ADO 2.0 支持事件,事件是对某些操作将要或已经发生的通知。

有两类事件:ConnectionEvent 和 RecordsetEvent。Connection 对象产

生 ConnectionEvent 事件,而 Recordset 对象则产生 RecordsetEvent 事件。

事件由事件处理程序例程处理,该例程在某个操作开始之前或结束之后被调用。

某些事件是成对出现的。开始操作前调用的事件名格式为 WillEvent(Will 事

件),而操作结束后调用的事件名格式为 EventComplete(Complete 事件)。

其余的不成对事件只在操作结束后发生。(其名称没有任何固定模式。)事件

处理程序由状态参数控制。附加信息由错误和对象参数提供。

可以请求事件处理程序不接受第一次通知以后的任何通知。例如,可以选择只

接收 Will 事件或 Complete 事件。

下面的代码显示了一个使用ADO的例子。

首先加入Microsoft ActiveX Data Object 2.0 Library引用。

Dim db As Connection

Set db = New Connection

db.CursorLocation = adUseClient

db.Open "PROVIDER=MSDASQL;DSN=TestDatabase", "sa", "", -1

Dim i As Long

Dim id As Long

Dim value As Single

Dim rst As New Recordset

Set rst = New Recordset

rst.Open "select * from 模拟量变化历史表", db, adOpenDynamic, adLockOptimistic

rst.MoveFirst

For i = 0 To rst.RecordCount - 1

id = rst.Fields("ID")

value=rst.Fields(“VALUE”)

rst.MoveNext

Next i

rst.Close

Set rst = Nothing

db.Close

6.4总结

ADO技术是访问数据库的新技术,具有易于使用、访问灵活、应用广泛的特点。

用ADO访问数据源的特点可总结如下:

1、 易于使用

这是ADO技术的最重要的一个特征。由于ADO是高层应用,所以相对于OLE DB或

者ODBC来说,它具有面向对象的特性。同时,在ADO的对象结构中,其对象之

间的层次关系并不明显。相对于DAO等访问技术来讲,又不必关心对象的构造

顺序和构造层次。对于要用的对象,不必选建立连接、会话等对象,只需直接

构造即可,方便了应用程序的编制。

2、 高速访问数据源

由于ADO技术基于OLE DB,所以,它也继承了OLE DB访问数据库的高速性。

3、 可以访问不同数据源

ADO技术可以访问包括关系数据库和非关系数据库的所有文件系统。此特点使应

用程序有很多的灵活性和通用性。

4、 可以用于Microsoft ActiveX页

ADO技术可以以ActiveX控件的形式出现,所以,可以被用于Microsoft ActiveX

页,此特征可简化WEB页的编程。

5、 程序占用内存少

由于ADO是基于组件对象模型(COM)的访问技术,所以,用ADO产生的应用程

序占用内存少。

7、 总结

要在访问数据时判断出应该使用哪一种技术,这并不容易。可能需要公用实用

程序来处理多个数据库类型;部分数据可能出现在本地硬盘驱动器上,部分在

网络上,还有一部分在主机上。甚至客户安装在设备上的产品也会使这种选择

更加困难。例如,你所期待的ODBC支持级别也许依赖于所安装的

Microsoft Office的版本,因为这个产品不提供ODBC支持。你还会发现,

ADO类提供的对象和方法要比ODBC类多。ADO可以提供程序中绝对必须具有的

一些特性棗例如,你会发现OLE-DB和ADO两者都支持DFX_Currency,但在ODBC

中没有对应的功能,但你要想掌握它们也必须付出一定的努力。

选择OLE-DB或ODBC时,有几条一般的规则。因为ADO实际上只是OLE-DB的包装,

所以这些规则也适用于它。下面提供一些基本的原则,可以用来帮助你决定选

择OLE-DB还是ODBC。

非OLE环境 如果要访问支持ODBC的数据库,而该数据库又在不支持OLE的服务

器上,那么ODBC是最好的选择。

非SQL环境 ODBC在处理SQL时非常出众。处理非SQL数据库时,OLE-DB则具有

非常明显的优势。

OLE环境 对支持OLE的服务器来说,选择OLE-DB还是ODBC也许是希望各半。

如果有ODBC驱动程序可供利用,那么使用ODBC是一个好主意;否则,就只有

选择OLE-DB了。

所需的互操作性 如果需要可互操作的数据库部件,那么只有选择OLE-DB。

 

8、 参考书目:

1、 希望图书创作室译、《ODBC3程序员参考及SDK指南》、1999

2、 郑章、程刚、张勇等著、《Visual C++6.0数据库开发技术》、2000

3、 希望图书创作室译、《Visual C++6.0 技术内幕》、1999

4、 希望图书创作室译、《Visual C++与数据库管理》、1999

5、 希望图书创作室译、《Visual Basic专业版循序渐进教程》、1999

 

 

 

用Visual C++开发数据库应用程序

 

 

 

1、 概述

 

1、1 Visual C++开发数据库技术的特点

 

Visual C++提供了多种多样的数据库访问技术——ODBC API、MFC ODBC、

DAO、OLE DB、ADO等。这些技术各有自己的特点,它们提供了简单、灵活、

访问速度快、可扩展性好的开发技术。

 

 

简单性

 

Visual C++中提供了MFC类库、ATL模板类以及AppWizard、ClassWizard等

一系列的Wizard工具用于帮助用户快速的建立自己的应用程序,大大简化了

应用程序的设计。使用这些技术,可以使开发者编写很少的代码或不需编写

代码就可以开发一个数据库应用程序。

 

 

灵活性

 

Visual C++提供的开发环境可以使开发者根据自己的需要设计应用程序的界

面和功能,而且,Visual C++提供了丰富的类库和方法,可以使开发者根据自

己的应用特点进行选择。

 

 

访问速度快

 

为了解决ODBC开发的数据库应用程序访问数据库的速度慢的问题,Visual C++

提供了新的访问技术——OLE DB和ADO,OLE DB和ADO都是基于COM接口的技术,

使用这种技术可以直接对数据库的驱动程序进行访问,这大大提供了访问速度。

 

可扩展性

 

Visual C++提供了OLE技术和ActiveX技术,这种技术可以增强应用程序的能力。

使用OLE技术和ActiveX技术可以使开发者利用Visual C++中提供的各种组件、

控件以及第三方开发者提供的组件来创建自己的程序,从而实现应用程序的组件

化。使用这种技术可以使应用程序具有良好的可扩展性。

 

 

访问不同种类数据源

 

传统的ODBC技术只能访问关系型数据库,在Visual C++中,提供了OLE DB访

问技术,不仅可以访问关系型数据库,还可以访问非关系型数据库。

 

1、2 Visual C++开发数据库技术

 

Visual C++提供了多种访问数据库的技术,如下所示:

 

 

ODBC(Open DataBase Connectivity)

 

 

MFC ODBC(Microsoft Foundation Classes ODBC)

 

 

DAO (Data Access Object)

 

 

OLE DB(Object Link and Embedding DataBase)

 

 

ADO(ActiveX Data Object)

 

这些技术各有自己的特点,总结如下:

 

 

ODBC

 

ODBC是客户应用程序访问关系数据库时提供的一个统一的接口,对于不同的

数据库,ODBC提供了一套统一的API,使应用程序可以应用所提供的API来访问

任何提供了ODBC驱动程序的数据库。而且,ODBC已经成为一种标准,所以,目

前所有的关系数据库都提供了ODBC驱动程序,这使ODBC的应用非常广泛,基本

上可用于所有的关系数据库。

 

但由于ODBC只能用于关系数据库,使得利用ODBC很难访问对象数据库及其它非

关系数据库。

 

由于ODBC是一种底层的访问技术,因些,ODBC API可以使客户应用程序能够从

底层设置和控制数据库,完成一些高层数据库技术无法完成的功能。

 

 

MFC ODBC

 

由于直接使用ODBCAPI编写应用程序要编制大量代码,在Visual C++中提供

了MFC ODBC类,封装了ODBC API,这使得利用MFC来创建ODBC的应用程序非

常简便。

 

 

DAO

 

DAO提供了一种通过程序代码创建和操纵数据库的机制。多个DAO构成一个体系

结构,在这个结构中,各个DAO对象协同工作。MFC DAO是微软提供的用于访问

Microsoft Jet数据库文件(*.mdb)的强有力的数据库开发工具,它通过DAO的

封装,向程序员提供了DAO丰富的操作数据库手段。

 

 

OLE DB

 

OLE DB是Visual C++开发数据库应用中提供的新技术,它基于COM接口。因

此,OLE DB对所有的文件系统包括关系数据库和非关系数据库都提供了统一的

接口。这些特性使得OLE DB技术比传统的数据库访问技术更加优越。

 

与ODBC技术相似,OLE DB属于数据库访问技术中的底层接口。

 

直接使用OLE DB来设计数据库应用程序需要大量的代码。在VC中提供了ATL模板,

用于设计OLE DB数据应用程序和数据提供程序。

 

 

ADO

 

ADO技术是基于OLE DB的访问接口,它继承了OLE DB技术的优点,并且,ADO

对OLE DB的接口作了封装,定义了ADO对象,使程序开发得到简化,ADO技术属

于数据库访问的高层接口。

 

 

 

2、 使用ODBC API

 

Microsoft 开放数据库互连(ODBC,Open DataBase Connectivity)是

Microsoft Windows 开放服务体系(WOSA)的一部分,是一个数据库访问的标

准接口。使用这一标准接口,我们可以不关心具体的数据库管理系统(DBMS)

的细节,而只要有相应类型数据库的ODBC驱动程序,就可以实现对数据库的访

问。

 

ODBC编程接口为我们提供了极大的灵活性,我们可以通过这一个接口访问不同

种类的数据库。而且,通过相应的ODBC驱动程序,我们可以方便地实现不同数据

类型之间的转换。

 

2.1 ODBC API 概述

 

ODBC是一个应用广泛的数据库访问应用编程接口(API),使用标准的SQL

(结构化查询语言)作为其数据库访问语言。

 

2.11体系结构

 

ODBC的结构是建立在客户机/服务器体系结构之上,它包含如下四个部分:

 

应用程序(Application ):

 

应用程序即用户的应用,它负责用户与用户接口之间的交互操作,以及调用

ODBC函数以给出SQL请求并提取结果以及进行错误处理。

 

ODBC驱动程序管理器(Driver Manager):

 

ODBC驱动程序管理器为应用程序加载和调用驱动程序,它可以同时管理多个应

用程序和多个驱动程序。它的功能是通过间接调用函数和使用动态链接库

(DLL)来实现的,因此它一般包含在扩展名为”DLL”的文件中。

 

ODBC驱动程序(Driver)

 

ODBC 驱动程序执行ODBC函数调用,呈送 SQL 请求给指定的数据源,并将结果

返回给应用程序。驱动程序也负责与任何访问数据源的必要软件层进行交互作

用,这种层包括与底层网络或文件系统接口的软件。

 

数据源

 

数据源由数据集和与其相关联的环境组成,包括操作系统、DBMS 和网络(如

果存在的话)。ODBC 通过引入“数据源”的概念解决了网络拓扑结构和主机的

大范围差异问题,这样,用户看到的是数据源的名称而不必关心其它东西。

 

2.12数据类型

 

ODBC使用两类数据类型:SQL数据类型和C数据类型。SQL数据类型用于数据源,

C数据类型用于应用程序代码中。

 

2.13句柄

 

ODBC API 实现数据库操作的手段是语句,这是一个强有力的手段。ODBC语句

除了能执行SQL语句和完成查询操作之外,还能实现大多数数据库操作。

 

在ODBC中,使用不同的句柄(HANDLE)来标志环境(ENVIRONMENT)、连接

(CONNECTION)、语句(STATEMENT)、描述器(DESCRIPTOR)等。

 

句柄就是一个应用程序变量,系统用它来存储关于应用程序的上下文信息和应

用程序所用到的一些对象。它和 Windows 编程中的概念类似,不过ODBC 更加

完善了句柄的作用。

 

1、 环境句柄是 ODBC 中整个上下文的句柄,使用 ODBC 的每个程序从创建环

境句柄开始,以释放环境句柄结束。所有其它的句柄(这一应用程序所有的联

接句柄和语句句柄)都由环境句柄中的上下文来管理。环境句柄在每个应用程

序中只能创建一个。

 

2、联接句柄管理有关联接的所有信息。联接句柄可以分配多个,这不仅合法而

且很有用;但不要生成不必要的句柄以免资源的浪费。但是,不同的驱动程序支

持的联接情况有所不同,有的驱动程序在一个应用程序中仅支持一个联接句柄,

有的驱动程序仅支持一个语句句柄。在应用程序中,可以在任何适当的时候联接

或脱离数据源,但不要轻易地建立或脱离联接。

 

3、语句句柄是 ODBC API 真正发挥重要作用的,它被用来处理 SQL 语句及目

录函数,每个语句句柄只与一个联接有关。当驱动程序接收一个来自应用程序

的函数调用指令而该指令包含一个语句句柄时,驱动程序管理器将使用存储在

语句句柄中的联接句柄来将这一函数调用发送给合适的驱动程序。

 

 

4、描述器句柄是元数据的集合,这些元数据描述了SQL语句的参数、记录集的

列等信息。当有语句被分配内存之后,描述器自动生成,称为自动分配描述器。

在程序中,应用程序也可调用SQLAllocHandle分配描述器。

 

当应用程序调用API函数SQLAllocHandle时,驱动管理器或者ODBC驱动程序将

为所声明的句柄类型分配内部结构,返回句柄值。

 

2.14异常处理

 

为了在程序开发过程中调试程序,发现程序错误,ODBC API通过两种方式返回

有关ODBC API函数执行的的信息:返回码和诊断记录。返回码返回函数执行的

返回值,说明函数执行成功与否。诊断记录说明函数执行的详细信息。

 

 

返回码(Return Code)

 

每一个ODBC API函数都返回一个代码——返回码,指示函数执行的成功与否。

如果函数调用成功,返回码为SQL_SUCCESS或SQL_SUCCESS_WITH_INFO。

SQL_SUCCESS指示可通过诊断记录获取有关操作的详细信息,

SQL_SUCCESS_WITH_INFO指示应用程序执行结果带有警告信息,可通过诊断记

录获取详细的信息。如果函数调用失败,返回码为SQL_ERROR。

 

下面的一段代码根据函数SQLFetch()执行的返回码,判断函数执行的成功与否,

从而据此进行相应的处理。

 

SQLRETURN rtcode;

 

SQLHSTMT hstmt;

 

While(rtcode=SQLFetch(hstmt)!=SQL_NO_DATA)

 

{

 

if(rtcode==SQL_SUCCESS_WITH_INFO)

 

{

 

//显示警告信息

 

}

 

else

 

{

 

//显示出错信息

 

break;

 

}

 

//函数调用成功,进行处理

 

}

 

如果程序执行错误,返回码为SQL_INVALID_HANDLE,程序无法执行,而其它的

返回码都带有程序执行的信息。

 

 

诊断记录(Diagnostic Records)

 

每个ODBC API函数都能够产生一系列的反映操作信息的诊断记录。这些诊断记

录放在相关连的ODBC句柄中,直到下一个使用同一个句柄的函数调用,该诊断

记录一直存在。诊断记录的大小没有限制。

 

诊断记录有两类:头记录(Head Record)和状态记录(Status Record)。

头记录是第一版权法记录(Record 0),后面的记录为状态记录。诊断记录有

许多的域组成,这些域在头记录和状态记录中是不同的。

 

可以用SQLGetDiagField函数获取诊断记录中的特定的域,另外,可以使用

SQLGetDiagRec()获取诊断记录中一些常用的域,如SQLSTATE、原始错误号等。

 

头记录

 

头记录的各个域中包含了一个函数执行的通用信息,无论函数执行成功与否,

只要不返回SQL_INVALID_HANDLE,都会生成头记录。

 

 

状态记录

 

状态记录中的每个域包含了驱动管理器、ODBC驱动程序或数据源返回的特定的错

误或警告信息,包括SQLSTATE、原始错误码、诊断信息、列号和行号等。只有

函数执行返回SQL_ERROR,SQL_STILL_EXEUTING、SQL_SUCCESS_WITH_INFO、

SQL_NEED_DATA或SQL_NO_DATA时,才会生成诊断记录。

 

 

使用SQLGetDiagRec和SQLGetDiagField

 

应用程序可以调用函数SQLGetDiagRec或SQLGetDiagField获取诊断信息。对于

给定的句柄,这两个函数返回最近使用该句柄的函数的诊断信息。当有使用该

句柄的函数执行时,句柄记录所记录的原有的诊断信息被覆盖。如果函数执行

后产生多个状态记录,程序必须多次调用这两个函数以获取信息。

 

2.2 应用ODBC API建立应用程序

 

虽然直接应用ODBC API编制应用程序相对来说较为繁琐,但是,由于直接使

用ODBC API编写的程序相对要简洁、高效。所以,我们有必要学习直接使用

ODBC API编程。

 

一般地,编写ODBC程序主要有以下几个步骤:

 

 

分配ODBC环境

 

 

分配连接句柄

 

 

连接数据源

 

 

构造和执行SQL语句

 

 

取得执行结果

 

 

断开同数据源的连接

 

 

释放ODBC环境

 

2.21 分配ODBC环境

 

对于任何ODBC应用程序来说,第一步的工作是装载驱动程序管理器,然后初始

化ODBC环境,分配环境句柄。

 

首先,程序中声明一个SQLHENV类型的变量,然后调用函数SQLAllocHandle,向

其中传递分配的上述SQLHENV类型的变量地址和SQL_HANDLE_ENV选项。如下代码

所示:

 

SQLHENV henv;

 

SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&henv);

 

执行该调用语句后,驱动程序分配一个结构,该结构中存放环境信息,然后返

回对应于该环境的环境句柄。

 

2.22分配连接句柄

 

分配环境句柄后,在建立至数据源的连接之前,我们必须分配一个连接句柄,

每一个到数据源的连接对应于一个连接句柄。

 

首先,程序定义了一个SQLHDBC类型的变量,用于存放连接句柄,然后调用

SQLAllocHandle函数分配句柄。如下代码所示:

 

SQLHDBC hdbc;

 

SQLAllocHandle(SQL_HANDLE_DBC,henv,&hdbc);

 

henv为环境句柄。

 

2.23 连接数据源

 

当连接句柄分配完成后,我们可以设置连接属性,所有的连接属性都有缺省

值,但是我们可以通过调用函数SQLSetConnectAttr()来设置连接属性。用函

数SQLGetConnectAttr()获取这些连接属性。

 

函数格式如下:

 

SQLRETURN SQLSetConnectAttr(SQLHDBC ConnectionHandle,SQLINTEGER Attribute,SQLPOINTER ValuePtr,SQLINTEGER StringLength);

 

SQLRETURN SQLGetConnectAttr(SQLHDBC ConnectionHandle,SQLINTEGER Attribute,SQLPOINTER ValuePtr,SQLINTEGER StringLength);

 

应用程序可以根据自己的需要设置不同的连接属性。

 

完成对连接属性的设置之后,就可以建立到数据源的连接了。对于不同的程序和

用户接口,可以用不同的函数建立连接:SQLConnect、SQLDriverConnect、SQLBrowseConnect。

 

SQLConnect

 

该函数提供了最为直接的程序控制方式,我们只要提供数据源名称、用户ID和

口令,就可以进行连接了。

 

函数格式:

 

SQLRETURN SQLConnect(SQLHDBC ConnectionHandle,SQLCHAR ServerName,SQLSMALLINT NameLength1,SQLCHAR UserName,SQLSMALLINT NameLength2,SQLCHAR *Authentication,SQLSMALLINT NameLength3);

 

参数:

 

ConnectionHandle 连接句柄

 

ServerName 数据源名称

 

NameLength1 数据源名称长度

 

UserName 用户ID

 

NameLength2 用户ID长度

 

Authentication 用户口令

 

NameLength3 用户口令长度

 

返回值:

 

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE.

 

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,

可以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

 

 

下面的代码演示了如何使用ODBC API的SQLConnect函数建立同数据源SQLServer

的连接。

 

 

 

#include “sqlext.h”

 

SQLHENV henv;;

 

SQLHDBC hdbc;

 

SQLHSTMT hstmt;

 

SQLRETURN retcode;

 

/*Allocate environment handle */

 

retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

 

/* Set the ODBC version environment attribute */

 

retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

 

/* Allocate connection handle */

 

retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

 

/* Set login timeout to 5 seconds. */

 

SQLSetConnectAttr(hdbc, (void*)SQL_LOGIN_TIMEOUT, 5, 0);

 

/* Connect to data source */

 

retcode = SQLConnect(hdbc, (SQLCHAR*) "Sales", SQL_NTS,

 

(SQLCHAR*) "JohnS", SQL_NTS,

 

(SQLCHAR*) "Sesame", SQL_NTS);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){

 

/* Allocate statement handle */

 

retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

 

/* Process data */;

 

SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

 

}

 

SQLDisconnect(hdbc);

 

}

 

SQLFreeHandle(SQL_HANDLE_DBC, hdbc);

 

}

 

}

 

SQLFreeHandle(SQL_HANDLE_ENV, henv);

 

SQLDriveConnect

 

函数SQLDriveConnect用一个连接字符串建立至数据源的连接。它可以提供比

SQLConnect函数的三个参数更多的信息,可以让用户输入必要的连接信息。

 

如果连接建立,该函数返回完整的字符串,应用程序可使用该连接字符串建立另

外的连接。

 

函数格式:

 

SQLRETURN SQLDriverConnect(SQLHDBC ConnectionHandle,SQLHWND WindowHandle,SQLCHAR InConnectionString,SQLSMALLINT StringLength1,SQLCHAR OutConnetionString,SQLSMALLINT BufferLength,SQLSMALLINT *StringLength2Ptr,SQLSMALLINT DriverCompletion);

 

参数:

 

ConnectionHandle 连接句柄

 

WindowHandle 窗口句柄,应用程序可以用父窗口的句柄,或用NULL指针

 

InConnectionString 连接字符串长度

 

OutConnectionString 一个指向连接字符中的指针

 

BufferLength 存放连接字符串的缓冲区的长度

 

StringLength2Ptr 返回的连接字符串中的字符数

 

DriverCompletion 额外连接信息,可能取值有:SQL_DRIVER_PROMPT,

 

SQL_DRIVER_COMPLETE,

 

SQL_DRIVER_COMPLETE_REQUIRED, or

 

SQL_DRIVER_NOPROMPT.

 

返回值:

 

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE.

 

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可

以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

 

 

SQLBrowseConnect

 

函数SQLBrowseConnect支持以一种迭代的方式获取到数据源的连接,直到最后

建立连接。它是基于客房机/服务器的体系结构,因此,本地数据库不支持该函

数。

 

一般,我们提供部分连接信息,如果足以建立到数据源的连接,则成功建立连

接,否则返回SQL__NEED__DATA,并在OutConnectionString参数中返回所需要的

信息。

 

函数格式:

 

SQLRETURN SQLBrowseConnect(SQLHDBC ConnectionHandle,SQLCHAR* InConnectionString,SQLSAMLLINT StringLength1,SQLCHAR* OutConnectionString,SQLSMALLINT BufferLength,SQLSMALLINT *StringLength2Ptr);

 

参数:

 

ConnectionHandle 连接句柄

 

InConnectionString 指向输出字符串的指针

 

StringLength1 输出字符串的指针长度

 

OutConnectionString 指向输出字符串的指针

 

BufferLength 用于存放输出字符串的缓冲区的长度

 

StringLength2Ptr 实际返回的字符串的长度

 

 

 

返回值:

 

SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_ERROR, or SQL_INVALID_HANDLE.

 

成功返回SQL_SUCCESS,如果返回值为SQL_ERROR或SQL_SUCCESS_WITH_INFO,可

以用函数SQLGetDiagRec获取相应SQLSTATE的值。

 

 

 

下面的代码讲述了如何使用ODBC API的SQLBrowseConnect函数建立同数据源的

连接。

 

 

 

#define BRWS_LEN 100SQLHENV

 

henv;SQLHDBC hdbc;

 

SQLHSTMT hstmt;

 

SQLRETURN retcode;

 

SQLCHAR szConnStrIn[BRWS_LEN], szConnStrOut[BRWS_LEN];

 

SQLSMALLINT cbConnStrOut;/* Allocate the environment handle. */

 

retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

 

/* Set the version environment attribute. */

 

retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3, 0);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

 

/* Allocate the connection handle. */

 

retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {

 

/* Call SQLBrowseConnect until it returns a value other than */

 

/* SQL_NEED_DATA (pass the data source name the first time). */

 

/* If SQL_NEED_DATA is returned, call GetUserInput (not */

 

/* shown) to build a dialog from the values in szConnStrOut. */

 

/* The user-supplied values are returned in szConnStrIn, */

 

/* which is passed in the next call to SQLBrowseConnect. */

 

lstrcpy(szConnStrIn, "DSN=Sales"); do {

 

retcode = SQLBrowseConnect(hdbc, szConnStrIn, SQL_NTS,

 

szConnStrOut, BRWS_LEN, &cbConnStrOut);

 

if (retcode == SQL_NEED_DATA)

 

GetUserInput(szConnStrOut, szConnStrIn);

 

} while (retcode == SQL_NEED_DATA);

 

if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){

 

/* Allocate the statement handle. */

 

retcod

 

 

 

一个优秀的网格控件CGridCtrl



作者:戴绍忠

  网格控件的用途非常广泛,在我的一个项目中需要实现类似EXCEL的界面,为此我采用了一个优秀的CGridCtrl控件,其原作者为 Chris Maunder (原作者所写的MFC Grid control的最新版本可以到http://www.codetools.com/miscctrl/gridctrl.asp查阅)为了方便地实现单元格的合并和撤销,我定制了如下的几个公有成员函数 

SetCellCombine(); //设置单元格的合并特性
UndoCellCombine(); //取消合并
SetFixedCellCombine(); //合并单元格
GetCellp(); //取得单元格对象指针 
其具体的实现我在代码中添加了//want remove it? 和//added for combine;注释语句来表明这段说明中的代码是用于合并。以方便以后的改变。


使用办法:
将文件名为:GridCtr.cpp,GridCtr.p,CellRange.h,GridDropTarget.cpp,GridDropTarget.h
InPlaceEdit.cpp,InPlaceEdit.h, InPlaceList.cpp,InPlaceList.h, MemDC.h,TitleTip.cpp, TitleTip.h 复制到你的项目中然后导入。修改相应的头文件就可以了。

本文提供了示例代码,包含了全部源程序,示例程序的运行效果图如下:












































OnDraw中ROP2方式有点问题,我这样修改了一下 作者:lolach 发表日期:2001-9-1 12:07:30

// int orientedROP;
// orientedROP=pDC->SetROP2(R2_NOT);
CGridCell *pCell;
CRect removelinerect;
if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_VERT) 
{
int x = nFixedColWidth;
for (col = minVisibleCol; col <= maxVisibleCol; col++)
{
x += GetColumnWidth(col);
// pDC->MoveTo(x-1, nFixedRowHeight);
// pDC->LineTo(x-1, VisRect.bottom); 
//want remove it?
for(int i = minVisibleRow; i <= maxVisibleRow; i++)

pCell=GetCell(i,col);
if(pCell->m_bSkipV!=TRUE)
{
GetCellRect(i,col,removelinerect);
pDC->MoveTo(x-1,removelinerect.top );
pDC->LineTo(x-1, removelinerect.bottom+1); 
}
}
}
}
// draw horizontal lines (drawn at bottom of each cell)
if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_HORZ) 
{
int y = nFixedRowHeight;
for (row = minVisibleRow; row <= maxVisibleRow; row++) 
{
y += GetRowHeight(row);
// pDC->MoveTo(nFixedColWidth, y-1); 
// pDC->LineTo(VisRect.right, y-1);
//want remove it?
for(int colj = minVisibleCol; colj <= maxVisibleCol; colj++)
{
pCell=GetCell(row,colj);
if(pCell->m_bSkipH!=TRUE)
{
GetCellRect(row,colj,removelinerect);
pDC->MoveTo(removelinerect.left-1,y-1 );
pDC->LineTo(removelinerect.right,y-1); 
}
}
}
}
// pDC->SetROP2(orientedROP);//added for combine;