?ODBC(Open Database Connectivity,开放式数据库连接),是一种用来在相关或不相关的数据库管理系统(DBMS)中存取数据的标准应用程序接口(API)。本文给出Windows 95 环境下用Visual C++ 进行ODBC 编程的具体方法及技巧。 \`|OAC0a
O`'r:W
---- 关键字:ODBC,Visual C++,Windows 编程。 XjJ[7"hs*
hv9k9i7@l
---- 一.概述 f26hB;n
JrwR:_+|
---- ODBC 是一种使用SQL 的程序设计接口。使用ODBC 让应用程序的编写者避免了与数据源相联的复杂性。这项技术目前已经得到了大多数DBMS 厂商们的广泛支持。 kSU]~x
'>dx~v %
---- Microsoft Developer Studio 为大多数标准的数据库格式提供了32 位ODBC 驱动器。这些标准数据格式包括有:SQL Server、Access、Paradox、dBase、FoxPro、Excel、Oracle 以及Microsoft Text。如果用户希望使用其他数据格式,用户需要相应的ODBC 驱动器及DBMS。 fqD1Ej
??? ;H
---- 用户使用自己的DBMS 数据库管理功能生成新的数据库模式后,就可以使用ODBC 来登录数据源。对用户的应用程序来说,只要安装有驱动程序,就能注册很多不同的数据库。登录数据库的具体操作参见有关ODBC 的联机帮助。 +IbQVU~/
ivP#qM1*;
---- 二.MFC 提供的ODBC 数据库类 eW;0{P
p7]V1w :
---- Visual C++ 的MFC 基类库定义了几个数据库类。在利用ODBC 编程时,经常要使用到CDatabase( 数据库类),CRecordSet( 记录集类) 和CRecordView( 可视记录集类)。其中: @x/D8HK2
wT^Q O^.
---- CDatabase 类对象提供了对数据源的连接,通过它你可以对数据源进行操作。 Hge0$6l
hH=}<@z
---- CRecordSet 类对象提供了从数据源中提取出的记录集。CRecordSet 对象通常用于两种形式:动态行集(dynasets)和快照集(snapshots)。动态行集能保持与其他用户所做的更改保持同步。快照集则是数据的一个静态视图。每一种形式在记录集被打开时都提供一组记录,所不同的是,当你在一个动态行集里滚动到一条记录时,由其他用户或是你应用程序中的其他记录集对该记录所做的更改会相应地显示出来。 qku!Mg
{Nny.@P)H
---- CRecordView 类对象能以控制的形式显示数据库记录。这个视图是直接连到一个CRecordSet 对象的表视图。 7\ kixfEg
gw v
s
---- 三.应用ODBC 编程 @LR :^>&*
^ub@Jwe
---- 应用Visual C++ 的AppWizard 可以自动生成一个ODBC 应用程序框架。方法是:打开File 菜单的New 选项,选取Projects,填入工程名,选择MFC AppWizard (exe),然后按AppWizard 的提示进行操作。当AppWizard 询问是否包含数据库支持时,如果你想读写数据库,那么选定Database view with file support;而 ?阆敕梦适?菘獾男畔⒍?幌牖匦此?龅母谋洌?敲囱《―atabase view without file support 选项就比较合适了。选择了数据库支持之后Database Source 按钮会激活,选中它去调用Data Options 对话框。在Database Options 对话框中会显示已向ODBC 注册的数据库资源,选定你所要操作的数据库,如:Super_ES,单击OK 后会出现Select Database Tables 对话框,其中列举了你所选中的数据库中包含的全部表,选择你希望操作的表后,单击OK。在选定了数据库和数据表之后,你可以按照惯例继续进行AppWizard 操作。 N&-J,p~
hBNA,e:
---- 特别需要指出的是:在生成的应用程序框架View 类(如:CSuper_ESView)中包含一个指向CSuper_ESSet 对象的指针m_pSet,该指针由AppWizard 建立,目的是在视表单和记录集之间建立联系,使得记录集中的查询结果能够很容易地在视表单上显示出来。有关m_pSet 的详细用法可以参见Visual C++ Online Book。 vuNq7V*}
NekPl/4
---- 程序与数据语言建立联系,使用CDatebase::OpenEx() 或CDatabase::Open() 函数来进行初始化。数据库对象必须在你使用它构造一个记录集对象之前被初始化。 |E9iG
-gy@sSfvkv
---- 下面举例说明在Visual C++ 环境中ODBC 的编程技巧: .WTar9e#
4{Af 3N
---- 1 .查询记录 (z.eXo P@>
ibQN
p Iz
---- 查询记录使用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}xyW"yp
(2p<I)t
Super_ESSet.Open(AFX_DATABASE_USE_DEFAULT,strSQL); n 8'#'^|
---- 如果没有指定参数,程序则使用缺省的SQL 语句,即对在GetDefaultSQL() 函数中指定的SQL 语句进行操作: v0'`K 5M
b=Oec%Adx
CString CSuper_ESSet::GetDefaultSQL() >sm<$'vZ/
{return _T("[BasicData],[MainSize]");} s|o+
Im
---- 对于GetDefaultSQL() 函数返回的表名,对应的缺省操作是SELECT 语句,即: %g{<EuK]p
gP:H_nVh
SELECT * FROM BasicData,MainSize Xi81?F?[
---- 查询过程中也可以利用CRecordSet 的成员变量m_strFilter 和m_strSort 来执行条件查询和结果排序。m_strFilter 为过滤字符串,存放着SQL 语句中WHERE 后的条件串;m_strSort 为排序字符串,存放着SQL 语句中ORDER BY 后的字符串。如: XmX{e.<NZ
/m(v5v7(
Super_ESSet.m_strFilter="TYPE=电动机"; 7':qx}c#!1
Super_ESSet.m_strSort="VOLTAGE"; db5@+_
Super_ESSet.Requery(); )|`|Usn#[
对应的SQL语句为: M
Qlx&.>
SELECT * FROM BasicData,MainSize db0]D\
WHERE TYPE=电动机 ])H[>.?K
ORDER BY VOLTAGE XPsRa[08WK
---- 除了直接赋值给m_strFilter 以外,还可以使用参数化。利用参数化可以更直观,更方便地完成条件查询任务。使用参数化的步骤如下: &BS*C} },
rM{V>s:N
---- (1) .声明参变量: {<y.G1<.
xJ|_R,>.H
CString p1; 0`%Ask
float p2; We?cRb
---- (2) .在构造函数中初始化参变量 .Arcsg
xdkC>o4>
p1=_T(""); u#~q86k
p2=0.0f; &(i_s
m_nParams=2; ;{f4E)t 7
---- (3) .将参变量与对应列绑定 qttJ*zu
6PdLJ#LS
pFX- >SetFieldType(CFieldExchange::param) xfADks2w
RFX_Text(pFX,_T("P1"),p1); yHjuT+/wM,
RFX_Single(pFX,_T("P2"),p2); 8a,pDE
---- 完成以上步骤之后就可以利用参变量进行条件查询了: b,):&M~p
x4%1P w
m_pSet- >m_strFilter="TYPE=? AND VOLTAGE=?"; [ T!0ka
m_pSet- >p1=" 电动机"; +jN%w{^=
m_pSet- >p2=60.0; 5tQZf'pHfd
m_pSet- >Requery(); 5><KTya?=
---- 参变量的值按绑定的顺序替换查询字串中的"?" 适配符。 IT=<p60"
mVNHH!
---- 如果查询的结果是多条记录的话,可以用CRecordSet 类的函数Move(),MoveNext(),MovePrev(),MoveFirst() 和MoveLast() 来移动光标。 ~"}o^#@DwJ
Gx/kel[Y}
---- 2 .增加记录 @z1pE@7jK
kYnp$8
---- 增加记录使用AddNew() 函数,要求数据库必须是以允许增加的方式打开: y,cz;2
o*
C_9M
m_pSet- >AddNew(); //在表的末尾增加新记录 .LA?2N
m_pSet- >SetFieldNull(&(m_pSet- >m_type), FALSE); zyPc<\HoK
m_pSet- >m_type=" 电动机"; {?hpW+1,#
... //输入新的字段值 Ic')L*i7O
m_pSet- > Update(); //将新记录存入数据库 9L9qLF5 t
m_pSet- >Requery(); //重建记录集 cPbAR'
---- 3 .删除记录 ?3Y~q;I]O
c@Q&i
---- 直接使用Delete() 函数,并且在调用Delete() 函数之后不需调用Update() 函数: cyPJ(&;
%E*Q0/
m_pSet- >Delete(); {dXmSuO
if (!m_pSet- >IsEOF()) }(/\vTn*1
m_pSet- >MoveNext(); g=L80$1
else (,OF<<OH
m_pSet- >MoveLast(); cbaa*qoU
---- 4 .修改记录 $i]G'fj
ViYfK7Z
---- 修改记录使用Edit() 函数: Vh'H =J
dBNx2T}_0
m_pSet- >Edit(); //修改当前记录 L5 Q^cY]p
m_pSet- >m_type="发电机"; jHQnD]Hr
//修改当前记录字段值 GiS:Nq`$(
... DuI>z?bS
m_pSet- >Update(); //将修改结果存入数据库 ckdXla
m_pSet- >Requery(); _(:<l
YaY
---- 5 .撤消操作 6'45c1e
WO!'("
---- 如果用户选择了增加或者修改记录后希望放弃当前操作,可以在调用Update() 函数之前调用: iph}!3f
?'RB'o~
CRecordSet::Move(AFX_MOVE_REFRESH); P87Lo4Rd
---- 来撤消增加或修改模式,并恢复在增加或修改模式之前的当前记录。其中的参数AFX_MOVE_REFRESH 的值为零。 _:7:ixN[Ie
E(g$f.9
---- 6 .数据库连接的复用 FL E3LH
o8h`9_
---- 在CRecordSet 类中定义了一个成员变量m_pDatabase: $(+#$F<eo+
V[2}
CDatabase* m_pDatabase; 4=qZ Z>[t
---- 它是指向对象数据库类的指针。如果在CRecordSet 类对象调用Open() 函数之前,将一个已经打开的CDatabase 类对象指针传给m_pDatabase,就能共享相同的CDatabase 类对象。如: /X;/}fk
d3"QCl
CDatabase m_db; J>8kJCh9g
CRecordSet m_set1,m_set2; 9Yd"Y-
m_db.Open(_T("Super_ES"));//建立ODBC连接 /I &wh
m_set1.m_pDatabase=&m_db; @UQ421Z`
//m_set1复用m_db对象 $0XR<D
m_set2.m_pDatabse=&m_db; Q)Q1a;o
// m_set2复用m_db对象 sf"vi i,1A
---- 7 .SQL 语句的直接执行 t-Uo
#\Zr$?t|V
---- 虽然通过CRecordSet 类,我们可以完成大多数的查询操作,而且在CRecordSet::Open() 函数中也可以提供SQL 语句,但是有的时候我们还想进行一些其他操作,例如建立新表,删除表,建立新的字段等等,这时就需要使用到CDatabase 类的直接执行SQL 语句的机制。通过调用CDatabase::ExecuteSQL() 函数来完成SQL 语句的直接执行: eI,H
BlfadM;
BOOL CDB::ExecuteSQLAndReportFailure(const CString& strSQL) |8?e4yVd
{ Zygu/M6
TRY 6u>]-K5
{ K.Tob,5`
m_pdb- >ExecuteSQL(strSQL);//直接执行SQL语句 $:RR1.Tv
} :}z`4S@b
CATCH (CDBException,e) JFFluL=-
{ >Og| *g
CString strMsg; nzU;Bi^m
strMsg.LoadString(IDS_EXECUTE_SQL_FAILED); "h-ZwL
strMsg+=strSQL; pp@O6
return FALSE; '<{Jlz(u9
} yw1-4*$c
END_CATCH Mj`g84
return TRUE; 3,?LpdTS
} IG&twJR
---- 应当指出的是,由于不同DBMS 提供的数据操作语句不尽相同,直接执行SQL 语句可能会破坏软件的DBMS 无关性,因此在应用中应当慎用此类操作。 uHq;z{ 2GI
8]D0)
---- 8 .动态连接表 P^AI*tH"m
1gQ_76Yck
---- 表的动态连接可以利用在调用CRecordSet::Open() 函数时指定SQL 语句来实现。同一个记录集对象只能访问具有相同结构的表,否则查询结果将无法与变量相对应。 #I1q,fm
>t{-_4Yv?
void CDB::ChangeTable() JOH\K0=e
{ u|LDN*#DW
if (m_pSet- >IsOpen()) m_pSet- >Close(); 0Wj,=9q
switch (m_id) ]>B4
{ 8([ MR
case 0: c:aW"U
m_pSet- >Open(AFX_DB_USE_DEFAULT_TYPE, C8x9 Jrc
"SELECT * FROM SLOT0"); //连接表SLOT0 -Fq`#"
m_id=1; U"=Lzo.0
break; 8u%,5GV>Xr
case 1: yLPP6_59$
m_pSet- >Open(AFX_DB_USE_DEFAULT_TYPE, l <p(zLR
"SELECT * FROM SLOT1"); //连接表SLOT1 C1>zwU_zo
m_id=0; 05:?5M4};
break; _F8THYg (
} ST2:&xH(
} OG9 '[o`8
---- 9 .动态连接数据库 !yd]~t
5Q
(D:-p:q.
---- 由于与数据库的连接是通过CDatabase 类对象来实现的,所以我们可以通过赋与CRecordSet 类对象参数m_pDatabase 以连接不同数据库的CDatabase 对象指针,就可以动态连接数据库。 6j!idA!'
udXzsY9Ng
void CDB::ChangeConnect() D?=4'"@v
{ \SoT^PW
CDatabase* pdb=m_pSet- >m_pDatabase; e+V8I&%
pdb- >Close(); J/IRCjQ}
8L+A&^qx
switch (m_id) $01csj
{ NeJ->x,
case 0: a@J/[$5
if (!pdb- >Open(_T("Super_ES"))) *?\u5O(
//连接数据源Super_ES zU0SlRFu
{ W*%(J$E
AfxMessageBox("数据源Super_ES打开失败," ]&N>F8.L+
"请检查相应的ODBC连接", MB_OK|MB_ICONWARNING); TB-dV'w
exit(0); XhA tf@n
} I{h KN V
m_id=1; 0'
oXA'L-J
break; F]t=5
-O<
case 1: +u&[ j/
if (!pdb- >Open(_T("Motor"))) F-$!e?,H
//连接数据源Motor 9)t[YE:U3!
{ @]]&^ 7
AfxMessageBox("数据源Motor打开失败," S}a]Bt
"请检查相应的ODBC连接", MB_OK|MB_ICONWARNING); 8Ihl}aguW
exit(0); J.'%=q(Sb
} ANNVE},
m_id=0; 9ln=f=
break; Oxa8u e?
} l4dG=x}M]
} Oi zj|'
---- 四.总结 Q6wa-Y,
8d2\H*a9~
---- Visual C++ 中的ODBC 类库可以帮助程序员完成绝大多数的数据库操作。利用ODBC 技术使得程序员从具体的DBMS 中解脱出来,从而极大的减少了软件开发的工作量,缩短开发周期,提高了效率和软件的可靠性。本文总结的笔者从事软件开发的一些经验心得希望对从事ODBC 开发的工作者有所帮助。