|
|
一、ADO简介 7 S; b X9 y4 M8 I& d1 X
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 ! B* P6 b4 @" ^
本文示例代码
5 D) n/ ~& E# w; b# \0 b! G; ]# ]# u+ }
二、基本流程 9 H+ Q G5 |" P `: r
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
# V% o+ {% r: t n% l8 N; O(1)初始化COM库,引入ADO库定义文件
1 q. m E: v7 w6 S0 N& B(2)用Connection对象连接数据库 # V5 F! W0 M T# T9 ?
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 1 O3 M2 V6 W% X5 @9 m
(4)使用完毕后关闭连接释放对象。
1 r v- G' r, {/ U& C- \: w: I$ e) _; w3 w4 f
准备工作: ; U" P0 X6 ~3 |1 A7 u2 H: n/ E
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
+ l1 Z4 `% f5 H4 {6 a下面我们将详细介绍上述步骤并给出相关代码。
2 z2 h* w7 X6 D【1】COM库的初始化
' D8 j- {! ~; K( m% n9 C我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
- V# J6 l6 f5 h+ [& i; y8 x; o( W. j; G8 e
- ?1 j3 o l+ S8 e sBOOL CADOTest1App::InitInstance() 5 M7 l6 |1 j1 K' E' U* @- G( w
{
6 ]1 R* Z, K& N' ^/ Y( @1 Y AfxOleInit();
2 @! R0 T/ W% n; X z) N' ` ...... : x6 j0 N0 R. D7 E% h& t" v
% e' q. W- O, C# m3 j【2】用#import指令引入ADO类型库
6 l9 W6 p5 S4 ~( V& a! h我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) . U' W6 {7 r+ _: a, t
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") V$ Z9 l" }" ^3 m
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
. s0 \$ w. h- T
6 g. |/ | Z7 Q' i几点说明: $ e0 j9 o9 }$ P/ r
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
% n3 s, Y. F( C' j* l6 O/ s(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。
0 x# @' [& z; x- fmsado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned $ D1 y+ G9 P7 J6 G
# X& T' k' P0 T/ V$ D6 Q" Z5 x【3】创建Connection对象并连接数据库
5 k5 v4 L$ g4 z6 G5 O首先我们需要添加一个指向Connection对象的指针: - Z+ A8 `+ o0 C/ g0 Z0 g. F$ F- g
_ConnectionPtr m_pConnection;
# z$ [9 R8 d; n* E, f下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 1 ^3 q& c! D2 i; h* a
I9 ^% l2 M4 `7 @! U
# G9 O0 F, i# l6 ZBOOL CADOTest1Dlg::OnInitDialog() $ l* [0 J4 A! v+ U# |
{ " T) { W3 q) B7 G% @1 j
CDialog::OnInitDialog(); 8 B- S8 j' t3 N/ c
HRESULT hr; " x" b5 |$ W5 w4 E S
try
6 n( V! C5 X/ t, e+ |' ? {
; F. \. f! g, D- X" Z3 `) n hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 2 }; q( i1 v$ n. O( \' E( L
if(SUCCEEDED(hr)) ' @$ l8 i2 `& c5 L3 V, P
{
) v) e0 u5 [6 r k$ [" K5 u hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 " U E+ Q: R" x! O2 S3 v. B
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
+ T+ W9 S+ A4 D: d# J5 ] }
. `% x L" D, M: ~ catch(_com_error e)///捕捉异常 ' z/ A( n% ^" I( A% Q$ p, x
{
, Z6 I0 h3 K, E5 u CString errormessage;
' [! d( f2 i1 W* O! t$ B errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); 4 n `& y( B$ {$ h/ n( J
AfxMessageBox(errormessage);///显示错误信息 - c( {& Q& {2 L9 L" J) v
}
* Q% }9 N: U( _( `" |5 H _* z. b9 x# m% P
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 ( x# \8 I; ]! Z% y5 J- v
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) ) O7 y) c( N2 Q }: ?# D
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
, u2 m* M. @) j1 [+ N. YOptions可以是如下几个常量:
8 e. @" Q; q- q6 GadModeUnknown:缺省。当前的许可权未设置
, n& |, L9 U) [% d; Z# ~5 a% IadModeRead:只读 7 t$ P* e- K( c/ p' E8 _9 W+ K
adModeWrite:只写
. r+ l0 L7 _* K* U8 F' ]; \adModeReadWrite:可以读写 0 ]/ Q { E3 k7 y# ^
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
! W) q$ O2 o4 k% A9 t: g) madModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 / @% b. D+ t! p7 k. R% [9 ~
adModeShareExclusive:阻止其它Connection对象打开连接
( [0 G8 D0 K( {- F7 C% EadModeShareDenyNone:允许其它程序或对象以任何权限建立连接 ! _( p3 @7 N- A$ S
( f( F5 k# Q/ T) i' u6 _* [我们给出一些常用的连接方式供大家参考: " y3 @6 d3 `2 j P; |" {
(1)通过JET数据库引擎对ACCESS2000数据库的连接
" a3 r9 X7 e8 r3 Q0 r: O
h! b7 m8 N5 \# b' p/ L8 ~+ ym_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
; `) o, f* F8 g4 Y2 b, ]) f
% H9 U6 S, }: O2 Z7 v(2)通过DSN数据源对任何支持ODBC的数据库进行连接: - a" H. R3 G0 _, i6 X6 m: P7 K$ H
m_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown);
" _1 t9 j7 D9 A0 t5 g9 U9 v5 [6 `* I* D) e; S) I
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
3 x" V0 s6 A; _5 L
" F, z; {5 r3 G l2 x其中Server是SQL服务器的名称,DATABASE是库的名称
! `; X! i3 m$ H m: ?. v, e+ A8 A
! s' j0 _- g# z: |7 A; ]Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State
6 R" M. @$ C$ Z, j* p B+ @ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒
9 W0 K! y' d# y1 om_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
P# h. S$ C0 ?0 H$ I' N5 ]
1 V! y5 h: T- P" `" z' } u* D5 [7 g( W/ d$ |4 i1 T: E
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
4 G3 D7 V" S- c+ ^. T6 Sif(m_pConnection->State)
" E. J. J8 V. Q m_pConnection->Close(); ///如果已经打开了连接则关闭它
8 k3 ]4 t. @0 ?" H' }) V. U# b; E" D# D9 _4 K; [7 j( N
1 N, }' i9 w3 ~* t- x8 U- T* [ c2 a【4】执行SQL命令并取得结果记录集
* W E0 l6 A5 Q5 ?' @为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset; 3 M6 {5 L; E- `
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); 7 \+ ]) N2 a7 x1 O" P; W
SQL命令的执行可以采用多种形式,下面我们一进行阐述。 % M$ K" v2 ^1 p& x* H7 f
% t* a ?* L3 @8 s# c/ m* Z(1)利用Connection对象的Execute方法执行SQL命令
5 i+ r' u0 x0 S, XExecute方法的原型如下所示:
; Z; @, N6 s8 S1 `_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
' z$ S# c8 q+ K; D/ v. z ]4 T6 z$ @' R4 AadCmdText:表明CommandText是文本命令
, z, [0 e( p5 F& y7 P0 X; dadCmdTable:表明CommandText是一个表名 9 R, J; W" V, e! ]$ C$ v' ^
adCmdProc:表明CommandText是一个存储过程
1 \+ ]: i8 X; KadCmdUnknown:未知 3 }% U, t& j1 D( y" r
4 K6 @( f6 H0 jExecute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; & h/ Z& p: q# _# T3 k0 T' b' F" T
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday 2 ^+ i3 G3 E$ W `8 V1 e
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);
+ x1 f: ^+ q7 i ///往表格里面添加记录
$ G1 G6 `( _- G0 N* o3 } m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
" y) R/ h. W4 t# B8 H ///将所有记录old字段的值加一
0 T' C; i( h! J. L0 Z8 u( X+ |; Z m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
$ V- H" ~6 W4 ?0 @ ///执行SQL统计命令得到包含记录条数的记录集 " n/ E3 A9 q0 K# g
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
0 Z" Q& l4 \, q+ T8 B5 b _variant_t vIndex = (long)0; ; q) i7 M6 C' y, |* }' z, a
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
8 e, F8 o* u: G1 {. A m_pRecordset->Close();///关闭记录集 % `; z! v# m; W- i* O$ `
CString message; 5 r2 ^" w [. z
message.Format("共有%d条记录",vCount.lVal); ; x3 q- Y) m% `8 f
AfxMessageBox(message);///显示当前记录条数 . f- Q3 [% R/ o6 u g$ t
7 |* U1 N" }5 o3 F
9 C5 @- t' Y1 o4 F4 V+ ~' m
(2)利用Command对象来执行SQL命令
! Y* X" u y+ i4 w) q _CommandPtr m_pCommand; . t/ K& m6 z( w1 _& B6 ~
m_pCommand.CreateInstance("ADODB.Command");
( }2 k3 @5 a |0 w0 e6 s _variant_t vNULL;
) s \+ d7 I8 C vNULL.vt = VT_ERROR;
# ?/ Z! L+ V( t1 J! }! ^! _ vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 9 P/ M& r a, @ ], p
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
, h* U' H: @ N% P, f2 \! Q m_pCommand->CommandText = "SELECT * FROM users";///命令字串
, k2 A3 S1 K L: x2 n% u m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
" B( B# C; [4 {1 Y* E8 W$ \
1 |5 U+ y8 h0 S! c在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。 9 f# W8 H- a8 {$ x' l/ c
8 x8 V/ |4 h9 w0 I3 @
5 K8 @" N4 c3 d5 A. A* z9 ]
(3)直接用Recordset对象进行查询取得记录集 & _! z2 K$ x! E- a/ J
例如 % Y4 B7 `9 \1 E
8 B2 Q/ [) {# X
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
9 A$ Q0 w: W0 \5 C' B* u1 [2 `) A7 H y# k0 o# j
Open方法的原型是这样的:
6 w5 R4 q( b+ L: }HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) + M( q( r- x% T6 W: j
其中: ) V4 K2 [9 ?9 |8 e3 N. a
①Source是数据查询字符串 $ _2 l* T6 u+ O# G9 I
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象) 5 F% R. r; G }
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: # U- B+ y9 r" W, C; {3 @
enum CursorTypeEnum
! C7 h& @% f' E9 O; D3 ]{ 7 q3 D5 Y- J: G6 Y% Z6 M$ O
adOpenUnspecified = -1,///不作特别指定 # C+ D1 j# M# O7 M3 C; q0 [7 E( x! o
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 6 e: c; [6 O! w! A7 f A$ `8 N$ x" w
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
7 T+ a) I6 I/ d9 _8 y. KadOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
' }8 f' Q4 m; I1 z, l |adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
/ H3 r4 \) G5 l* a6 r. w}; % V8 i: G8 v6 f$ `; k5 C# K
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: % E. R7 q& E, W2 l! q% o
enum LockTypeEnum
! U4 `( m( R) _/ o2 V5 l5 o2 E5 o7 C{
1 ?& O" L' t! v4 f4 zadLockUnspecified = -1,///未指定
9 J; j. c! p1 xadLockReadOnly = 1,///只读记录集 ; j( n' z* Q. l" s' E$ p; l
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 ; c' h# ]# w6 [( j, d _
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
8 ?( j% k4 ]/ ^adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。 . k! w6 C/ [! a5 B0 [7 V* D
};
5 K0 E4 J5 i7 C5 G$ w⑤Options请参考本文中对Connection对象的Execute方法的介绍 ! Y9 S0 u! w) h' B& |: G
6 V9 m b" | S5 z$ Y* u8 g2 V2 N }7 s+ u/ P
【5】记录集的遍历、更新
: l# G: ], s' g- `/ i9 m根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday , z, v( N% a. ?( S7 b& O6 p* M! M) _
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 9 `& }9 R* s0 d2 Y
8 c9 R+ i9 ?! j. z! z: t: `
' N/ W' H6 u( M0 t4 m9 {, ]
_variant_t vUsername,vBirthday,vID,vOld; 1 Z3 E, g' F# P9 ~, ^9 [& N
_RecordsetPtr m_pRecordset; 1 f$ j s7 D' f- Q
m_pRecordset.CreateInstance("ADODB.Recordset");
( {2 U( @/ _ E Qm_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); ; m7 g+ o8 \" I
while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? 5 |: F8 `. s7 Z Q, }
{
/ N# Y% g' u- O7 UvID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
" b0 A9 C9 S9 F0 c- G* {vUsername = m_pRecordset->GetCollect("username");///取得username字段的值 ) \* w9 I8 s' F, H
vOld = m_pRecordset->GetCollect("old");
8 q+ W! C3 d) k8 l0 pvBirthday = m_pRecordset->GetCollect("birthday"); ( h* }" Q! H, t1 R! i" X
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录 " h9 w7 n! h$ e2 z7 A6 X
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL) ]. b# R1 c6 d
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); ; M* r' }3 R7 S3 _ C; p
m_pRecordset->MoveNext();///移到下一条记录 / J& Y% r5 n8 {+ O* u3 T
} ) }1 D8 H0 Q+ r& `. x
m_pRecordset->MoveFirst();///移到首条记录
0 W* f/ A1 D5 j' Xm_pRecordset->Delete(adAffectCurrent);///删除当前记录 , [" v5 Z; h2 Y' b6 N( k1 E/ d
///添加三条新记录并赋值
1 Z6 h* j# }. u; O* E, ufor(int i=0;i<3;i++) z! E0 l- m( z3 }
{ ; J1 r% ]( Q* S1 x6 G9 E6 Q
m_pRecordset->AddNew();///添加新记录
# o+ V2 j& o, a, I( km_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); + |/ K% A! m- d3 y' j0 p8 m
m_pRecordset->PutCollect("username",_variant_t("叶利钦")); ) q0 \1 R) o1 t; f" ` H
m_pRecordset->PutCollect("old",_variant_t((long)71)); 5 j6 @+ T% [7 Y
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
4 a* u9 F& c q N8 C}
& N$ P8 M: \ A5 z" t4 S' gm_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 8 O8 T" [; L0 @2 a1 r1 }( c
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
. b( O5 k1 I) ?$ L& C# Mm_pRecordset->Update();///保存到库中
/ H0 x1 V4 ~+ Y! V" |* \$ {6 U6 ?$ t L; I
【6】关闭记录集与连接 1 {& J/ c9 l' X# d: ^
记录集或连接都可以用Close方法来关闭 # S& c7 o4 P' y4 @+ G
m_pRecordset->Close();///关闭记录集 0 \. x5 c$ ^7 G; [! g; _4 W5 W9 j
m_pConnection->Close();///关闭连接
9 [6 X) v9 E' R* ^
; d3 j4 d' T% K. J L' i至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
0 m1 Y/ w* l5 J4 _( q( E点这里下载示例代码
( w4 }# L: n2 w" n- q6 v- S, j
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|