|
|
一、ADO简介
2 U4 I) N* R9 l/ M" g# c8 cADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
3 l D& U. i" P. u本文示例代码 7 C3 H2 i3 i+ R
( p9 _! i9 c X3 i$ `5 S二、基本流程 4 c( q% ~* [7 g/ Y6 q, Q1 f8 V
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! & o1 K- ]8 ~ e& _1 s( H5 p
(1)初始化COM库,引入ADO库定义文件 + w; [/ u. a2 i: h' K Y% t
(2)用Connection对象连接数据库 3 d' [( n2 ?( Y0 i- u
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 - s' d" c3 L/ m! ?9 l
(4)使用完毕后关闭连接释放对象。 L; R6 t( h7 k% ^+ X1 ]5 y7 W( }
+ D4 H/ |3 o$ \" |- x
准备工作: , `* Q. V+ `0 y8 V9 R. z9 s
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
3 I9 @4 Q" i' a ]2 u$ t; M下面我们将详细介绍上述步骤并给出相关代码。 ! }2 B% P- Y/ D/ ^ i
【1】COM库的初始化 3 @9 [5 L/ p" _
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
" |+ C+ I! e" O5 w0 `% K
7 u% s" U$ }7 y" g6 U, i0 J# b, K4 f* p8 t' A0 b" b
BOOL CADOTest1App::InitInstance()
# {8 }( ?% l5 w2 B( x { 0 u& }4 o2 O. a8 l) v; T
AfxOleInit(); . d3 @4 P/ q3 ]: D* O' O! @8 p& ]
...... - K$ o# i/ V8 k0 M$ i& M- C# o" ^
4 T1 d, f: q8 v0 V: W【2】用#import指令引入ADO类型库
# A( b2 g# p0 E我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) 7 u$ f( `4 e/ k$ O
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") 6 v' r+ @) U9 [+ `$ b4 @4 B
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。 * U6 p! ^4 V4 Z# O# t, n
2 e1 T) K* B" H- Q: u9 | o
几点说明: / H& A. v. ?- q9 c2 n
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 ! ~5 v! M9 Y, ]
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 + d9 }" _/ x1 {& C
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned
4 e3 f/ r9 Z; o
/ g1 f( G3 [% X. G+ ~【3】创建Connection对象并连接数据库
3 {9 S- Y* d1 s; f首先我们需要添加一个指向Connection对象的指针: & R* J4 O7 P5 W% e) R6 p$ ]2 A
_ConnectionPtr m_pConnection;
! ?, ~! i$ R' Q& T! k* @' d$ ?下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 0 n- p; v2 J$ O/ [4 n; @$ W' r* i
# b1 i) [+ i2 z" F3 Q+ O6 V3 u5 L; b S5 @
BOOL CADOTest1Dlg::OnInitDialog()
7 K) u/ k, n) R {
) {1 T% R) b/ _9 | CDialog::OnInitDialog(); - q' n5 p& G, Q* ^
HRESULT hr; 3 c" G; H' \# j, k2 r
try
# Y+ [- w8 E( S' @9 @9 } {
' ~5 S1 X% M& n. A" Q hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
* p2 N5 N+ J1 @4 ` _ if(SUCCEEDED(hr))
/ D' l$ b: H% H) N$ c6 v2 { { ) j' f# e- [% O* _
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 ' J$ T/ o" M0 D9 Q" N5 F1 F5 g
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; } 5 ?6 i8 F& p0 X$ @+ g% F# C
} 5 H% ]0 H2 W5 v" o/ e0 k$ Z# {9 ?
catch(_com_error e)///捕捉异常
; N/ {' {0 ?( J- j+ v9 t5 Q1 _ {
) [) d$ p+ H' g. k- r2 G' D CString errormessage;
& d1 ^+ X& D+ J0 X errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); ( h3 S' T& i9 j& V( b4 T3 n$ \
AfxMessageBox(errormessage);///显示错误信息 ( N ~ u e2 p6 V6 o& ]
}
( O0 l5 |3 x& \$ j) |5 ^
- o* n+ z7 c) v- L) e* k7 b在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 4 Y+ P: |8 B) [" ^8 r( |, U* ?
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) 5 X3 M n9 H# j7 J+ F3 i# g3 M) f
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
2 z$ S$ l7 {6 h4 X% _& kOptions可以是如下几个常量:
9 O- i; r0 a- T$ B) |adModeUnknown:缺省。当前的许可权未设置
' @- d5 F2 F1 W& O$ z' A# ~adModeRead:只读 / @9 j* s" L' N
adModeWrite:只写
2 p2 } H+ e+ h& d: @adModeReadWrite:可以读写 ) M; P. S$ b3 Z; r- S$ O
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
" |. m! t8 n% R, M9 m; S4 F3 hadModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 5 [; N, j* o! I* h$ e4 M+ S
adModeShareExclusive:阻止其它Connection对象打开连接 . X) ]0 ?! ^2 A1 d
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接
3 b+ q, [( X- l9 D g* j( q; R$ g5 e8 a* `; n
我们给出一些常用的连接方式供大家参考:
; x" T: B, H5 D& g2 O$ i( f' i(1)通过JET数据库引擎对ACCESS2000数据库的连接 9 H. g; Y: t9 I/ H) ~
0 Z( j8 v4 A- |7 X' j1 M0 D
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
1 ], z2 {' F# @
: o& G( y T, P$ F1 q- E: R(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
0 I& s7 e" \, H& a. @3 om_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); % S! i Q" I$ c! a$ K/ ?
& q) [# a, p1 t- s(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
$ N7 @1 o4 G- R# j
7 T$ r I2 ]5 ?, J其中Server是SQL服务器的名称,DATABASE是库的名称
: ~" ?3 M, I% v+ B$ I4 v) K6 }6 j1 d
0 n) q( p9 X! U$ X" XConnection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State 9 P+ e: u, k' q! @$ |
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 " K6 \1 B" l' p6 J" x& s
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown); " Y% e8 r" v2 J7 v$ z2 [" L$ e$ k4 j
" s( M: D) _, a s- F/ Y
# P6 V/ D) Q3 h# u# u- fState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
1 O9 Y+ L3 T! ]if(m_pConnection->State)
" c7 B* G0 q* A. R% I$ M9 D m_pConnection->Close(); ///如果已经打开了连接则关闭它 1 W# M& V! }, z( } M+ G0 r
; j5 N1 u& N' a6 c' T1 K+ F' f, G" h' r* ]
【4】执行SQL命令并取得结果记录集
, t( ^' B1 R: p( g* i为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
! V4 k; }6 ]0 U. _并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
K) u D& b( M+ y2 KSQL命令的执行可以采用多种形式,下面我们一进行阐述。 2 H" R B6 `- }1 p" y F2 i
/ P) \' o( s" t8 d
(1)利用Connection对象的Execute方法执行SQL命令 , u6 i7 [" _, v0 H
Execute方法的原型如下所示: * G/ x" v* q% J! e
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: 1 X% |8 ]: y: L8 g
adCmdText:表明CommandText是文本命令
- x) N" {& F" s0 L' c- c! ]4 CadCmdTable:表明CommandText是一个表名
) r4 x$ o7 }) u0 cadCmdProc:表明CommandText是一个存储过程 8 Q9 e' n3 ]0 ~# @4 N
adCmdUnknown:未知
[; m* T E( ^8 g1 M& G. o" z) `. E. P" u0 q* x$ z% C
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; * h" N# [6 ~" C. P; E0 m, q8 l9 x
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday + p( w& r" z; }' ~: k+ D
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);
! f/ l" j/ G5 i ///往表格里面添加记录
% J3 n4 D4 L- G$ ~1 c4 ]% F3 z1 i4 _1 A m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText); ) h W/ e/ K( l# M
///将所有记录old字段的值加一
9 Y0 M( B( r4 C# C: T( ? m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); , |0 X# a. z; g; p' C& D# v$ f
///执行SQL统计命令得到包含记录条数的记录集 * f% D9 h& @, N
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText); h, a3 f1 U8 T% }8 v1 g
_variant_t vIndex = (long)0; , T7 O/ W3 Q+ | [
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
+ X$ D5 T6 W5 R5 g2 [# I N* g$ y m_pRecordset->Close();///关闭记录集 - _+ C/ x7 w1 B
CString message; - b) Z/ [. U6 J: j/ w$ h
message.Format("共有%d条记录",vCount.lVal); 3 }3 Q* i5 v5 e
AfxMessageBox(message);///显示当前记录条数
, N: N$ ?, X- I2 u
, M1 v" ^) W. a& b# G
: R" h# V" B9 f2 K; p% P) v( y(2)利用Command对象来执行SQL命令
+ g! g; @2 X% T _CommandPtr m_pCommand; . M6 u4 V2 P) e1 A
m_pCommand.CreateInstance("ADODB.Command");
$ U1 j8 H" u5 D. l _variant_t vNULL;
% U. c. V8 O, C2 B vNULL.vt = VT_ERROR;
: s6 g. ]- Y- k- V1 @& ` vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
9 r6 L3 i( E0 d' U2 L9 k m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 9 j( O& {# Q L! _& ?* {3 h: o
m_pCommand->CommandText = "SELECT * FROM users";///命令字串 3 N4 W6 j. Q( w; l1 N
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
O! W/ T' H! s5 \. {5 I3 h8 O+ L) w9 y8 I* S5 Z
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
& F, k3 W2 e; w$ D, `; R
7 O3 }3 @3 N. d* ^; D+ k. L k, j% z# \, J
(3)直接用Recordset对象进行查询取得记录集
7 k' m; s# i8 G" p: n4 G3 G例如 . Z7 Q) P+ B2 m, }- W- g( Y
" V$ i, Y/ t# K6 z0 ^ m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); ; @5 }( u/ ?1 d/ T/ `4 _
" N9 K: Q/ q% i
Open方法的原型是这样的:
; k+ W; J1 k2 {. b5 [HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) ! v. P* n7 M* \' H: Z% S
其中:
; w/ y5 `: Z1 B8 H! O①Source是数据查询字符串 7 w0 z1 J3 h& w5 a0 P, Y
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象) ' D/ y) ^4 |2 ?3 F
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: 2 I/ F9 N4 c1 {+ o k7 C/ L7 l6 W; m
enum CursorTypeEnum
) d: R8 H* H* K/ O0 q- g6 y; b{
& r3 [; @0 T5 G. i" CadOpenUnspecified = -1,///不作特别指定 4 s# {" ~% |- H" d0 Q
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
W% l w3 m1 Q* {adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。 " K5 F8 w" @* G6 h6 B! l' G
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。 7 [. C8 A% j, r' C" n4 M
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
- Q ~1 `# ^, f};
4 }4 @; _. \1 v9 D* y/ n/ P④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
1 G, G4 o& N; n% r8 S0 N9 Oenum LockTypeEnum
/ ]$ K( f' \7 m+ P) {$ K{
6 _9 ?5 \2 d/ j5 ?% d$ f% ~adLockUnspecified = -1,///未指定 ! E' v: d7 n( V% X7 y
adLockReadOnly = 1,///只读记录集
# p5 |+ c+ F+ K$ U: n* eadLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
' `& P3 K8 _7 g2 p& T7 q6 C2 J% MadLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
5 V0 E+ D9 b( |7 e! G1 @( xadLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。 ; @! z: W* d+ r
};
! B) K; s4 T0 X3 y) k5 g⑤Options请参考本文中对Connection对象的Execute方法的介绍 % ?& m4 z0 W5 W A! q$ M6 O% ?0 J4 i7 q
- o ? a/ E# w; |6 p
( b9 c$ V3 z! n2 k$ G0 B7 t【5】记录集的遍历、更新 : q$ J' }9 W" P. s9 A2 N S0 e3 y, I
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
4 r2 c, G" x% z8 Z) \- }$ H以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
9 ?3 [* I0 f2 J) r% P
( z4 ^( V" ~# B! [
% j6 |( K5 ~. O! K1 b7 X( X_variant_t vUsername,vBirthday,vID,vOld;
/ e s4 m0 c' z% l: m_RecordsetPtr m_pRecordset; 9 V3 p* c( d% a+ Z! e6 V9 A5 p
m_pRecordset.CreateInstance("ADODB.Recordset");
0 \' z' _$ m* w+ N9 Y8 o. O$ b8 ?( am_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); 7 P* a9 m ]$ }) G$ k, b1 b
while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
6 }9 Z+ v9 J6 N! z$ D9 \6 [{ ! Z k( E6 {5 p# C7 k' f6 A
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
5 M& `9 z5 _, ~" n2 n: QvUsername = m_pRecordset->GetCollect("username");///取得username字段的值
# P' L( Z& Q0 v0 P6 ], nvOld = m_pRecordset->GetCollect("old");
1 v/ b. p6 J: x8 `2 \: e+ C k- QvBirthday = m_pRecordset->GetCollect("birthday"); ! P$ z3 x$ |& N4 J4 g& N
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录 # j# p0 r) I# `6 }' G3 X% O/ n- s
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
i, R& x1 `2 z h) W9 a# r TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); + J* D0 u1 v% x* ]8 Z# E n
m_pRecordset->MoveNext();///移到下一条记录 # F/ W z1 d4 ]4 ^5 P0 D# X
}
- U- s7 S) G$ ^2 y* {( Y4 M: K5 Im_pRecordset->MoveFirst();///移到首条记录
+ w8 C5 [* \' B& ~+ Q/ d0 n+ ?m_pRecordset->Delete(adAffectCurrent);///删除当前记录 ; n& |6 t8 L: U/ M( V( E/ S! P, v
///添加三条新记录并赋值 ; ?- n S: e4 [5 ?* d$ j
for(int i=0;i<3;i++) & E+ C$ Y0 Z) {/ m0 Z
{ " d4 c/ k6 Y( x8 f
m_pRecordset->AddNew();///添加新记录
1 S/ d1 a: c. X/ }! M {" qm_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
" |0 M4 C/ w4 D8 O. {# J4 z* Nm_pRecordset->PutCollect("username",_variant_t("叶利钦"));
/ V" G# n- j; z3 U. ^6 {m_pRecordset->PutCollect("old",_variant_t((long)71));
4 t/ E' W. r c- q4 lm_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
: ~" r0 `2 p( s} 5 i7 r, h2 \+ g9 B# a+ U
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
$ ~! v% Q( s3 H9 R% V: V% M5 ^0 p+ Pm_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
- Q( y/ k7 A. p) _8 }3 Dm_pRecordset->Update();///保存到库中 2 e8 p) `7 B9 E) F+ @$ A, v
- b% T9 ~5 v' X+ @7 P
【6】关闭记录集与连接
0 j8 M5 g- [" P! P+ I4 v) l. Y记录集或连接都可以用Close方法来关闭 2 q4 z) W2 e( x# g$ A9 c% |5 J5 _
m_pRecordset->Close();///关闭记录集 / R: q2 h5 b. F: R9 c( \
m_pConnection->Close();///关闭连接 2 s" @1 T3 s; F/ [( C! _: i1 a
& q$ z; Y7 R& f& L$ U
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。 ) X8 j7 ^% D/ E5 m4 E
点这里下载示例代码 , Q9 x9 _1 B% v& v. H/ G5 H( e
) v* D- G6 y+ [8 @( l1 y% `后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|