首页 > 开发 > 综合 > 正文

Visual C++ ADO数据库编程入门(下)

2024-07-21 02:05:49
字体:
来源:转载
供稿:网友
 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。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表