正文

VC++下ODBC的编程(转载)2007-08-28 17:23:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/sword2008/28913.html

分享到:

 标  题: VC++下ODBC的编程
发信站: 控制台 (Mon Mar 23 19:17:49 1998), 转信

VC++下ODBC的编程

作者:姜青松  王洋  刘强

   
    摘要本文在介绍了ODBC(开放性数据库连接,Open  DataBase  Co
nnectivity)运行机制的基础上,着重讨论了VisualC++2.0下利用ODBC
API及利用MFC进行ODBC编程的两种方法.
    关键词ODBC,SQL,数据源,文档/视图结构,DBMS
    一、ODBC的发展背景
    在传统的数据库领域,数据库应用程序通常是指在特定的数据库
管理系统(DBMS)的支持下,用特定的内嵌式结构化查询语言(SQL)开发
的.这样的数据库应用程序存在如下的缺点:(1)它往往需要一个庞大
的数据库管理系统的支持,对用户的软、硬件要求高;(2)它通常只能
处理一种格式的数据库文件.
    与传统的数据库应用程序的实现方法相比,Microsoft的开放性数
据库连接(ODBC)标准则提供了一种新的途径:它建立了一组规范,并提
供了一组高层应用程序调用接口和一套基于动态链接库(DLL)的运行
支持环境.用这样一组接口规范开发的应用程序,使用标准的函数和结
构化查询语言(SQL)对数据库进行操作,不必关心"数据源"(DataSourc
e)来自何种数据库管理系统DBMS,所有的数据库的底层操作都是由相
应的ODBC驱动程序(ODBCDriver)完成.只要有了相应的ODBC驱动程序,
应用程序处理的对象-数据源就可以非常广泛,既可以是本机的某种数
据库格式的文件,如FoxPro的*.dbf文件,也可以是远程数据库文件,如
MicrosoftSQLServer等.
    二、ODBC的运行机制
    ODBC的体系构架包括五个部分:ODBC管理器(ODBCAdministrator)
、应用程序(Application)、ODBC驱动程序管理器(ODBCDriver  Mana
ger)、ODBC驱动程序(ODBCDriver)和数据源(Data  Source).图1表示
了ODBC各部分之间的关系.
    ODBC管理器在整个ODBC运行机制中起配置环境、登录信息的作用
,它被安装在Control  Pannel里(ODBCINST.CPL).通过该工具,可以用
来配置、增添和删除数据源,也可以用来删除、安装ODBC驱动程序.OD
BC管理器把数据源和ODBC驱动程序的信息记录在ODBC.INI、ODBCINST
.INI和ODBCISAM.INI中,或者登录在系统数据库中,ODBC的其他部件通
过读取这些信息,相互作用,应用程序就能够实现对已登录数据库的共
享.
    应用程序(Application)的主要任务是通过调用标准的ODBC函数,
提交SQL语句并返回结果,对结果进行处理.
    ODBC驱动程序管理器的作用是根据应用程序的要求,调用不同的O
DBC驱动程序.
    ODBC驱动程序的作用是实现ODBC函数调用,对指定的数据源执行S
QL语句,并把结果返回给应用程序.有时候,为了符合特定的数据库管
理系统的语法,ODBC驱动程序还会对应用程序的要求作适当修改.这里
,ODBC驱动程序的作用与运行在Windows下的打印机驱动程序的作用非
常相似.
    数据源,由用户要访问的数据及与之相关的操作系统、数据库管
理系统和用于访问数据库管理系统所需的网络平台组成.
    对应用程序而言,ODBC驱动程序管理器和ODBC驱动程序就像一个
实现ODBC函数调用的整体单元,应用程序感觉不到它们之间的分工合
作关系.整个ODBC的结构是基于一种独特的动态链接库DLL而存在的,
它使得系统完全模块化了.
    三、利用ODBC  API的C语言ODBC编程
    下面以一个典型ODBC数据库访问程序的伪代码为例,说明用ODBCA
