10、邦定数据
定义一个绑定类,将其成员变量绑定到一个指定的记录集,以方便于访问记录集的字段值。
(1). 从cadorecordbinding派生出一个类:
class ccustomrs : public cadorecordbinding
{
begin_ado_binding(ccustomrs)
ado_variable_length_entry2(3, advarchar, m_szau_fname,
sizeof(m_szau_fname), lau_fnamestatus, false)
ado_variable_length_entry2(2, advarchar, m_szau_lname,
sizeof(m_szau_lname), lau_lnamestatus, false)
ado_variable_length_entry2(4, advarchar, m_szphone,
sizeof(m_szphone), lphonestatus, true)
end_ado_binding()
public:
char m_szau_fname[22];
ulong lau_fnamestatus;
char m_szau_lname[42];
ulong lau_lnamestatus;
char m_szphone[14];
ulong lphonestatus;
};
其中将要绑定的字段与变量名用begin_ado_binding宏关联起来。每个字段对应于两个变量,一个存放字段的值,另一个存放字段的状态。字段用从1开始的序号表示,如1,2,3等等。
特别要注意的是:如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(比如m_szau_fname[22],其绑定的字段au_fname的长度实际是20),不这样绑定就会失败。我分析多出的2可能是为了存放字符串结尾的空字符null和bstr字符串开头的一个字(表示bstr的长度)。这个问题对于初学者来说可能是一个意想不到的问题。
cadorecordbinding类的定义在icrsint.h文件里,内容是:
class cadorecordbinding
{
public:
stdmethod_(const ado_binding_entry*, getadobindingentries) (void) pure;
};
begin_ado_binding宏的定义也在icrsint.h文件里,内容是:
#define begin_ado_binding(cls) public: /
typedef cls adorowclass; /
const ado_binding_entry* stdmethodcalltype getadobindingentries() { /
static const ado_binding_entry rgadobindingentries[] = {
ado_variable_length_entry2宏的定义也在icrsint.h文件里:
#define ado_variable_length_entry2(ordinal, datatype, buffer, size, status, modify)/
{ordinal, /
datatype, /
0, /
0, /
size, /
offsetof(adorowclass, buffer), /
offsetof(adorowclass, status), /
0, /
classoffset(cadorecordbinding, adorowclass), /
modify},
#define end_ado_binding宏的定义也在icrsint.h文件里:
#define end_ado_binding() {0, adempty, 0, 0, 0, 0, 0, 0, 0, false}};/
return rgadobindingentries;}
(2). 绑定
_recordsetptr rs1;
iadorecordbinding *picrs=null;
ccustomrs rs;
......
rs1->queryinterface(__uuidof(iadorecordbinding),
(lpvoid*)&picrs));
picrs->bindtorecordset(&rs);
派生出的类必须通过iadorecordbinding接口才能绑定,调用它的bindtorecordset方法就行了。
(3). rs中的变量即是当前记录字段的值
//set sort and filter condition:
// step 4: manipulate the data
rs1->fields->getitem("au_lname")->properties->getitem("optimize")->value = true;
rs1->sort = "au_lname asc";
rs1->filter = "phone like '415 5*'";
rs1->movefirst();
while (variant_false == rs1->endoffile)
{
printf("name: %s/t %s/tphone: %s/n",
(rs.lau_fnamestatus == adfldok ? rs.m_szau_fname : ""),
(rs.lau_lnamestatus == adfldok ? rs.m_szau_lname : ""),
(rs.lphonestatus == adfldok ? rs.m_szphone : ""));
if (rs.lphonestatus == adfldok)
strcpy(rs.m_szphone, "777");
testhr(picrs->update(&rs)); // add change to the batch
rs1->movenext();
}
rs1->filter = (long) adfilternone;
......
if (picrs) picrs->release();
rs1->close();
pconn->close();
只要字段的状态是adfldok,就可以访问。如果修改了字段,不要忘了先调用picrs的update(注意不是recordset的update),然后才关闭,也不要忘了释放picrs(即picrs->release();)。
(4). 此时还可以用iadorecordbinding接口添加新纪录
if(failed(picrs->addnew(&rs)))
......
11. 访问长数据
在microsoft sql中的长数据包括text、image等这样长类型的数据,作为二进制字节来对待。
可以用field对象的getchunk和appendchunk方法来访问。每次可以读出或写入全部数据的一部分,它会记住上次访问的位置。但是如果中间访问了别的字段后,就又得从头来了。
请看下面的例子:
//写入一张照片到数据库:
variant varchunk;
safearray *psa;
safearraybound rgsabound[1];
//vt_array │ vt_ui1
cfile f("h://aaa.jpg",cfile::moderead);
byte bval[chunksize+1];
uint uisread=0;
//create a safe array to store the array of bytes
while(1)
{
uisread=f.read(bval,chunksize);
if(uisread==0)break;
rgsabound[0].celements =uisread;
rgsabound[0].llbound = 0;
psa = safearraycreate(vt_ui1,1,rgsabound);
for(long index=0;index<uisread;index++)
{
if(failed(safearrayputelement(psa,&index,&bval[index])))
::messagebox(null,"啊,又出毛病了。","提示",mb_ok │ mb_iconwarning);
}
varchunk.vt = vt_array│vt_ui1;
varchunk.parray = psa;
try{
m_precordset->fields->getitem("photo")->appendchunk(varchunk);
}
catch (_com_error &e)
{
cstring str=(char*)e.description();
::messagebox(null,str+"/n又出毛病了。","提示",mb_ok │ mb_iconwarning);
}
::variantclear(&varchunk);
::safearraydestroydata( psa);
if(uisread<chunksize)break;
}//while(1)
f.close();
//从数据库读一张照片:
cfile f;
f.open("h://bbb.jpg",cfile::modewrite│cfile::modecreate);
long lphotosize = m_precordset->fields->item["photo"]->actualsize;
long lisread=0;
_variant_t varchunk;
byte buf[chunksize];
while(lphotosize>0)
{
lisread=lphotosize>=chunksize? chunksize:lphotosize;
varchunk = m_precordset->fields->
item["photo"]->getchunk(lisread);
for(long index=0;index<lisread;index++)
{
::safearraygetelement(varchunk.parray,&index,buf+index);
}
f.write(buf,lisread);
lphotosize-=lisread;
}//while()
f.close();
12. 使用safearray问题
学会使用safearray也是很重要的,因为在ado编程中经常要用。它的主要目的是用于automation中的数组型参数的传递。因为在网络环境中,数组是不能直接传递的,而必须将其包装成safearray。实质上safearray就是将通常的数组增加一个描述符,说明其维数、长度、边界、元素类型等信息。safearray也并不单独使用,而是将其再包装到variant类型的变量中,然后才作为参数传送出去。在variant的vt成员的值如果包含vt_array│...,那么它所封装的就是一个safearray,它的parray成员即是指向safearray的指针。safearray中元素的类型可以是variant能封装的任何类型,包括variant类型本身。
使用safearray的具体步骤:
方法一:
包装一个safearray:
(1). 定义变量,如:
variant varchunk;
safearray *psa;
safearraybound rgsabound[1];
(2). 创建safearray描述符:
uisread=f.read(bval,chunksize);//read array from a file.
if(uisread==0)break;
rgsabound[0].celements =uisread;
rgsabound[0].llbound = 0;
psa = safearraycreate(vt_ui1,1,rgsabound);
(3). 放置数据元素到safearray:
for(long index=0;index<uisread;index++)
{
if(failed(safearrayputelement(psa,&index,&bval[index])))
::messagebox(null,"出毛病了。","提示",mb_ok │ mb_iconwarning);
}
一个一个地放,挺麻烦的。
(4). 封装到variant内:
varchunk.vt = vt_array│vt_ui1;
varchunk.parray = psa;
这样就可以将varchunk作为参数传送出去了。
读取safearray中的数据的步骤:
(1). 用safearraygetelement一个一个地读
byte buf[lisread];
for(long index=0;index<lisread;index++)
{
::safearraygetelement(varchunk.parray,&index,buf+index);
}
就读到缓冲区buf里了。
方法二:
使用safearrayaccessdata直接读写safearray的缓冲区:
(1). 读缓冲区:
byte *buf;
safearrayaccessdata(varchunk.parray, (void **)&buf);
f.write(buf,lisread);
safearrayunaccessdata(varchunk.parray);
(2). 写缓冲区:
byte *buf;
::safearrayaccessdata(psa, (void **)&buf);
for(long index=0;index<uisread;index++)
{
buf[index]=bval[index];
}
::safearrayunaccessdata(psa);
varchunk.vt = vt_array│vt_ui1;
varchunk.parray = psa;
这种方法读写safearray都可以,它直接操纵safearray的数据缓冲区,比用safearraygetelement和safearrayputelement速度快。特别适合于读取数据。但用完之后不要忘了调用::safearrayunaccessdata(psa),否则会出错的。
13. 使用书签( bookmark )
书签可以唯一标识记录集中的一个记录,用于快速地将当前记录移回到已访问过的记录,以及进行过滤等等。provider会自动为记录集中的每一条记录产生一个书签,我们只需要使用它就行了。我们不能试图显示、修改或比较书签。ado用记录集的bookmark属性表示当前记录的书签。
用法步骤:
(1). 建立一个variant类型的变量
_variant_t varbookmark;
(2). 将当前记录的书签值存入该变量
也就是记录集的bookmark属性的当前值。
varbookmark = rst->bookmark;
(3). 返回到先前的记录
将保存的书签值设置到记录集的书签属性中:
// check for whether bookmark set for a record
if (varbookmark.vt == vt_empty)
printf("no bookmark set!/n");
else
rst->bookmark = varbookmark;
设置完后,当前记录即会移动到该书签指向的记录。
14、设置过滤条件
recordset对象的filter属性表示了当前的过滤条件。它的值可以是以and或or连接起来的条件表达式(不含where关键字)、由书签组成的数组或ado提供的filtergroupenum枚举值。为filter属性设置新值后recordset的当前记录指针会自动移动到满足过滤条件的第一个记录。例如:
rst->filter = _bstr_t ("姓名='赵薇' and 性别=’女’");
在使用条件表达式时应注意下列问题:
(1)、可以用圆括号组成复杂的表达式
例如:
rst->filter = _bstr_t ("(姓名='赵薇' and 性别=’女’) or age<25");
但是微软不允许在括号内用or,然后在括号外用and,例如:
rst->filter = _bstr_t ("(姓名='赵薇' or 性别=’女’) and age<25");
必须修改为:
rst->filter = _bstr_t ("(姓名='赵薇' and age<25) or (性别=’女’ and age<25)");
(2)、表达式中的比较运算符可以是like
like后被比较的是一个含有通配符*的字符串,星号表示若干个任意的字符。
字符串的首部和尾部可以同时带星号*
rst->filter = _bstr_t ("姓名 like '*赵*' ");
也可以只是尾部带星号:
rst->filter = _bstr_t ("姓名 like '赵*' ");
filter属性值的类型是variant,如果过滤条件是由书签组成的数组,则需将该数组转换为safearray,然后再封装到一个variant或_variant_t型的变量中,再赋给filter属性。
15、索引与排序
(1)、建立索引
当以某个字段为关键字用find方法查找时,为了加快速度可以以该字段为关键字在记录集内部临时建立索引。只要将该字段的optimize属性设置为true即可,例如:
prst->fields->getitem("姓名")->properties->
getitem("optimize")->putvalue("true");
prst->find("姓名 = '赵薇'",1,adsearchforward);
......
prst->fields->getitem("姓名")->properties->
getitem("optimize")->putvalue("false");
prst->close();
说明:optimize属性是由provider提供的属性(在ado中称为动态属性),ado本身没有此属性。
(2)、排序
要排序也很简单,只要把要排序的关键字列表设置到recordset对象的sort属性里即可,例如:
prstauthors->cursorlocation = aduseclient;
prstauthors->open("select * from mytable",
_variant_t((idispatch *) pconnection),
adopenstatic, adlockreadonly, adcmdtext);
......
prst->sort = "姓名 desc, 年龄 asc";
关键字(即字段名)之间用逗号隔开,如果要以某关键字降序排序,则应在该关键字后加一空格,再加desc(如上例)。升序时asc加不加无所谓。本操作是利用索引进行的,并未进行物理排序,所以效率较高。
但要注意,在打开记录集之前必须将记录集的cursorlocation属性设置为aduseclient,如上例所示。sort属性值在需要时随时可以修改。
16、事务处理
ado中的事务处理也很简单,只需分别在适当的位置调用connection对象的三个方法即可,这三个方法是:
(1)、在事务开始时调用
pcnn->begintrans();
(2)、在事务结束并成功时调用
pcnn->committrans ();
(3)、在事务结束并失败时调用
pcnn->rollbacktrans ();
在使用事务处理时,应尽量减小事务的范围,即减小从事务开始到结束(提交或回滚)之间的时间间隔,以便提高系统效率。需要时也可在调用begintrans()方法之前,先设置connection对象的isolationlevel属性值,详细内容参见msdn中有关ado的技术资料。
三、使用ado编程常见问题解答
以下均是针对ms sql 7.0编程时所遇问题进行讨论。
1、连接失败可能原因
enterprise managemer内,打开将服务器的属性对话框,在security选项卡中,有一个选项authentication。
如果该选项是windows nt only,则你的程序所用的连接字符串就一定要包含trusted_connection参数,并且其值必须为yes,如:
"provider=sqloledb;server=888;trusted_connection=yes"
";database=master;uid=lad;";
如果不按上述操作,程序运行时连接必然失败。
如果authentication选项是sql server and windows nt,则你的程序所用的连接字符串可以不包含trusted_connection参数,如:
"provider=sqloledb;server=888;database=master;uid=lad;pwd=111;";
因为ado给该参数取的默认值就是no,所以可以省略。我认为还是取默认值比较安全一些。
2、改变当前数据库的方法
使用tansct-sql中的use语句即可。
3、如何判断一个数据库是否存在
(1)、可打开master数据库中一个叫做schemata的视图,其内容列出了该服务器上所有的数据库名称。
(2) 、更简便的方法是使用use语句,成功了就存在;不成功,就不存在。例如:
try{
m_pconnect->execute ( _bstr_t("use insurance_2002"),null,
adcmdtext│adexecutenorecords );
}
catch (_com_error &e)
{
blsuccess=false;
cstring str="数据库insurance_2002不存在!/n";
str+=e.description();
::messagebox(null,str,"警告",mb_ok │ mb_iconwarning);
}
4、判断一个表是否存在
(1)、同样判断一个表是否存在,也可以用是否成功地打开它来判断,十分方便,例如:
try{
m_precordset->open(_variant_t("mytable"),
_variant_t((idispatch *)m_pconnection,true), adopenkeyset,
adlockoptimistic, adcmdtable);
}
catch (_com_error &e)
{
::messagebox(null,"该表不存在。","提示",mb_ok │ mb_iconwarning);
}
(2)、要不然可以采用麻烦一点的办法,就是在ms-sql服务器上的每个数据库中都有一个名为sysobjects的表,查看此表的内容即知指定的表是否在该数据库中。
(3)、同样,每个数据库中都有一个名为tables的视图(view),查看此视图的内容即知指定的表是否在该数据库中。
5、类型转换问题
(1)、类型variant_bool
类型variant_bool等价于short类型。the variant_bool is equivalent to short. see it's definition below:
typdef short variant_bool
(2)、_com_ptr_t类的类型转换
_connectionptr可以自动转换成idspatch*类型,这是因为_connectionptr实际上是_com_ptr_t类的一个实例,而这个类有此类型转换函数。
同理,_recordsetptr和_commandptr也都可以这样转换。
(3)、_bstr_t和_variant_t类
在ado编程时,_bstr_t和_variant_t这两个类很有用,省去了许多bstr和variant类型转换的麻烦。
6、打开记录集时的问题
在打开记录集时,在调用recordset的open方法时,其最后一个参数里一定不能包含adasyncexecute,否则将因为是异步操作,在读取数据时无法读到数据。
7、异常处理问题
对所有调用ado的语句一定要用try和catch语句捕捉异常,否则在发生异常时,程序会异常退出。
8、使用safearray问题
在初学使用中,我曾遇到一个伤脑筋的问题,一定要注意:
在定义了safearray的指针后,如果打算重复使用多次,则在中间可以调用::safearraydestroydata释放数据,但决不能调用::safearraydestroydescriptor,否则必然出错,即使调用safearraycreate也不行。例如:
safearray *psa;
......
//when the data are no longer to be used:
::safearraydestroydata( psa);
我分析在定义psa指针时,一个safearray的实例(也就是safearray描述符)也同时被自动建立了。但是只要一调用::safearraydestroydescriptor,描述符就被销毁了。
所以我认为::safearraydestroydescriptor可以根本就不调用,即使调用也必须在最后调用。
9、重复使用命令对象问题
一个命令对象如果要重复使用多次(尤其是带参数的命令),则在第一次执行之前,应将它的prepared属性设置为true。这样会使第一次执行减慢,但却可以使以后的执行全部加快。
10、绑定字符串型字段问题
如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(比如m_szau_fname[22],其绑定的字段au_fname的长度实际是20),不这样绑定就会失败。
11、使用appendchunk的问题
当用addnew方法刚刚向记录集内添加一个新记录之后,不能首先向一个长数据字段(image类型)写入数据,必须先向其他字段写入过数据之后,才能调用appendchunk写该字段,否则出错。也就是说,appendchunk不能紧接在addnew之后。另外,写入其他字段后还必须紧接着调用appendchunk,而不能调用记录集的update方法后,才调用appendchunk,否则调用appendchunk时也会出错。换句话说,就是必须appendchunk在前,update在后。因而这个时候就不能使用带参数的addnew了,因为带参数的addnew会自动调用记录集的update,所以appendchunk就跑到update的后面了,就只有出错了!因此,这时应该用不带参数的addnew。
我推测这可能是ms sql 7.0的问题,在ms sql 2000中则不存在这些问题,但是appendchunk仍然不能在update之后。
四、小结
一般情况下,connection和command的execute用于执行不产生记录集的命令,而recordset的open用于产生一个记录集,当然也不是绝对的。特别command主要是用于执行参数化的命令,可以直接由command对象执行,也可以将command对象传递给recordset的open。