|
一、ADO简介 & I+ V4 {9 ]6 b6 Y0 Q' @1 Q$ P
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 1 }; m3 Q* @" Y: M, M& h l( |
本文示例代码
7 v* V/ i- X, Q, {: o3 ?7 _5 M- o" i- f& h* e9 t8 J
二、基本流程 + E) n# R- o1 Y E$ f2 g4 k
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! 7 d4 o# I# u& C) }' x
(1)初始化COM库,引入ADO库定义文件
5 i/ e$ _2 ?0 ]3 G% n* d(2)用Connection对象连接数据库 ( N Q+ g# a* V3 g+ _2 F/ }
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 3 X: v7 l4 A, m( u" [& y3 M
(4)使用完毕后关闭连接释放对象。
1 z/ H/ U/ l+ q ?( E+ F, A! g. h
准备工作: 1 S# i* _7 b' r4 p% `* y: l, o
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
: T4 Q; \- U$ s$ R/ u/ K4 b下面我们将详细介绍上述步骤并给出相关代码。
7 V: f* e# }- @; ^, i# u- x! l$ z【1】COM库的初始化 ( _0 f2 \5 t1 V5 L
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
6 H8 U& {8 B% j4 T8 y: w
( N# p z6 b, [& ^, Z* d
9 Q: b1 Z. g o$ ?) w" u6 m+ C8 @& zBOOL CADOTest1App::InitInstance()
4 h9 V. [3 r2 i0 m3 C! v3 g4 E { ( O& m- Y" @) D, T2 l' \+ `3 B
AfxOleInit();
/ N: ~0 J* e/ Q9 }0 i0 H+ @ ......
) g* h+ z. g N g9 B; B( X: |! Q% _3 |# G: V' L
【2】用#import指令引入ADO类型库 2 {" a, V6 G" S. c% p
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
4 w4 q* O& }7 x6 G: M; Z7 J#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") 2 `' Y/ S3 O; c# [, }; ?
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。 / t4 V+ d' @* z% u& o5 t% s5 n
" ^- d% X: A* B$ _9 a2 ~
几点说明: ( C w7 h% [6 Z1 n& v# N
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 ( p7 f# @+ g0 p3 R3 k2 b$ H
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 0 K- Q% Q# F7 _, ?' w
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned " V/ p, V. k) S
# Z& i y: i. |, ]# ~- @: Y8 C9 N! k【3】创建Connection对象并连接数据库 & z; o, ?" f7 D# N
首先我们需要添加一个指向Connection对象的指针: , f$ u* ~! v4 b; K4 `& |+ R
_ConnectionPtr m_pConnection;
5 R$ N: s& l- _5 h4 C& y: v下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
+ N: X8 G' p- K; t1 V0 g' ~! W( N* Z! p0 T
9 M# v% ^; t* i" N* F6 R
BOOL CADOTest1Dlg::OnInitDialog()
& i6 {3 [2 x6 q* O# J3 f {
N! _ @# }7 W! h; Q CDialog::OnInitDialog(); / Q3 T! b) [( I# |
HRESULT hr;
0 o. {7 r$ R% a try
& ?; _8 _. `' O! S { ) W. B+ a# h5 k6 ^8 T/ a" w
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
! Q: C: h6 h$ L; v. g6 X if(SUCCEEDED(hr))
) S1 X7 ?/ a6 d K! ?0 M1 P; v {
5 x$ w' c- s" c0 ~ hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
' h* ~- n0 ?" a ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
, v: n5 o7 U' v8 G# s: ~) B } & P* Z" v! `1 o* \
catch(_com_error e)///捕捉异常
6 R4 e v6 {4 R4 ~- }5 \ { ; M) ^9 \. ]7 s+ ~5 q
CString errormessage; / Y P& y) C. k! H: {$ E" w
errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage());
% E: d0 Z: S$ v# @2 u( W' { AfxMessageBox(errormessage);///显示错误信息 ( @1 K$ J9 S8 F$ X q
}
( V# h9 ?% g$ f; s+ t( }
6 d' Y3 |8 p0 ^; j& S. J在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
! y! J6 b i' i! b3 WHRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
; B) j) g( E) F) l' kConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
3 ` B. b- j) Y wOptions可以是如下几个常量:
( G* O) @! \0 F& g* F1 TadModeUnknown:缺省。当前的许可权未设置
& l" g2 A& p" A- Y0 Z# CadModeRead:只读 0 Y+ g0 I- g ]. z7 t, H0 }4 w
adModeWrite:只写
" N& v3 ?! j, SadModeReadWrite:可以读写 & ^ _3 [, n: E+ s% t& N! h- F
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
# v. m. m) z0 y: ]0 @0 dadModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
7 U F5 G- S- Y" g0 d, B, j$ q3 jadModeShareExclusive:阻止其它Connection对象打开连接
8 P% D4 S6 z+ c) Y, Y; A6 |adModeShareDenyNone:允许其它程序或对象以任何权限建立连接
! J) I' N5 N' s i' g1 F; P5 `) \. m" A: O! i6 u
我们给出一些常用的连接方式供大家参考: 7 G9 Z4 _# }2 ~# |( I* V' ]
(1)通过JET数据库引擎对ACCESS2000数据库的连接 9 d" h* ^& r0 K$ `% q: Y
3 M2 k! _# q4 T! v$ c: ym_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); # o! _ X7 Z" L; A N: w" }$ Q/ ?
8 n9 U! k% m6 ` v9 y
(2)通过DSN数据源对任何支持ODBC的数据库进行连接: }$ R: F. R# J. p; b
m_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); ; S/ K' v2 \4 s# U
M9 H6 D7 b; ^, b
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
8 B" T0 g* ?) b3 E% C# ~' `
6 v3 U8 z) Y8 p$ t5 |0 }其中Server是SQL服务器的名称,DATABASE是库的名称
- Y' s% c0 m4 y' w7 x) h9 @
2 [$ K' {/ A( _& B# y( k( Z* wConnection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State 9 r6 L* w7 Z! d
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒
& c* r" r8 D* B" p6 t' [; M$ }, r( Vm_pConnection->Open("Data Source=adotest;","","",adModeUnknown); 1 M& x3 W4 B- C
* \ z6 @; A1 v) e, u
5 F/ \" O, M1 iState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: ( J4 U+ M& l4 }7 c! f0 g* @" _
if(m_pConnection->State)
7 ]& _, U4 E1 Q" i) z' P: \" p m_pConnection->Close(); ///如果已经打开了连接则关闭它 1 M) R% ~5 G( z V- |7 s: e1 p
9 q* A3 j! k5 W. V; t
6 h1 \$ C' m1 ^" R) ]" h; Q) l* \
【4】执行SQL命令并取得结果记录集
) L! B. t. i& b7 f为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
: \8 O1 \* T! d* _并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
; H9 g- w: Z- @5 J4 J: X1 @SQL命令的执行可以采用多种形式,下面我们一进行阐述。
, ~3 v; j( Q9 S8 x' F+ `& Z) ^2 ^+ B s
(1)利用Connection对象的Execute方法执行SQL命令
8 l& Y6 V) @) {( ]1 SExecute方法的原型如下所示: * e; |4 ]% L" |3 k. z
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: + H7 X2 H; M) n3 \2 Q$ L q# n
adCmdText:表明CommandText是文本命令
- q2 z( `5 W4 oadCmdTable:表明CommandText是一个表名
; l8 t& n2 j M; W; GadCmdProc:表明CommandText是一个存储过程
5 }/ U6 J6 R0 X3 X" aadCmdUnknown:未知
# i- F' `, M; k1 Z
t+ P! g% Y* K: d) I5 ?Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
# l- F1 h1 Z+ b$ r ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
8 q) e9 u' J9 c! c! J, `/ q m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);
0 g* J2 r- R- ~; m/ u ///往表格里面添加记录
0 Q1 N- H6 _' Z' f m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
7 i# Q7 i, A: K; a: a5 m& ?: S" j ///将所有记录old字段的值加一
+ c6 M+ H; ]3 R8 e7 f& r8 Z+ ^2 J m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
. ?# {5 E" F1 G( U2 |# U ///执行SQL统计命令得到包含记录条数的记录集
( q/ X# _, g+ j/ v: e- J m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
* {* E- c" S, u. _2 c9 U5 f% ]/ Z _variant_t vIndex = (long)0;
3 b8 r" l% Y' `% A6 Q5 R* o! e7 k7 ] _variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
- Q! b" l8 q, Q4 e) G; K% j: @ m_pRecordset->Close();///关闭记录集
# N# ?; u% D3 ]$ f( \ CString message;
# y+ e& e2 l. F7 l2 Y0 W% Z message.Format("共有%d条记录",vCount.lVal); 3 f* \" V$ A* H
AfxMessageBox(message);///显示当前记录条数 4 |( a7 ~7 E1 y
5 Y( g1 j. ^7 B! q- V$ n' @0 V
; ^( P B* |. W* Q, [
(2)利用Command对象来执行SQL命令
! i1 I' ~; U- G _CommandPtr m_pCommand; 1 M. W5 t; b* H2 r9 D
m_pCommand.CreateInstance("ADODB.Command"); ; u6 U7 A- [" j) L" y
_variant_t vNULL; 5 L9 N1 S* }/ Q7 Q! N
vNULL.vt = VT_ERROR; 9 x4 C6 X U+ W5 J* P& A" {( ]2 j
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
1 J+ F( U- O- Z5 [& X4 c7 @/ M. m m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 - M9 e6 {, q7 \! i& w) ]
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
* T- h$ K" X8 i! U, X7 a0 a% ~ m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
?. @& \( v8 u# N, p! |$ L& y" o+ C9 Y3 q0 R& S
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
& q) w6 ?9 k; P8 B
4 D: U$ E" L2 k( L/ U5 q
- L; i1 v( _( m6 @ Q4 M(3)直接用Recordset对象进行查询取得记录集 * |* c$ ~% x5 K7 R% D, w; m; ?8 Y
例如 ) v. @) m% |1 x9 V' {# A* f% d
' u* h' D# ]7 N$ E+ g5 B
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); 4 e- d% ~5 H$ @0 R; c
) u5 u7 r) `' R" O
Open方法的原型是这样的: " h$ q/ s" T+ X! }: r; F
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) % j/ H% M# s5 Q
其中:
; h0 `2 O; h- s7 Z' k①Source是数据查询字符串 - i+ ^2 ~- n2 [; o8 F/ u. Q: r! P
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
k$ O: C$ h' Z6 k4 o1 D1 U③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
& p S: Q W d9 G, y) ienum CursorTypeEnum
y% M0 |7 {! b5 b- k& h{ ( T, [ ~% Q7 u3 k* o
adOpenUnspecified = -1,///不作特别指定
; I# p V* S: N# ]adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 , q; {% b7 i X" O0 N) N1 L- v9 f3 S
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
2 |, u& |% ]) m5 ^adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
' _# l) @1 e5 ]4 KadOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
, E% ~6 M- c) P$ k2 n7 @6 z}; * W0 O6 u/ j- Q) Z
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: . G* j6 t7 \# c U3 C
enum LockTypeEnum ( k* j" _+ v: W( r$ L/ a% t5 L j
{
" y! F3 g# B) @) LadLockUnspecified = -1,///未指定 2 b, w$ Z2 L5 l0 C2 P* _1 j
adLockReadOnly = 1,///只读记录集
8 ~- {5 L# t0 s* ZadLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 ; X- t* L* Q& K5 d6 B
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作 ( ~/ s4 i* Q' B4 f# w
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
$ O; [& W( t5 A8 a' F};
; g9 s- U! J+ @: B# ]5 ~" G⑤Options请参考本文中对Connection对象的Execute方法的介绍
7 m3 N2 l0 e% p0 H4 V
9 k$ f6 L' ^0 |9 S; o
0 H/ |+ V( y. K. q2 M0 ?" f【5】记录集的遍历、更新 : k0 j# v8 G6 f
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
1 J# }, R, f: Y% f+ ^以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
% g: m" ~& q# A# \: G. }6 Y3 q. F8 ?: H5 W/ Q" F
/ w4 d: z6 C& T9 L/ L% k Z
_variant_t vUsername,vBirthday,vID,vOld;
7 }9 f, p% Q. r- `6 i p- ^_RecordsetPtr m_pRecordset; ' v' g4 g: W; _* ~' Y
m_pRecordset.CreateInstance("ADODB.Recordset"); % X2 w% a4 T" {5 h- o
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
7 n& E( @0 N/ Q& \4 lwhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
% M9 C; H$ P7 x* \" r3 t{
8 I6 U. Y: h2 M8 Y: L( C! RvID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
0 L+ ~4 C+ v/ ]$ \, T1 ^1 {vUsername = m_pRecordset->GetCollect("username");///取得username字段的值 7 V: [& g5 t9 Z8 [; t1 p
vOld = m_pRecordset->GetCollect("old");
{9 W) O! E+ VvBirthday = m_pRecordset->GetCollect("birthday");
3 b0 t. I# ], ^5 B; x///在DEBUG方式下的OUTPUT窗口输出记录集中的记录 ! {# |7 `, M' B9 `
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
) S+ Q# Z: C( n: v( p% j% w5 p TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
" d& r2 f$ M- Q6 d$ I B ]6 wm_pRecordset->MoveNext();///移到下一条记录
. h- `( A/ |# A- y/ T" ?, u' F}
: |6 O1 l; y. V9 v( Y$ pm_pRecordset->MoveFirst();///移到首条记录 , Q. g4 B1 v2 G
m_pRecordset->Delete(adAffectCurrent);///删除当前记录
1 \4 O0 g' |$ x7 }" e) K///添加三条新记录并赋值
& G7 s! M; y' ?; bfor(int i=0;i<3;i++)
+ Z. b3 J$ |* b{
2 \- E% a* _" l. E( R2 U) a2 t: X% Im_pRecordset->AddNew();///添加新记录
- _) m( T( J- P" um_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); ( ^/ t" E- g" @* A# ~
m_pRecordset->PutCollect("username",_variant_t("叶利钦")); 1 r) i4 F8 J: `; H$ V
m_pRecordset->PutCollect("old",_variant_t((long)71));
, X/ l$ Q( c$ r9 N8 I0 d* X6 fm_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
8 e$ j* \# j$ B6 h. ~3 L} % R# \# H. E7 x4 u
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 , a1 p- q( z# p/ u1 w
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 7 i1 Z# X4 o- w% q
m_pRecordset->Update();///保存到库中 ( I- g C% l- t
- U6 x, |1 ^& X- N ~【6】关闭记录集与连接 & a6 x- S' X- a! ]0 N
记录集或连接都可以用Close方法来关闭 6 {6 b; l/ l8 J3 b. Y
m_pRecordset->Close();///关闭记录集
! o* Z" p3 a( U* R! @) `* [ m_pConnection->Close();///关闭连接
( K# X% j0 F7 p% q% c7 ?& S8 q- v! i! k! y* N5 m- ~! D3 Z! p: o& B4 K; u
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
! H, B1 X$ L9 N点这里下载示例代码
+ a5 ?6 k+ n! x, u5 ]" t) O
: L! o% u, Y6 z) L后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|