PI的C语言进行ODBC编程的一般步骤.该程序从用户接受SQL查询语句,
然后获取结果,集中各行各列的数据.
    HENV  henv
    HDBChdbc
    HSTMThstmt
    int  nCols
    SQLALLocEnv(&henv)/*环境申请,获得一个环境句柄*/
    SQLAllocConnect(henv,&hdbc)/*连接申请,获得数据库连接句柄
*/
    SQLDriverConnect(hdbc,…)/*与具体的ODBC驱动程序和数据源
联系*/
    SQLAllocStmt(hdbc,&hstmt)/*语句申请,获得一个语句句柄*/
    Input  SQLStatement/*从用户接受SQL语句*/
    SQLExecDirect(hstmt,SQLStatement,…)/*执行SQL语句*/
    SQLNumResultCols(hstmt,&nCols)/*获取结果集中列数*/
    for  each  column{/*获得每列的信息,并与相应的C变量联系起
来*/
    SQLDescribeCol(hstmt,col,ColName,…)/*获取当前列的信息*/
    SQLBindCol(hstmt,col,…,variable,…)/*与相应变量联系起来
*/
    }
    for  each  row{/*获取结果集中每行每列的数据,并作相应的处
理*/
    SQLFetch(hstmt)/*将当前行的数据存储在SQLBindCol
    指定的变量中,当前行在结果集中后向下滚动一行*/
    foreach  column{
    process  field  data  from  column  variable
    }
    }
    SQLfreeStmt(hstmt,…)/*释放SQL语句句柄*/
    SQLDisconnect(hdbc)/*断开当前的连接*/
    SQLFreeConnect(hdbc)/*释放当前数据库连接句柄*/
    SQLFreeEnv(henv)/*释放环境句柄*/
    当编写C程序对ODBC数据库进行访问时,必须了解三个非常重要的
概念:环境(environment)、连接(connection)、语句(statement).和
其他的C语言Windows程序一样,它们都是通过句柄(handle)来访问的.
    首先需要一个环境句柄,如上面伪代码所示,它建立了应用程序与
ODBC系统之间的联系,一个应用程序通常只有一个环境句柄.
    下一步需建立一个或多个链接,它建立了驱动程序与数据源之间
的特定组合关系.如果在函数SQLDriverConnect(hdbc,…,strConnect
,…)中提供了如下的输入字符串,
    DSN=COMPONEN;FIL=FOXPRO2.6
    而且ODBC管理器中路径及配置正确的话,该函数就能自动建立起
连接来.
    环境和连接获得后,最后还需要一个SQL语句来实施具体的数据库
操作,它可以是如下形式的查询语句:
    SELECT  NAME,AGE,SEX,GRADE  FROMSTUDENT  WHERE  SEX=′F

    也可以是如下的更新语句:
    UPDATE  STUDENTSETAGE=′24′WHERE  NAME=′JIANG  QING  S
ONG′
    四、利用MFC的C++语言ODBC编程
    VisualC++2.0对ODBCAPI进行了封装,封装后,最重要的数据库MFC
类有CDatabase、CRecordset和CRecordView类.尤其是CRecordset类,
它一方面与CDatabase类的对象进行数据交换,另一方面又与CRecordV
iew类进行数据交换,是CDatabase类和CRecordView类之间的桥梁.它
们之间的作用关系如图2所示.
    实际上,CDatabase类的每个对象代表了一个数据源的连接,适宜
于对数据源下的某个表格进行整体操作;CRecordset类的每个对象代
表了记录的集合,这个集合是对指定数据表格按预定的查询条件获得
的,它适宜于对所选的记录集合进行操作;CRecordView类的每个对象
是与之相联系CRecordset类对象的外部表现形式,它的作用是把CReco
rdset类对象的变化以一些标准Windows控制的形式表现出来,是主要
的用户界面.
    在应用编程时,根据问题的复杂程度和要求,可采用两种不同的方
法来实现.如果仅仅是对某指定的数据库进行
    一些简单的操作,如向数据库中追加、删除、更改一些特定的记
录等,并且不需要显示数据库的这些变化的话,就可以采用第一种方法
,仅仅利用CDatabase类的相关功能来实现.这种方法只需两步就可完
成:
    1.调用CDatabase类的打开(Open)函数,进行数据源的实际连接.
    2.调用CDatabase类的成员函数ExecuteSQL,执行一条SQL语句,实
现特定的数据库操作.下面InsertRecord函数是实现向数据源compone
n的表COMPONEN中插入一行记录,记录的TYPE字段的值为"pump"的最简
代码.
    void  InsertRecord()
    {
    CDatabase  m-db;
    m-db.Open(NULL,FALSE,FALSE,″ODBC;DSN=componen″);
    m-db.ExecuteSQL(″insertinto  COMPONEN(TYPE)value(′pump
′)″);
    m-db.Close();
    }
    然而,在实际编程中,这种情况很少,多数数据库应用程序不仅要
