|
|
一、ADO简介 ( O( o& X: M: l) m+ F) [
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
+ E8 ]" Z0 ?; I# r本文示例代码
/ E, \" W9 A! O! P, D- R
2 M; o2 Q; K7 R y/ r3 ~二、基本流程
( g/ L. |' H' A, I. [9 k万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! 3 _$ b4 n- W1 Y& ? L
(1)初始化COM库,引入ADO库定义文件
/ \ P8 f, w2 [% B(2)用Connection对象连接数据库 * z- C- f9 j0 U- C- u% ?) c" m
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 5 a5 m9 D( |( i' c% X- E2 g" @
(4)使用完毕后关闭连接释放对象。 9 [3 ~( s, |/ x' c2 m# Y
! j; |/ C n P3 w1 F) `
准备工作:
6 i4 k: s! @# U5 e( h3 ~7 x为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
2 _$ n4 C+ n/ c下面我们将详细介绍上述步骤并给出相关代码。 5 s# D4 }8 y! k" |
【1】COM库的初始化
( a2 `& e d0 v8 f' U我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
- O$ i: ~- P$ a9 |0 y2 U: |$ B* m4 o9 q- V" T$ y7 _# X( ?
3 _& r1 C, p, \& }; z# D$ M1 u$ C
BOOL CADOTest1App::InitInstance()
3 X% Z2 F7 m8 H* m" \4 c {
. k6 |" h3 d# I AfxOleInit(); & c! X& v, M+ b# ^5 e/ ?
...... ) v3 e7 Z0 N9 s j& f2 M$ b
/ l' a0 ^ x1 O9 H+ o【2】用#import指令引入ADO类型库
2 A& S% q3 V( t) t9 A1 h我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) ; Z- h) N7 F( O( l
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") Y- T( ^" }# f: x3 q- D
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。 , o/ M9 d6 k" G- G
2 ^# h8 m; q4 L1 C# M5 o
几点说明: 6 f! F+ a, F! @0 ]- C- c9 @. N
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 ( l5 b8 |' `0 J1 X1 s
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 2 }5 z2 N) r' S) V9 @! A
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned
2 g" x+ G# V; Z$ y% y2 W( B. b+ [$ w/ j4 D9 A$ B* n8 | r1 v
【3】创建Connection对象并连接数据库
% y/ k: R/ `0 ^( Q首先我们需要添加一个指向Connection对象的指针: 3 N4 o: I- u9 V" }% @% D: k0 ]8 _
_ConnectionPtr m_pConnection; : A. |' c3 a1 Y! I4 K
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 6 B& x/ W* S: e: M2 D9 T! D5 } {
" i) m# s2 m; ]' ^2 s0 ]5 E- H
4 b7 T7 |/ K0 o0 H7 o2 t
BOOL CADOTest1Dlg::OnInitDialog()
& N1 I' B# X7 Q. D4 u$ Y! }+ u, X { 3 i4 y3 _/ _; V* L3 \% T: H
CDialog::OnInitDialog(); & v. |' C( O' a; ^9 v. f
HRESULT hr; ; D u& L$ B" k. N [* P. ]. d
try 3 i1 ^4 y' X" ]% q
{
1 q% U8 p+ J) q, m hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 / Q& m( N3 u& Q. Y8 ^/ ]6 z; @
if(SUCCEEDED(hr)) M w4 f9 [- U3 W2 x( C, i% b! J
{ $ K6 q/ H9 w( I7 L9 R
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 * W* W/ Z3 S- a a( k8 D
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
5 j( {4 J% z7 Q }
% s: E6 E" R9 J( w catch(_com_error e)///捕捉异常 : z7 a5 m1 q. V( \+ a! D/ m
{ 5 I- u( k$ g1 f) ?
CString errormessage;
4 ~1 Q" F6 N1 u errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); 6 [0 x+ b$ y( r+ [' x: u- t
AfxMessageBox(errormessage);///显示错误信息 2 z9 I) C% g5 m! A( D0 Y( [3 m
} ' [$ `* B4 y6 Z' H# s2 I! p
7 Z, D: u8 [# i7 l在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 6 Z$ |: L' R4 K/ f5 i% H
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
( Y& b6 b0 k0 U/ v1 k* tConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, 0 V; S: R' G; f G
Options可以是如下几个常量: " W P0 j/ F* F1 Y2 z
adModeUnknown:缺省。当前的许可权未设置 ! T( K! m/ `& o' G
adModeRead:只读
! c( Z4 q6 O+ ~1 hadModeWrite:只写 7 W H: }$ M- L2 @ V" N# k$ _
adModeReadWrite:可以读写 6 d d4 Z) q# J% i3 L
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接 5 G- y- z# h( f: S m( k6 p! p+ h
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
e- ]! t3 e8 h- _! {! o& ?+ r# R4 [adModeShareExclusive:阻止其它Connection对象打开连接 % q: E' M1 p1 [- c: u& v
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接
' O2 t* u: {8 K9 J
) e Y: H6 e9 Q2 B/ g( \3 G$ `我们给出一些常用的连接方式供大家参考: ) d% Y8 b7 ?8 _0 y% c/ A, u7 h
(1)通过JET数据库引擎对ACCESS2000数据库的连接
' N3 r) x& d( p8 i3 |2 [9 l
3 U8 B$ b0 Z9 c2 Sm_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); 4 y7 L9 [* j( B
( C; B' _% L& }2 |2 B' Z
(2)通过DSN数据源对任何支持ODBC的数据库进行连接: ) f7 w' W8 d9 D6 \5 A$ g, u
m_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); ! @7 ?. V' ?# R: ~
% Y% Z2 v* f* `
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
- ]8 V9 R1 X0 Y3 t) n7 I, S7 L% z$ F/ O& _$ h8 ?1 D8 O
其中Server是SQL服务器的名称,DATABASE是库的名称
. M6 {; M9 J; W+ D! @' Y( D; F5 N+ K5 p
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State & a, c( v% i9 Z
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 , L' t3 y, c! [, ^0 ~
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown); % J) E" @, x0 C% D1 X
, M% t* b& j' j1 g. f
. |6 {$ d! e- K- P# Z8 VState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
7 V# {. L. y9 s Q `; F2 P/ r7 C* Yif(m_pConnection->State) ) m- T7 c0 G5 P- B1 ]
m_pConnection->Close(); ///如果已经打开了连接则关闭它
! V3 h# ~. Q) j4 X5 F: Q- A5 u/ }) `( t1 B" s5 w. m( e) {- K, x! O; U
+ X# T$ K: L8 X8 g% a$ x; s
【4】执行SQL命令并取得结果记录集 3 O; b! q+ |9 X, ]
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
! d# B l4 c5 L6 x! S9 X! p9 P并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); & E0 _" x: f2 ^% M% T
SQL命令的执行可以采用多种形式,下面我们一进行阐述。
# d; L8 T- y& L4 t
- z( k0 {, ?1 P9 l' J) H5 b7 H! E(1)利用Connection对象的Execute方法执行SQL命令 8 F' L# K" c N5 y) ]
Execute方法的原型如下所示:
+ q& |# w# O# h& S2 m" n# l_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
% G; r7 C2 R/ N# R2 EadCmdText:表明CommandText是文本命令 " j4 T+ A1 a/ ?
adCmdTable:表明CommandText是一个表名 : n/ @; z G: J3 T ]. C8 a
adCmdProc:表明CommandText是一个存储过程 $ ` H+ T4 \8 p5 t, W
adCmdUnknown:未知 4 ]: m* d7 j `! \$ i5 r) ]
7 \. ~; b# l/ X" O/ |! U7 E' \Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; ! W! a6 y7 O- V8 }& G
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday : B5 q; L, L l2 c, ~! p }
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); ! g8 L. x4 F$ T9 o$ [
///往表格里面添加记录 $ W4 f+ R0 M$ D8 j; x, _
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
. w! C! x! b: l$ L$ w9 v ///将所有记录old字段的值加一 # ^! g2 T+ B. O5 U! h! x
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
4 s2 A2 ^ D; w- j' {6 M8 [1 Y ///执行SQL统计命令得到包含记录条数的记录集
# @8 T" t- y, f9 _7 d- L( N) W m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
; i9 h* v1 U6 g8 V! P _variant_t vIndex = (long)0;
% z a" L @5 X/ f _variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 % ?4 _) n- q P7 l7 X" I
m_pRecordset->Close();///关闭记录集
w ~- E% t# c. I CString message;
8 ]: E `- v ~8 U' d( q message.Format("共有%d条记录",vCount.lVal);
; u3 U9 } z" e* Q AfxMessageBox(message);///显示当前记录条数 : o2 d) b, q9 X/ Q
9 M' {6 [: \9 {+ M: n* @$ R1 D& f# l! ~3 v( N/ A' x
(2)利用Command对象来执行SQL命令
$ |" V3 o& X! E% w _CommandPtr m_pCommand; 7 ? ` P! q, @- k
m_pCommand.CreateInstance("ADODB.Command"); / N* Y: a" s$ q2 w
_variant_t vNULL;
1 s# d6 L# E. j% a2 A4 l vNULL.vt = VT_ERROR; ( {/ _! ?' |! O' @2 q/ S
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 & n+ u9 b) g& [( H- ^
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
. [+ J5 B9 D K( G m_pCommand->CommandText = "SELECT * FROM users";///命令字串 % d( A( Y1 ~* H W' V' q' v4 i
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
h+ r' B; e& q( ^) w; S- j/ n
2 s2 o# d! z3 W4 L9 Y; j0 ~在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。 2 |! b" z1 p0 Q
/ @1 p- r+ Q$ E4 R4 i- \" F! M5 P+ a) y7 q- I
(3)直接用Recordset对象进行查询取得记录集
2 N5 H4 S9 M4 g; q例如
$ T/ x0 e |1 O3 W4 P, Y! Z: z# E. k5 A- \+ {$ f) c# D
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
9 v+ C( b U4 n) W9 z2 B% p/ c6 x; U( k4 @9 S
Open方法的原型是这样的: ; k8 u4 Z/ Q" O' T; R3 P
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) $ v( V" G) M: W6 q! A1 U) }! i
其中: q3 _0 c7 ^' k% N
①Source是数据查询字符串
' B( r( W) E6 D% x5 w②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象) 2 m& K& V% K6 J6 \- U% _
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
1 H! U& N3 J' L3 V& yenum CursorTypeEnum
0 o0 u5 }' ?* |& b0 W! \+ A! V. J{
4 ~' A1 w2 J+ z* C {+ h2 e! U# {+ CadOpenUnspecified = -1,///不作特别指定
/ D& H5 Z& ]& i L2 ?: badOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
* M U1 o" e- u) d. RadOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。 % {( @" l. b" P
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。 / z& d& k% ~ u; q# K
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 + O a( I' {- M4 q8 ?
};
9 J8 b; C. k+ ?, ?) p④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
. I+ N. j5 c3 S3 l9 W( |enum LockTypeEnum & e8 ]1 Y5 m' j% G* R9 i
{ % V! g5 X5 H! \/ q0 _4 C7 y2 }
adLockUnspecified = -1,///未指定
M% o0 ]7 J2 ^; _' ^) yadLockReadOnly = 1,///只读记录集
" Q$ W; E0 H% \2 F! t; x2 ladLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
5 W$ m2 i+ [1 P9 _+ A' ?adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
% y6 q+ s$ Y& i. m; LadLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
- m4 ?" S6 ~8 Q+ V4 L Z}; 3 w% i8 F& s4 w7 y5 i2 S# {) x9 b5 h
⑤Options请参考本文中对Connection对象的Execute方法的介绍
/ o3 b! v2 e) w5 n( g
+ [; A" |# I, W* Z2 F) T" o3 Y7 C9 z+ ^1 S! a& {! ]) F3 ~
【5】记录集的遍历、更新 1 c8 m$ Y9 o- j! Z
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday u# l5 h0 L& ^' `7 l8 K
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
* Y" z9 h6 }, b: y% O, ?1 Q Q
! a5 ]/ q2 u# f$ e- M5 m/ U2 v# N# t: {0 j7 ^& @; T4 n
_variant_t vUsername,vBirthday,vID,vOld; 1 A0 y* c" F. w* l
_RecordsetPtr m_pRecordset; 2 b7 [8 [) l& o* Y: `' H6 o7 V
m_pRecordset.CreateInstance("ADODB.Recordset"); ( u+ l. L/ s' K+ d3 K
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); 3 u6 Y5 a5 u: N1 U/ R, I
while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? + G8 P* ` x1 b0 |
{ 9 J3 m7 y3 N3 x) `! e
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行 5 o+ S% }) c; L, @' c
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值 ' u0 i% ~9 U0 t/ Z% R2 v* {1 m
vOld = m_pRecordset->GetCollect("old"); h: u/ K1 c3 B
vBirthday = m_pRecordset->GetCollect("birthday");
/ Y. z4 ] X# e+ g9 V///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
6 i( V8 L+ Z# R4 Sif(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
, a& Z1 w- F5 Z) G/ k TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); % Q6 B; m) G6 N O2 U3 I3 Y, Q
m_pRecordset->MoveNext();///移到下一条记录
; V9 w7 a. d" D9 L, e( [% Z} ) h: v1 p1 ]9 u+ [
m_pRecordset->MoveFirst();///移到首条记录 2 r+ `" A t7 M ?
m_pRecordset->Delete(adAffectCurrent);///删除当前记录 5 ^4 u$ {9 K2 U0 l
///添加三条新记录并赋值
# R: O. C6 N+ e+ S3 b+ y8 [5 jfor(int i=0;i<3;i++) ! u7 r6 ^0 C4 x
{
, Z6 }' g; z4 B8 Em_pRecordset->AddNew();///添加新记录 ( h6 x7 C) w$ T; h: y U
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); # Q) ?3 J. @" S+ L* |, _
m_pRecordset->PutCollect("username",_variant_t("叶利钦"));
d9 J% N9 i5 z4 z8 t3 om_pRecordset->PutCollect("old",_variant_t((long)71));
3 J- n$ X; h' |2 @6 q& O9 l) L" o) ym_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
' S5 |, ^; v, C}
: P( V& v+ j) \3 G, C3 X P+ dm_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 2 ]! X; r$ @6 E! t
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 ( j J$ B8 n+ \, T$ L+ S
m_pRecordset->Update();///保存到库中 ) _; q/ w( a; R& E/ O; j
8 |' Q- d( o+ @3 P
【6】关闭记录集与连接 ( ?6 ?( @* a% c/ Z8 I2 j
记录集或连接都可以用Close方法来关闭
' l# G! I9 |/ ^# e m_pRecordset->Close();///关闭记录集 4 }7 G: c* e9 c5 c' m& D% r* y
m_pConnection->Close();///关闭连接 % S: L" X) {) |! e0 s* z
6 }6 s5 x' u Z( y; k3 K/ z
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
. U5 r. d. L5 N$ V$ H点这里下载示例代码
& e* m* z/ Q% |, b7 l7 Y# V& q+ G2 M4 b6 I; u
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|