实现复杂得多的数据库操作,还需要显示变化了的数据库的情况,这样
就需要用到VisualC++的文档/视图结构,需要CDatabase、CRecordset
、CRecordView和CDocument类之间相互配合来实现.下面以VisualC++
2.0的AppWizard和ClassWizard产生的一个实际工程test.mak为例,说
明这些类是如何相互作用的.
    工程test.mak的数据源是componen的COMPONEN表,该表的结构如
表1所示.test.exe要实现的功能是按默认的条件查询COMPONEN表,滚
动记录集,并在屏幕上显示当前记录集中的当前记录.有关的代码如下
所示:
    表1COMPONEN的结构
    class  CTestSet:public  Crecordset
    {
    public:
    CTestSet(CDatabase*pDatabase=NULL);
    DECLARE-DYNAMIC(CTestSet)
    //{{AFX-FIELD(CTestSet,CRecordset)
    CStringm-NAME;
    CString  m-CATALOGID;
    CStringm-SIZE;
    CStringm-ORDERID;
    CString  m-PRODUCER;
    //}}AFX-FIELD
    //{{AFX-VIRTUAL(CTestSet)
    public:
    virtual  CStringGetDefaultConnect();//Default  connectio
n  string
    virtual  CString  GetDefault//default  SQLfor  Recordset
    virtual  void  DoFieldExchange(CFieldExchange*pFX);//RFX
support
    //}}AFX-VIRTUAL
    ……
    };
    CStringCTestSet::GetDefaultConnect()
    {
    return-T(″ODBC;DSN=componen;″);
    }
    CStringCTestSet::GetDefaultSQL()
    {
    return-T(″COMPONEN″);
    }
    void  CTestSet::DoFieldExchange(CFieldExchange*pFX)
    {//{{AFX-FIELD-MAP(CTestSet)
    pFX->SetFieldType(CFieldExchange::outputColumn);
    RFX-Text(pFX,″NAME″,m-NAME);
    RFX-Text(pFX,″CATALOGID″,m-CATALOGID);
    RFX-Text(pFX,″SIZE″,m-SIZE);
    RFX-Text(pFX,″ORDERID″,m-ORDERID);
    RFX-Text(pFX,″PRODUCER″,m-PRODUCER);
    /*以下是参数连接部分,m-nParams必须先被赋值为2.这段代码仅
仅是为了说明如何参数化查询条件而设置,test.mak中没有这几行代
码.
    pFX->SetFieldType(CFieldExchange::param);
    RFX-Text(pFX,″NAMEParam″,m-strNAMEParam,20);
    RFX-Text(pFX,″CATALOGIDParam″,m-strCATALOGIDParam,10);
    */
    //}}AFX-FIELD-MAP
    }
    class  CTestDoc:public  Cdocument
    {
    ……
    public:
    CTestSet  m-testSet;
    public:……
    };
    class  CTestView:public  CRecordView
    {
    ……
    public:
    //{{AFX-DATA(CTestView)
    enum{IDD=IDD-TEST-FORM};
    CTestSet*m-pSet;
    //}}AFX-DATA
    //Attributes
    public:
    CTestDoc*GetDocument();
    ……
    public:
    //{{AFX-VIRTUAL(CTestView)
    public:
    virtual  CRecordset*OnGetRecordset();
    protected:
    virtual  void  DoDataExchange(CDataExchange*pDX(;//DDX/D
DVsupport
    virtual  void  OnInitialUpdate();//called  firsttime  af
ter  construct
    //}}AFX-VIRTUAL
    };
    void  CTestView::DoDataExchange(CDataExchange*pDX)
    {
    CRecordView::DoDataExchange(pDX);
    //{{AFX-DATA-MAP(CTestView)
    DDX-FieldText(pDX,IDC-EDIT-CATALOGID,m-pSet->m-CATALOGI
D,m-pSet);
    DDX-FieldText(pDX,IDC-EDIT-NAME,m-pSet->m-NAME,m-pSet);
    DDX-FieldText(pDX,IDC-EDIT-ORDERID,m-pSet->m-ORDERID,m-
pSet);
    DDX-FieldText(pDX,IDC-EDIT-PRODUCER,m-pSet->m-PRODUCER,
m-pSet);
    DDX-FieldText(pDX,IDC-EDIT-SIZE,m-pSet->m-SIZE,m-pSet);
    //}}AFX-DATA-MAP
    }
    CRecordset*CTestView::OnGetRecordset()
    {
    return  m-pSet;
    }
    CTestDoc*CTestView::GetDocument()
    {
    ASSERT(m-pDocument->IsKindOf(RUNTIME-CLASS(CTestDoc)));
    return(CTestDoc*)m-pDocument;
    }
    void  CTestView::OnInitialUpdate()
    {
    m-pSet=&GetDocument()->m-testSet;
    CRecordView::OnInitialUpdate();
    }
    分析以上代码要从CTestView类开始.依照VC标准的文档/视图结
构的相互作用关系,记录视图类CRecordView的导出类CTestView,在显
示前要调用它的成员函数OnInitialUpdate(),弄清函数CTestView::O
nInitialUpdate()在这里的行为是理解整个代码的关键.下面是CTest
View::OnIni
    tialUpdate()函数调用关系图:
    CTestView::OnInitialUpdate()
    CRecordView::OnInitialUpdate()//调用基类的OnInitialUpdat
e()函数
    CTestView::OnGetRecordset()//得到一个CRecordset类的指针
    CRecordset::Open()//利用前面得到的指针访问Open函数
    CTestSet::GetDefaultConnect()//得到默认连接字符串
    CDatabase::Open()//利用默认连接字符串与数据源连接
    CTestSet::GetDefaultSQL()//得到默认SQL语言,执行它
    CFormView::OnInitialUpdate()//调用CForm  View::OnInitial
Update
    CTestView::DoDataExchange()//与Windows编辑控制联系起来
    通过以上分析,我们知道,表面上只有CRecordset、CRecordView
和CDocument类的导出类之间相互作用,实际上,由于CRecordset::Ope
n()函数要构造一个CDatabase类指针,并通过默认连接字符串与特定
的数据源连接,仍然是四个类之间相互作用的,只不过是VC的标准框架
将这种关系封装起来罢了.
    补充说明几点:
    ·对当前记录集中当前记录的滚动功能,应用程序中不需要任何
代码支持,VC的标准框架默认实现了.
    ·CRecordset::Open()是如何构造一个完整的SQL查询语句、如
何理解CRecordset类工作机制的关键.一个完整的SQL查询语句具有下
面的普遍形式:
    SELECT[ALL|DISTINCT]columnlist  FROMtablelist
    [WHEREsearchcondition][ORDER  BYcolumnlist[ASC|DESC]]
    []内的内容表示是可选的,|表示"或者".CRecordset::Open()构
造这样的SQL查询语句的伪代码如下所示:
    CRecordset::Open(…,lpszSQL…)
    {
    IF(lpszSQL!=NULL)strSQL=lpszSQL
    ELSEstrSQL=CRecordSet::GetDefaultSQL()
    IF(strSQL不以"SELECT"或"CALL}"开头)
    {
    strSQL="SELECTcolumnlist  FROMtablelist"
    其中,columnlist与CRecordSet::DoFieldExchange()中列名保持
数量和顺序上的一致;tablelist是前面strSQL的值
    }
    IF(m-strFilter!=NULL)strSQL+="WHERE"+m-strFilter
    IF(m-strSort!=NULL)strSQL+="ORDER  BY"+m-strSort
    }
    其中,strSQL是保存SQL语句的变量、m-strFilter、m-strSort是
CRecordSet类的公有成员变量.CRecordSet类对象中包含的数据就是
符合strSQL中查询条件的记录的集合.
    ·程序可以动态改变SQL语句中的查询条件,即参数化WHERE字句
和ORDERBY字句.在变量m-strFilter、m-strSort中将要参数化的部分
以"?"代替.在调用打开函数CRecordSet::Open()前(如在构造函数中)
,将参数变量的个数赋值给变量m-nParams,并在CRecordSet::DoField
Exchange()中按参数出现的先后顺序定义好,如CTestSet::DoFieldEx
change()代码中注释部分所示.
    五、结束语
    从以上讨论中可知,ODBC应用程序与传统的数据库应用程序相比,
确实有自己独特的优点,它不仅编程接口十分简单方便,更主要的是它
的运行机制非常适合与应用程序的其他功能完美地融合.笔者最近利
用了ODBC技术,通过ODBC系统读取FoxPro2.6数据库文件中的记录,能
够实现液压原理图中元件明细表的自动填充功能,非常方便快捷.

阅读(6754) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册