<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-时间的痕迹-文章分类-ADO编程技术</title><link>http://www.cppblog.com/ivenher/category/317.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 19 May 2008 13:02:27 GMT</lastBuildDate><pubDate>Mon, 19 May 2008 13:02:27 GMT</pubDate><ttl>60</ttl><item><title>_variant_t  中VT_DECIMAL 问题</title><link>http://www.cppblog.com/ivenher/articles/16638.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Wed, 20 Dec 2006 03:24:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/16638.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/16638.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/16638.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/16638.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/16638.html</trackback:ping><description><![CDATA[_variant_t 数据类型与其他数据类型的转换比较麻烦，以前用的是从网上找来的一段代码，存在一些问题，后果很严重，害我以为出现灵异事件了。<br />原方法如下：<br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000"> VT_DECIMAL: <br />             {<br />                  </span><span style="COLOR: #0000ff">double</span><span style="COLOR: #000000"> val </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> var.decVal.Lo32;<br />                                     <br />                    val </span><span style="COLOR: #000000">*=</span><span style="COLOR: #000000"> (var.decVal.sign </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">128</span><span style="COLOR: #000000">)</span><span style="COLOR: #000000">?</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000"> : </span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">;<br />                    val </span><span style="COLOR: #000000">/=</span><span style="COLOR: #000000"> pow(</span><span style="COLOR: #000000">10</span><span style="COLOR: #000000">, var.decVal.scale); <br />                    CString strFormat;<br />                    strFormat.Format(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">%d</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">, var.decVal.scale);<br />                    strFormat </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">%.</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">strFormat</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">f</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">;<br />                                         strValue.Format(strFormat, val);<br /><br />                                 </span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br />             }</span></div><br /><br />转过多次没出什么事就认为这段代码可以用。结果问题就出在这段代码。<br /><br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/images/OutliningIndicators/None.gif" align="top" /><span style="COLOR: #000000"><br /><img id="Codehighlighter1_1_8_Open_Image" onclick="this.style.display='none'; Codehighlighter1_1_8_Open_Text.style.display='none'; Codehighlighter1_1_8_Closed_Image.style.display='inline'; Codehighlighter1_1_8_Closed_Text.style.display='inline';" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_1_8_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_1_8_Closed_Text.style.display='none'; Codehighlighter1_1_8_Open_Image.style.display='inline'; Codehighlighter1_1_8_Open_Text.style.display='inline';" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif" align="top" /></span><span id="Codehighlighter1_1_8_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">/**/</span><span id="Codehighlighter1_1_8_Open_Text"><span style="COLOR: #808080">///</span><span style="COLOR: #008000">问题所在</span><span style="COLOR: #808080"></span></span><br /><img src="http://www.cppblog.com/images/OutliningIndicators/None.gif" align="top" /><span style="COLOR: #0000ff">double</span><span style="COLOR: #000000"> val </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> var.decVal.Lo32;<br /><img src="http://www.cppblog.com/images/OutliningIndicators/None.gif" align="top" /><br /><img src="http://www.cppblog.com/images/OutliningIndicators/None.gif" align="top" /></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">应改为 </span><span style="COLOR: #008000"><br /><img src="http://www.cppblog.com/images/OutliningIndicators/None.gif" align="top" /></span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">double</span><span style="COLOR: #000000"> val</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">var.decVal.Lo64;</span></div><br />当数据较大时数据被截断，灵异事件就出现了。 <img src ="http://www.cppblog.com/ivenher/aggbug/16638.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-12-20 11:24 <a href="http://www.cppblog.com/ivenher/articles/16638.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>调用存储过程</title><link>http://www.cppblog.com/ivenher/articles/9655.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Mon, 10 Jul 2006 11:34:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/9655.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/9655.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/9655.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/9655.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/9655.html</trackback:ping><description><![CDATA[ ptrCmd-&gt;CommandText = "P_KS_ADDUSER";<br />  ptrCmd-&gt;CommandType = adCmdStoredProc;<br />  ptrCmd-&gt;Execute(NULL,NULL,adCmdStoredProc);<img src ="http://www.cppblog.com/ivenher/aggbug/9655.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-07-10 19:34 <a href="http://www.cppblog.com/ivenher/articles/9655.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Visual C++ ADO数据库编程入门（下）</title><link>http://www.cppblog.com/ivenher/articles/2294.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 30 Dec 2005 10:32:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/2294.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/2294.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/2294.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/2294.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/2294.html</trackback:ping><description><![CDATA[<DIV class=postText>10、邦定数据<BR><BR>　　定义一个绑定类，将其成员变量绑定到一个指定的记录集，以方便于访问记录集的字段值。<BR><BR>　　(1). 从CADORecordBinding派生出一个类：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>class CCustomRs : public CADORecordBinding<BR>{<BR>BEGIN_ADO_BINDING(CCustomRs)<BR>ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_szau_fname, <BR>sizeof(m_szau_fname), lau_fnameStatus, false)<BR>ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_szau_lname, <BR>sizeof(m_szau_lname), lau_lnameStatus, false)<BR>ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_szphone, <BR>sizeof(m_szphone), lphoneStatus, true)<BR>END_ADO_BINDING()<BR><BR>public:<BR>CHAR m_szau_fname[22];<BR>ULONG lau_fnameStatus;<BR>CHAR m_szau_lname[42];<BR>ULONG lau_lnameStatus;<BR>CHAR m_szphone[14];<BR>ULONG lphoneStatus;<BR>};</TD></TR></TBODY></TABLE><BR>　　其中将要绑定的字段与变量名用BEGIN_ADO_BINDING宏关联起来。每个字段对应于两个变量，一个存放字段的值，另一个存放字段的状态。字段用从1开始的序号表示，如1，2，3等等。<BR><BR>　　特别要注意的是：如果要绑定的字段是字符串类型，则对应的字符数组的元素个数一定要比字段长度大2（比如m_szau_fname[22]，其绑定的字段au_fname的长度实际是20），不这样绑定就会失败。我分析多出的2可能是为了存放字符串结尾的空字符null和BSTR字符串开头的一个字（表示BSTR的长度）。这个问题对于初学者来说可能是一个意想不到的问题。<BR><BR>　　CADORecordBinding类的定义在icrsint.h文件里，内容是：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>class CADORecordBinding<BR>{<BR>public:<BR>STDMETHOD_(const ADO_BINDING_ENTRY*, GetADOBindingEntries) (VOID) PURE;<BR>};<BR><BR>BEGIN_ADO_BINDING宏的定义也在icrsint.h文件里，内容是：<BR>#define BEGIN_ADO_BINDING(cls) public: \<BR>typedef cls ADORowClass; \<BR>const ADO_BINDING_ENTRY* STDMETHODCALLTYPE GetADOBindingEntries() { \<BR>static const ADO_BINDING_ENTRY rgADOBindingEntries[] = { <BR><BR>ADO_VARIABLE_LENGTH_ENTRY2宏的定义也在icrsint.h文件里：<BR>#define ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify)\<BR>{Ordinal, \<BR>DataType, \<BR>0, \<BR>0, \<BR>Size, \<BR>offsetof(ADORowClass, Buffer), \<BR>offsetof(ADORowClass, Status), \<BR>0, \<BR>classoffset(CADORecordBinding, ADORowClass), \<BR>Modify},<BR><BR>#define END_ADO_BINDING宏的定义也在icrsint.h文件里：<BR>#define END_ADO_BINDING() {0, adEmpty, 0, 0, 0, 0, 0, 0, 0, FALSE}};\<BR>return rgADOBindingEntries;}</TD></TR></TBODY></TABLE><BR>　　　(2). 绑定<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>_RecordsetPtr Rs1;<BR>IADORecordBinding *picRs=NULL;<BR>CCustomRs rs;<BR>......<BR>Rs1-&gt;QueryInterface(__uuidof(IADORecordBinding), <BR>(LPVOID*)&amp;picRs));<BR>picRs-&gt;BindToRecordset(&amp;rs);</TD></TR></TBODY></TABLE><BR>　　派生出的类必须通过IADORecordBinding接口才能绑定，调用它的BindToRecordset方法就行了。<BR><BR>　　(3). rs中的变量即是当前记录字段的值<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>//Set sort and filter condition:<BR>// Step 4: Manipulate the data<BR>Rs1-&gt;Fields-&gt;GetItem("au_lname")-&gt;Properties-&gt;GetItem("Optimize")-&gt;Value = true; <BR>Rs1-&gt;Sort = "au_lname ASC";<BR>Rs1-&gt;Filter = "phone LIKE '415 5*'";<BR><BR>Rs1-&gt;MoveFirst();<BR>while (VARIANT_FALSE == Rs1-&gt;EndOfFile)<BR>{<BR>printf("Name: %s\t %s\tPhone: %s\n", <BR>(rs.lau_fnameStatus == adFldOK ? rs.m_szau_fname : ""), <BR>(rs.lau_lnameStatus == adFldOK ? rs.m_szau_lname : ""),<BR>(rs.lphoneStatus == adFldOK ? rs.m_szphone : ""));<BR>if (rs.lphoneStatus == adFldOK)<BR>strcpy(rs.m_szphone, "777");<BR>TESTHR(picRs-&gt;Update(&amp;rs)); // Add change to the batch<BR>Rs1-&gt;MoveNext();<BR>}<BR>Rs1-&gt;Filter = (long) adFilterNone;<BR>......<BR>if (picRs) picRs-&gt;Release();<BR>Rs1-&gt;Close();<BR>pConn-&gt;Close();</TD></TR></TBODY></TABLE><BR>　　只要字段的状态是adFldOK，就可以访问。如果修改了字段，不要忘了先调用picRs的Update（注意不是Recordset的Update），然后才关闭，也不要忘了释放picRs（即picRs-&gt;Release();）。<BR><BR>　　(4). 此时还可以用IADORecordBinding接口添加新纪录<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>if(FAILED(picRs-&gt;AddNew(&amp;rs)))<BR>......</TD></TR></TBODY></TABLE><BR>　　11. 访问长数据<BR><BR>　　在Microsoft SQL中的长数据包括text、image等这样长类型的数据，作为二进制字节来对待。<BR><BR>　　可以用Field对象的GetChunk和AppendChunk方法来访问。每次可以读出或写入全部数据的一部分，它会记住上次访问的位置。但是如果中间访问了别的字段后，就又得从头来了。<BR><BR>　　请看下面的例子：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>//写入一张照片到数据库：<BR>VARIANT varChunk;<BR>SAFEARRAY *psa;<BR>SAFEARRAYBOUND rgsabound[1];<BR><BR>//VT_ARRAY │ VT_UI1<BR>CFile f("h:\\aaa.jpg",Cfile::modeRead);<BR>BYTE bVal[ChunkSize+1];<BR>UINT uIsRead=0;<BR>//Create a safe array to store the array of BYTES <BR>while(1)<BR>{<BR>uIsRead=f.Read(bVal,ChunkSize);<BR>if(uIsRead==0)break;<BR>rgsabound[0].cElements =uIsRead;<BR>rgsabound[0].lLbound = 0;<BR>psa = SafeArrayCreate(VT_UI1,1,rgsabound);<BR>for(long index=0;index&lt;uIsRead;index++) <BR>{<BR>if(FAILED(SafeArrayPutElement(psa,&amp;index,&amp;bVal[index])))<BR>::MessageBox(NULL,"啊，又出毛病了。","提示",MB_OK │ MB_ICONWARNING);<BR>}<BR>varChunk.vt = VT_ARRAY│VT_UI1;<BR>varChunk.parray = psa;<BR>try{<BR>m_pRecordset-&gt;Fields-&gt;GetItem("photo")-&gt;AppendChunk(varChunk); <BR>}<BR>catch (_com_error &amp;e)<BR>{<BR>CString str=(char*)e.Description();<BR>::MessageBox(NULL,str+"\n又出毛病了。","提示",MB_OK │ MB_ICONWARNING);<BR>}<BR>::VariantClear(&amp;varChunk);<BR>::SafeArrayDestroyData( psa);<BR>if(uIsRead&lt;ChunkSize)break;<BR>}//while(1) <BR>f.Close();<BR><BR>//从数据库读一张照片：<BR>CFile f;<BR>f.Open("h:\\bbb.jpg",Cfile::modeWrite│Cfile::modeCreate);<BR>long lPhotoSize = m_pRecordset-&gt;Fields-&gt;Item["photo"]-&gt;ActualSize; <BR>long lIsRead=0;<BR><BR>_variant_t varChunk;<BR>BYTE buf[ChunkSize];<BR>while(lPhotoSize&gt;0)<BR>{<BR>lIsRead=lPhotoSize&gt;=ChunkSize? ChunkSize:lPhotoSize;<BR>varChunk = m_pRecordset-&gt;Fields-&gt;<BR>Item["photo"]-&gt;GetChunk(lIsRead);<BR>for(long index=0;index&lt;lIsRead;index++) <BR>{ <BR>::SafeArrayGetElement(varChunk.parray,&amp;index,buf+index); <BR>}<BR>f.Write(buf,lIsRead);<BR>lPhotoSize-=lIsRead;<BR>}//while()<BR>f.Close();</TD></TR></TBODY></TABLE><BR><BR><SPAN class=f14><FONT size=3>　　12. 使用SafeArray问题<BR><BR>　　学会使用SafeArray也是很重要的，因为在ADO编程中经常要用。它的主要目的是用于automation中的数组型参数的传递。因为在网络环境中，数组是不能直接传递的，而必须将其包装成SafeArray。实质上SafeArray就是将通常的数组增加一个描述符，说明其维数、长度、边界、元素类型等信息。SafeArray也并不单独使用，而是将其再包装到VARIANT类型的变量中，然后才作为参数传送出去。在VARIANT的vt成员的值如果包含VT_ARRAY│...,那么它所封装的就是一个SafeArray，它的parray成员即是指向SafeArray的指针。SafeArray中元素的类型可以是VARIANT能封装的任何类型，包括VARIANT类型本身。 <BR><BR>　　使用SafeArray的具体步骤：<BR><BR>　　方法一：<BR><BR>　　包装一个SafeArray：<BR><BR>　　(1). 定义变量，如：<BR><BR></FONT>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>VARIANT varChunk;<BR>SAFEARRAY *psa;<BR>SAFEARRAYBOUND rgsabound[1];</TD></TR></TBODY></TABLE><BR>　　(2). 创建SafeArray描述符：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>uIsRead=f.Read(bVal,ChunkSize);//read array from a file.<BR>if(uIsRead==0)break;<BR>rgsabound[0].cElements =uIsRead;<BR>rgsabound[0].lLbound = 0;<BR>psa = SafeArrayCreate(VT_UI1,1,rgsabound);</TD></TR></TBODY></TABLE><BR>　　(3). 放置数据元素到SafeArray：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>for(long index=0;index&lt;uIsRead;index++) <BR>{<BR>if(FAILED(SafeArrayPutElement(psa,&amp;index,&amp;bVal[index])))<BR>::MessageBox(NULL,"出毛病了。","提示",MB_OK │ MB_ICONWARNING);<BR>}</TD></TR></TBODY></TABLE><BR>　　一个一个地放，挺麻烦的。<BR><BR>　　(4). 封装到VARIANT内：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>varChunk.vt = VT_ARRAY│VT_UI1;<BR>varChunk.parray = psa;</TD></TR></TBODY></TABLE><BR>　　这样就可以将varChunk作为参数传送出去了。<BR><BR>　　读取SafeArray中的数据的步骤：<BR><BR>　　(1). 用SafeArrayGetElement一个一个地读<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>BYTE buf[lIsRead];<BR>for(long index=0;index&lt;lIsRead;index++) <BR>{ <BR>::SafeArrayGetElement(varChunk.parray,&amp;index,buf+index); <BR>}</TD></TR></TBODY></TABLE><BR>　　就读到缓冲区buf里了。<BR><BR>　　方法二：<BR><BR>　　使用SafeArrayAccessData直接读写SafeArray的缓冲区：<BR><BR>　　(1). 读缓冲区：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>BYTE *buf;<BR>SafeArrayAccessData(varChunk.parray, (void **)&amp;buf);<BR>f.Write(buf,lIsRead);<BR>SafeArrayUnaccessData(varChunk.parray);</TD></TR></TBODY></TABLE><BR>　　(2). 写缓冲区：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>BYTE *buf;<BR>::SafeArrayAccessData(psa, (void **)&amp;buf);<BR>for(long index=0;index&lt;uIsRead;index++) <BR>{<BR>buf[index]=bVal[index]; <BR>}<BR>::SafeArrayUnaccessData(psa);<BR><BR>varChunk.vt = VT_ARRAY│VT_UI1;<BR>varChunk.parray = psa;</TD></TR></TBODY></TABLE><BR>　　这种方法读写SafeArray都可以，它直接操纵SafeArray的数据缓冲区，比用SafeArrayGetElement和SafeArrayPutElement速度快。特别适合于读取数据。但用完之后不要忘了调用::SafeArrayUnaccessData(psa)，否则会出错的。<BR><BR>　　13. 使用书签( bookmark )<BR><BR>　　书签可以唯一标识记录集中的一个记录，用于快速地将当前记录移回到已访问过的记录，以及进行过滤等等。Provider会自动为记录集中的每一条记录产生一个书签，我们只需要使用它就行了。我们不能试图显示、修改或比较书签。ADO用记录集的Bookmark属性表示当前记录的书签。<BR><BR>　　用法步骤：<BR><BR>　　(1). 建立一个VARIANT类型的变量<BR><BR>_variant_t VarBookmark;<BR><BR>　　(2). 将当前记录的书签值存入该变量<BR><BR>　　也就是记录集的Bookmark属性的当前值。<BR><BR>VarBookmark = rst-&gt;Bookmark;<BR><BR>　　(3). 返回到先前的记录<BR><BR>　　将保存的书签值设置到记录集的书签属性中：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>// Check for whether bookmark set for a record<BR>if (VarBookmark.vt == VT_EMPTY)<BR>printf("No Bookmark set!\n");<BR>else <BR>rst-&gt;Bookmark = VarBookmark;</TD></TR></TBODY></TABLE><BR>　　设置完后，当前记录即会移动到该书签指向的记录。<BR><BR><BR><SPAN class=f14><FONT size=3>　　14、设置过滤条件<BR><BR>　　Recordset对象的Filter属性表示了当前的过滤条件。它的值可以是以AND或OR连接起来的条件表达式（不含WHERE关键字）、由书签组成的数组或ADO提供的FilterGroupEnum枚举值。为Filter属性设置新值后Recordset的当前记录指针会自动移动到满足过滤条件的第一个记录。例如：<BR><BR></FONT>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>rst-&gt;Filter = _bstr_t ("姓名='赵薇' AND 性别=’女’");</TD></TR></TBODY></TABLE><BR>　　在使用条件表达式时应注意下列问题：<BR><BR>　　（1）、可以用圆括号组成复杂的表达式<BR><BR>　　例如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>rst-&gt;Filter = _bstr_t ("(姓名='赵薇' AND 性别=’女’) OR AGE&lt;25");</TD></TR></TBODY></TABLE><BR>　　但是微软不允许在括号内用OR，然后在括号外用AND，例如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>rst-&gt;Filter = _bstr_t ("(姓名='赵薇' OR 性别=’女’) AND AGE&lt;25");</TD></TR></TBODY></TABLE><BR>　　必须修改为：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>rst-&gt;Filter = _bstr_t ("(姓名='赵薇' AND AGE&lt;25) OR (性别=’女’ AND AGE&lt;25)");</TD></TR></TBODY></TABLE><BR>　　（2）、表达式中的比较运算符可以是LIKE<BR><BR>　　LIKE后被比较的是一个含有通配符*的字符串，星号表示若干个任意的字符。<BR><BR>　　字符串的首部和尾部可以同时带星号*<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>rst-&gt;Filter = _bstr_t ("姓名 LIKE '*赵*' ");</TD></TR></TBODY></TABLE><BR>　　也可以只是尾部带星号：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>rst-&gt;Filter = _bstr_t ("姓名 LIKE '赵*' ");</TD></TR></TBODY></TABLE><BR>　　Filter属性值的类型是Variant，如果过滤条件是由书签组成的数组，则需将该数组转换为SafeArray，然后再封装到一个VARIANT或_variant_t型的变量中，再赋给Filter属性。<BR><BR>　　15、索引与排序<BR><BR>　　（1）、建立索引<BR><BR>　　当以某个字段为关键字用Find方法查找时，为了加快速度可以以该字段为关键字在记录集内部临时建立索引。只要将该字段的Optimize属性设置为true即可，例如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>pRst-&gt;Fields-&gt;GetItem("姓名")-&gt;Properties-&gt;<BR>GetItem("Optimize")-&gt;PutValue("True");<BR>pRst-&gt;Find("姓名 = '赵薇'",1,adSearchForward);<BR>......<BR>pRst-&gt;Fields-&gt;GetItem("姓名")-&gt;Properties-&gt;<BR>GetItem("Optimize")-&gt;PutValue("False");<BR>pRst-&gt;Close();</TD></TR></TBODY></TABLE><BR>　　说明：Optimize属性是由Provider提供的属性（在ADO中称为动态属性），ADO本身没有此属性。<BR><BR>　　（2）、排序<BR><BR>　　要排序也很简单，只要把要排序的关键字列表设置到Recordset对象的Sort属性里即可，例如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>pRstAuthors-&gt;CursorLocation = adUseClient;<BR>pRstAuthors-&gt;Open("SELECT * FROM mytable",<BR>_variant_t((IDispatch *) pConnection),<BR>adOpenStatic, adLockReadOnly, adCmdText);<BR>......<BR>pRst-&gt;Sort = "姓名 DESC, 年龄 ASC";</TD></TR></TBODY></TABLE><BR>　　关键字（即字段名）之间用逗号隔开，如果要以某关键字降序排序，则应在该关键字后加一空格，再加DESC（如上例）。升序时ASC加不加无所谓。本操作是利用索引进行的，并未进行物理排序，所以效率较高。<BR>但要注意，在打开记录集之前必须将记录集的CursorLocation属性设置为adUseClient，如上例所示。Sort属性值在需要时随时可以修改。<BR><BR>　　16、事务处理<BR><BR>　　ADO中的事务处理也很简单，只需分别在适当的位置调用Connection对象的三个方法即可，这三个方法是：<BR><BR>　　（1）、在事务开始时调用<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>pCnn-&gt;BeginTrans();</TD></TR></TBODY></TABLE><BR>　　（2）、在事务结束并成功时调用<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>pCnn-&gt;CommitTrans ();</TD></TR></TBODY></TABLE><BR>　　（3）、在事务结束并失败时调用<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>pCnn-&gt;RollbackTrans ();</TD></TR></TBODY></TABLE><BR>　　在使用事务处理时，应尽量减小事务的范围，即减小从事务开始到结束（提交或回滚）之间的时间间隔，以便提高系统效率。需要时也可在调用BeginTrans()方法之前，先设置Connection对象的IsolationLevel属性值，详细内容参见MSDN中有关ADO的技术资料。<BR><BR><SPAN class=f14><FONT size=3>　　<B>三、使用ADO编程常见问题解答</B><BR><BR>　　以下均是针对MS SQL 7.0编程时所遇问题进行讨论。<BR><BR>　　1、连接失败可能原因<BR><BR>　　Enterprise Managemer内，打开将服务器的属性对话框，在Security选项卡中，有一个选项Authentication。<BR><BR>　　如果该选项是Windows NT only，则你的程序所用的连接字符串就一定要包含Trusted_Connection参数，并且其值必须为yes，如：<BR><BR></FONT>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>"Provider=SQLOLEDB;Server=888;Trusted_Connection=yes"<BR>";Database=master;uid=lad;";</TD></TR></TBODY></TABLE><BR>　　如果不按上述操作，程序运行时连接必然失败。<BR><BR>　　如果Authentication选项是SQL Server and Windows NT，则你的程序所用的连接字符串可以不包含Trusted_Connection参数，如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>"Provider=SQLOLEDB;Server=888;Database=master;uid=lad;pwd=111;";</TD></TR></TBODY></TABLE><BR>　　因为ADO给该参数取的默认值就是no，所以可以省略。我认为还是取默认值比较安全一些。<BR><BR>　　2、改变当前数据库的方法<BR><BR>　　使用Tansct-SQL中的USE语句即可。<BR><BR>　　3、如何判断一个数据库是否存在<BR><BR>　　(1)、可打开master数据库中一个叫做SCHEMATA的视图，其内容列出了该服务器上所有的数据库名称。<BR><BR>　　(2) 、更简便的方法是使用USE语句，成功了就存在；不成功，就不存在。例如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>try{<BR>m_pConnect-&gt;Execute ( _bstr_t("USE INSURANCE_2002"),NULL,<BR>adCmdText│adExecuteNoRecords );<BR>}<BR>catch (_com_error &amp;e)<BR>{<BR>blSuccess=FALSE;<BR>CString str="数据库INSURANCE_2002不存在！\n";<BR>str+=e.Description();<BR>::MessageBox(NULL,str,"警告",MB_OK │ MB_ICONWARNING); <BR>}</TD></TR></TBODY></TABLE><BR>　　4、判断一个表是否存在<BR><BR>　　（1）、同样判断一个表是否存在，也可以用是否成功地打开它来判断，十分方便，例如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>try{<BR>m_pRecordset-&gt;Open(_variant_t("mytable"),<BR>_variant_t((IDispatch *)m_pConnection,true), adOpenKeyset,<BR>adLockOptimistic, adCmdTable);<BR>}<BR>catch (_com_error &amp;e)<BR>{<BR>::MessageBox(NULL,"该表不存在。","提示",MB_OK │ MB_ICONWARNING);<BR>}</TD></TR></TBODY></TABLE><BR>　　(2)、要不然可以采用麻烦一点的办法，就是在MS-SQL服务器上的每个数据库中都有一个名为sysobjects的表，查看此表的内容即知指定的表是否在该数据库中。<BR><BR>　　(3)、同样，每个数据库中都有一个名为TABLES的视图(View)，查看此视图的内容即知指定的表是否在该数据库中。<BR><BR>　　5、类型转换问题<BR><BR>　　（1）、类型VARIANT_BOOL<BR><BR>　　类型VARIANT_BOOL等价于short类型。The VARIANT_BOOL is equivalent to short. see it's definition below: <BR>typdef short VARIANT_BOOL<BR><BR>　　（2）、_com_ptr_t类的类型转换<BR><BR>　　_ConnectionPtr可以自动转换成IDspatch*类型，这是因为_ConnectionPtr实际上是_com_ptr_t类的一个实例，而这个类有此类型转换函数。<BR><BR>　　同理，_RecordsetPtr和_CommandPtr也都可以这样转换。<BR><BR>　　（3）、_bstr_t和_variant_t类<BR><BR>　　在ADO编程时，_bstr_t和_variant_t这两个类很有用，省去了许多BSTR和VARIANT类型转换的麻烦。<BR><BR>　　6、打开记录集时的问题<BR><BR>　　在打开记录集时，在调用Recordset的Open方法时，其最后一个参数里一定不能包含adAsyncExecute，否则将因为是异步操作，在读取数据时无法读到数据。<BR><BR>　　7、异常处理问题<BR><BR>　　对所有调用ADO的语句一定要用try和catch语句捕捉异常，否则在发生异常时，程序会异常退出。<BR><BR>　　8、使用SafeArray问题<BR><BR>　　在初学使用中，我曾遇到一个伤脑筋的问题，一定要注意：<BR><BR>　　在定义了SAFEARRAY的指针后，如果打算重复使用多次，则在中间可以调用::SafeArrayDestroyData释放数据，但决不能调用::SafeArrayDestroyDescriptor，否则必然出错，即使调用SafeArrayCreate也不行。例如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>SAFEARRAY *psa;<BR>......<BR>//When the data are no longer to be used:<BR>::SafeArrayDestroyData( psa);</TD></TR></TBODY></TABLE><BR>　　我分析在定义psa指针时，一个SAFEARRAY的实例（也就是SAFEARRAY描述符）也同时被自动建立了。但是只要一调用::SafeArrayDestroyDescriptor，描述符就被销毁了。<BR><BR>　　所以我认为::SafeArrayDestroyDescriptor可以根本就不调用，即使调用也必须在最后调用。<BR><BR>　　9、重复使用命令对象问题<BR><BR>　　一个命令对象如果要重复使用多次（尤其是带参数的命令），则在第一次执行之前，应将它的Prepared属性设置为TRUE。这样会使第一次执行减慢，但却可以使以后的执行全部加快。<BR><BR>　　10、绑定字符串型字段问题<BR><BR>　　如果要绑定的字段是字符串类型，则对应的字符数组的元素个数一定要比字段长度大2（比如m_szau_fname[22]，其绑定的字段au_fname的长度实际是20），不这样绑定就会失败。<BR><BR>　　11、使用AppendChunk的问题<BR><BR>　　当用AddNew方法刚刚向记录集内添加一个新记录之后，不能首先向一个长数据字段（image类型）写入数据，必须先向其他字段写入过数据之后，才能调用AppendChunk写该字段，否则出错。也就是说，AppendChunk不能紧接在AddNew之后。另外，写入其他字段后还必须紧接着调用AppendChunk，而不能调用记录集的Update方法后，才调用AppendChunk，否则调用AppendChunk时也会出错。换句话说，就是必须AppendChunk在前，Update在后。因而这个时候就不能使用带参数的AddNew了，因为带参数的AddNew会自动调用记录集的Update，所以AppendChunk就跑到Update的后面了，就只有出错了！因此，这时应该用不带参数的AddNew。<BR><BR>　　我推测这可能是MS SQL 7.0的问题，在MS SQL 2000中则不存在这些问题，但是AppendChunk仍然不能在Update之后。<BR><BR>　　<B>四、小结</B><BR><BR>　　一般情况下，Connection和Command的Execute用于执行不产生记录集的命令，而Recordset的Open用于产生一个记录集，当然也不是绝对的。特别Command主要是用于执行参数化的命令，可以直接由Command对象执行，也可以将Command对象传递给Recordset的Open。</SPAN></SPAN></SPAN></DIV><BR></SPAN><img src ="http://www.cppblog.com/ivenher/aggbug/2294.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-30 18:32 <a href="http://www.cppblog.com/ivenher/articles/2294.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Visual C++ ADO数据库编程入门（上）</title><link>http://www.cppblog.com/ivenher/articles/2293.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 30 Dec 2005 10:31:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/2293.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/2293.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/2293.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/2293.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/2293.html</trackback:ping><description><![CDATA[<DIV class=postText>　　ADO 是目前在Windows环境中比较流行的客户端数据库编程技术。ADO是建立在OLE DB底层技术之上的高级编程接口，因而它兼具有强大的数据处理功能（处理各种不同类型的数据源、分布式的数据处理等等）和极其简单、易用的编程接口，因而得到了广泛的应用。而且按微软公司的意图，OLE DB和ADO将逐步取代 ODBC和DAO。现在介绍ADO各种应用的文章和书籍有很多，本文着重站在初学者的角度，简要探讨一下在VC++中使用ADO编程时的一些问题。我们希望阅读本文之前，您对ADO技术的基本原理有一些了解。<BR><BR>　　<B>一、在VC++中使用ADO编程</B><BR><BR>　　ADO实际上就是由一组Automation对象构成的组件，因此可以象使用其它任何Automation对象一样使用ADO。ADO中最重要的对象有三个：Connection、Command和Recordset，它们分别表示连接对象、命令对象和记录集对象。如果您熟悉使用MFC中的ODBC类（CDatabase、CRecordset)编程，那么学习ADO编程就十分容易了。<BR><BR>　　使用ADO编程时可以采用以下三种方法之一：<BR><BR>　　1、使用预处理指令#import<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#c0c0c0 border=1>
<TBODY>
<TR>
<TD>#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \<BR>no_namespace rename("EOF", "EndOfFile")</TD></TR></TBODY></TABLE><BR>　　但要注意不能放在stdAfx.h文件的开头，而应该放在所有include指令的后面。否则在编译时会出错。<BR>程序在编译过程中，VC++会读出msado15.dll中的类型库信息，自动产生两个该类型库的头文件和实现文件msado15.tlh和msado15.tli（在您的Debug或Release目录下）。在这两个文件里定义了ADO的所有对象和方法，以及一些枚举型的常量等。我们的程序只要直接调用这些方法就行了，与使用MFC中的COleDispatchDriver类调用Automation对象十分类似。<BR><BR>　　2、使用MFC中的CIDispatchDriver<BR><BR>　　就是通过读取msado15.dll中的类型库信息，建立一个COleDispatchDriver类的派生类，然后通过它调用ADO对象。<BR><BR>　　3、直接用COM提供的API<BR><BR>　　如使用如下代码：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>CLSID clsid;<BR>HRESULT hr = ::CLSIDFromProgID(L"ADODB.Connection", &amp;clsid);<BR>if(FAILED(hr))<BR>{...}<BR>::CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)<BR>&amp;pDispatch);<BR>if(FAILED(hr))<BR>{...}</TD></TR></TBODY></TABLE><BR>　　以上三种方法，第一和第二种类似，可能第一种好用一些，第三种编程可能最麻烦。但可能第三种方法也是效率最高的，程序的尺寸也最小，并且对ADO的控制能力也最强。<BR><BR>　　据微软资料介绍，第一种方法不支持方法调用中的默认参数，当然第二种方法也是这样，但第三种就不是这样了。采用第三种方法的水平也最高。当你需要绕过ADO而直接调用OLE DB底层的方法时，就一定要使用第三种方法了。<BR><BR>　　ADO编程的关键，就是熟练地运用ADO提供的各种对象(object)、方法(method)、属性(property)和容器（collection）。另外，如果是在MS SQL或Oracle等大型数据库上编程，还要能熟练使用SQL语言。<BR><BR><BR><SPAN class=f14><FONT size=3>　　<B>二、使用#import方法的编程步骤</B><BR><BR>　　这里建议您使用#import的方法，因为它易学、易用，代码也比较简洁。<BR><BR>　　1、 添加#import指令<BR><BR>　　打开stdafx.h文件，将下列内容添加到所有的include指令之后：<BR><BR></FONT>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>#include &lt;icrsint.h&gt; //Include support for VC++ Extensions<BR>#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \<BR>no_namespace rename("EOF", "adoEOF")</TD></TR></TBODY></TABLE><BR>　　其中icrsint.h文件包含了VC++扩展的一些预处理指令、宏等的定义，用于COM编程时使用。<BR><BR>　　2、定义_ConnectionPtr型变量，并建立数据库连接<BR><BR>　　建立了与数据库服务器的连接后，才能进行其他有关数据库的访问和操作。ADO使用Connection对象来建立与数据库服务器的连接，所以它相当于MFC中的CDatabase类。和CDatabase类一样，调用Connection对象的Open方法即可建立与服务器的连接。<BR><BR>　　数据类型 _ConnectionPtr实际上就是由类模板_com_ptr_t而得到的一个具体的实例类，其定义可以到msado15.tlh、comdef.h 和comip.h这三个文件中找到。在msado15.tlh中有：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>_COM_SMARTPTR_TYPEDEF(_Collection, __uuidof(_Collection));</TD></TR></TBODY></TABLE><BR>　　经宏扩展后就得到了_ConnectionPtr类。_ConnectionPtr类封装了Connection对象的Idispatch接口指针，及一些必要的操作。我们就是通过这个指针来操纵Connection对象。类似地，后面用到的_CommandPtr和_RecordsetPtr类型也是这样得到的，它们分别表示命令对象指针和记录集对象的指针。<BR><BR>　　（1）、连接到MS SQL Server<BR><BR>　　注意连接字符串的格式，提供正确的连接字符串是成功连接到数据库服务器的第一步，有关连接字符串的详细信息参见微软MSDN Library光盘。<BR><BR>　　本例连接字符串中的server_name，database_name，user_name和password在编程时都应该替换成实际的内容。<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>_ConnectionPtr pMyConnect=NULL;<BR>HRESULT hr=pMyConnect.CreateInstance(__uuidof(Connection)));<BR>if(FAILED(hr))return;<BR><BR>_bstr_t strConnect="Provider=SQLOLEDB; Server=server_name;"<BR>"Database=database_name; uid=user_name; pwd=password;"; <BR>//connecting to the database server now:<BR>try{pMyConnect-&gt;Open(strConnect,"","",NULL);}<BR>catch (_com_error &amp;e)<BR>{<BR>::MessageBox(NULL,e.Description(),"警告",MB_OK │ MB_ICONWARNING);<BR>}</TD></TR></TBODY></TABLE><BR>　　注意Connection对象的Open方法中的连接字符串参数必须是BSTR或_bstr_t类型。另外，本例是直接通过OLE DB Provider建立连接，所以无需建立数据源。<BR><BR>　　（2）、通过ODBC Driver连接到Database Server连接字符串格式与直接用ODBC编程时的差不多：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>_bstr_t strConnect="DSN=datasource_name; Database=database_name; uid=user_name; pwd=password;";</TD></TR></TBODY></TABLE><BR>　　此时与ODBC编程一样，必须先建立数据源。<BR><BR>　　3、定义_RecordsetPtr型变量，并打开数据集<BR><BR>　　定义_RecordsetPtr型变量，然后通过它调用Recordset对象的Open方法，即可打开一个数据集。所以Recordset对象与MFC中的CRecordset类类似，它也有当前记录、当前记录指针的概念。如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>_RecordsetPtr m_pRecordset;<BR>if(!FAILED(m_pRecordset.CreateInstance( __uuidof( Recordset )))<BR>{<BR>m_pDoc-&gt;m_initialized=FALSE;<BR>return;<BR>}<BR><BR>try{<BR>m_pRecordset-&gt;Open(_variant_t("mytable"),<BR>_variant_t((IDispatch *)pMyConnect,true), adOpenKeyset,<BR>adLockOptimistic, adCmdTable);<BR>}<BR>catch (_com_error &amp;e)<BR>{<BR>::MessageBox(NULL,"无法打开mytable表。","提示",<BR>MB_OK │ MB_ICONWARNING);<BR>}</TD></TR></TBODY></TABLE><BR>　　Recordset对象的Open方法非常重要，它的第一个参数可以是一个SQL语句、一个表的名字或一个命令对象等等；第二个参数就是前面建立的连接对象的指针。此外，用Connection和Command对象的Execute方法也能得到记录集，但是只读的。<BR><BR><BR><SPAN class=f14><FONT size=3>　　4、读取当前记录的数据<BR><BR>　　我认为读取数据的最方便的方法如下：<BR><BR></FONT>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>try{<BR>m_pRecordset-&gt;MoveFirst(); <BR>while(m_pRecordset-&gt;adoEOF==VARIANT_FALSE) <BR>{<BR>//Retrieve column's value: <BR>CString sName=(char*)(_bstr_t)(m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("name"))-&gt;Value);<BR>short cAge=(short)(m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("age"))-&gt;Value);<BR>//Do something what you want to do:<BR>......<BR>m_pRecordset-&gt;MoveNext(); <BR>}<BR>}//try<BR>catch (_com_error &amp;e)<BR>{<BR>CString str=(char*)e.Description();<BR>::MessageBox(NULL,str+"\n又出毛病了。","提示",<BR>MB_OK │ MB_ICONWARNING);<BR>}</TD></TR></TBODY></TABLE><BR>　　本例中的name和age都是字段名，读取的字段值分别保存在sName和cAge变量内。例中的Fields是Recordset对象的容器，GetItem方法返回的是Field对象，而Value则是Field对象的一个属性（即该字段的值）。通过此例，应掌握操纵对象属性的方法。例如，要获得Field 对象的Value属性的值可以直接用属性名Value来引用它（如上例），但也可以调用Get方法，例如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>CString sName=(char*)(_bstr_t)(m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("name"))-&gt;GetValue());</TD></TR></TBODY></TABLE><BR>　　从此例还可以看到，判断是否到达记录集的末尾，使用记录集的adoEOF属性，其值若为真即到了结尾，反之则未到。判断是否到达记录集开头，则可用BOF属性。<BR><BR>　　另外，读取数据还有一个方法，就是定义一个绑定的类，然后通过绑定的变量得到字段值（详见后面的介绍）。<BR><BR>　　5、修改数据<BR><BR>　　方法一：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>try{<BR>m_pRecordset-&gt;MoveFirst(); <BR>while(m_pRecordset-&gt;adoEOF==VARIANT_FALSE) <BR>{ <BR>m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("姓名"))-&gt;Value=_bstr_t("赵薇");<BR>......<BR>m_pRecordset-&gt;Update();<BR><BR>m_pRecordset-&gt;MoveNext(); <BR>}<BR>}//try</TD></TR></TBODY></TABLE><BR>　　改变了Value属性的值，即改变了字段的值。<BR><BR>　　方法二：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("姓名"))-&gt;PutValue(_bstr_t("赵薇"));</TD></TR></TBODY></TABLE><BR>　　方法三：就是用定义绑定类的方法（详见后面的介绍）。<BR><BR>　　6、添加记录<BR><BR>　　新记录添加成功后，即自动成为当前记录。AddNew方法有两种形式，一个含有参数，而另一个则不带参数。<BR><BR>　　方法一（不带参数）：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>// Add new record into this table:<BR>try{<BR>if(!m_pRecordset-&gt;Supports(adAddNew)) return;<BR><BR>m_pRecordset-&gt;AddNew(); <BR>m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("姓名"))-&gt;Value=_bstr_t("赵薇");<BR>m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("性别"))-&gt;Value=_bstr_t("女");<BR>m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("age"))-&gt;Value=_variant_t((short)20);<BR>m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("marry"))-&gt;Value=_bstr_t("未婚");<BR>m_pRecordset-&gt;Update(); <BR>}//try<BR>catch (_com_error &amp;e)<BR>{<BR>::MessageBox(NULL, "又出毛病了。","提示",MB_OK │ MB_ICONWARNING);<BR>}</TD></TR></TBODY></TABLE><BR>　　这种方法弄完了还要调用Update()。<BR><BR>　　方法二（带参数）：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>_variant_t varName[4],narValue[4];<BR>varName[0] = L"姓名";<BR>varName[1] = L"性别";<BR>varName[2] = L"age";<BR>varName[3] = L"marry";<BR>narValue[0]=_bstr_t("赵薇");<BR>narValue[1]=_bstr_t("女");<BR>narValue[2]=_variant_t((short)20);<BR>narValue[3]=_bstr_t("未婚");<BR><BR>const int nCrit = sizeof varName / sizeof varName[0];<BR>// Create SafeArray Bounds and initialize the array<BR>SAFEARRAYBOUND rgsaName[1],rgsaValue[1];<BR>rgsaName[0].lLbound = 0; <BR>rgsaName[0].cElements = nCrit;<BR>SAFEARRAY *psaName = SafeArrayCreate( VT_VARIANT, 1, rgsaName );<BR>rgsaValue[0].lLbound = 0;<BR>rgsaValue[0].cElements = nCrit;<BR>SAFEARRAY *psaValue = SafeArrayCreate( VT_VARIANT, 1, rgsaValue );<BR>// Set the values for each element of the array<BR>HRESULT hr1=S_OK.hr2=S_OK;<BR>for( long i = 0 ; i &lt; nCrit &amp;&amp; SUCCEEDED( hr1 ) &amp;&amp; SUCCEEDED( hr2 );i++) <BR>{ <BR>hr1=SafeArrayPutElement(psaName, &amp;i,&amp;varName[i]);<BR>hr2=SafeArrayPutElement(psaValue, &amp;i,&amp;narValue[i]); }<BR><BR>// Initialize and fill the SafeArray<BR>VARIANT vsaName,vsaValue; <BR>vsaName.vt = VT_VARIANT │ VT_ARRAY;<BR>vsaValue.vt = VT_VARIANT │ VT_ARRAY;<BR>V_ARRAY(&amp;vsaName) = psaName;//&amp;vsaName-&gt;parray=psaName;<BR>//see definition in oleauto.h file.<BR>V_ARRAY(&amp;vsaValue) = psaValue;<BR><BR>// Add a new record:<BR>m_pRecordset-&gt;AddNew(vsaName,vsaValue);</TD></TR></TBODY></TABLE><BR>　　这种方法不需要调用Update，因为添加后，ADO会自动调用它。此方法主要是使用SafeArray挺麻烦。<BR><BR>　　方法三：就是用定义绑定类的方法（详见后面的介绍）。<BR><BR><FONT size=3>　7、删除记录<BR><BR>　　调用Recordset的Delete方法就行了，删除的是当前记录。要了解Delete的其它用法请查阅参考文献。<BR><BR></FONT>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>try{<BR>m_pRecordset-&gt;MoveFirst(); <BR>while(m_pRecordset-&gt;adoEOF==VARIANT_FALSE) <BR>{<BR>CString sName=(char*)(_bstr_t)(m_pRecordset-&gt;Fields-&gt;GetItem<BR>(_variant_t("姓名"))-&gt;Value);<BR>if(::MessageBox(NULL,"姓名="+sName+"\n删除她吗？",<BR>"提示",MB_YESNO │ MB_ICONWARNING)==IDYES)<BR>{<BR>m_pRecordset-&gt;Delete(adAffectCurrent); <BR>m_pRecordset-&gt;Update();<BR>}<BR>m_pRecordset-&gt;MoveNext(); <BR>}<BR>}//try<BR>catch (_com_error &amp;e)<BR>{<BR>::MessageBox(NULL,"又出毛病了。","提示",MB_OK │ MB_ICONWARNING);<BR>}</TD></TR></TBODY></TABLE><BR>　　8、使用带参数的命令<BR><BR>　　Command对象所代表的就是一个Provider能够理解的命令，如SQL语句等。使用Command对象的关键就是把表示命令的语句设置到CommandText属性中，然后调用Command对象的Execute方法就行了。一般情况下在命令中无需使用参数，但有时使用参数，可以增加其灵活性和效率。<BR><BR>　　(1). 建立连接、命令对象和记录集对象<BR><BR>　　本例中表示命令的语句就是一个SQL语句（SELECT语句）。SELECT语句中的问号?就代表参数，如果要多个参数，就多放几个问号，每个问号代表一个参数。<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>_ConnectionPtr Conn1;<BR>_CommandPtr Cmd1;<BR>ParametersPtr *Params1 = NULL; // Not an instance of a smart pointer.<BR>_ParameterPtr Param1;<BR>_RecordsetPtr Rs1;<BR><BR>try<BR>{<BR>// Create Connection Object (1.5 Version)<BR>Conn1.CreateInstance( __uuidof( Connection ) );<BR>Conn1-&gt;ConnectionString = bstrConnect;<BR>Conn1-&gt;Open( bstrEmpty, bstrEmpty, bstrEmpty, -1 );<BR>// Create Command Object<BR>Cmd1.CreateInstance( __uuidof( Command ) );<BR>Cmd1-&gt;ActiveConnection = Conn1;<BR>Cmd1-&gt;CommandText = _bstr_t("SELECT * FROM mytable WHERE age&lt; ?");<BR>}//try</TD></TR></TBODY></TABLE><BR>　　要注意命令对象必须与连接对象关联起来才能起作用，本例中将命令对象的ActiveConnection属性设置为连接对象的指针，即为此目的：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>Cmd1-&gt;ActiveConnection = Conn1;</TD></TR></TBODY></TABLE><BR>　　(2). 创建参数对象，并给参数赋值<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>// Create Parameter Object<BR>Param1 = Cmd1-&gt;CreateParameter( _bstr_t(bstrEmpty),<BR>adInteger,<BR>adParamInput,<BR>-1,<BR>_variant_t( (long) 5) );<BR>Param1-&gt;Value = _variant_t( (long) 5 );<BR>Cmd1-&gt;Parameters-&gt;Append( Param1 );</TD></TR></TBODY></TABLE><BR>　　用命令对象的方法来创建一个参数对象，其中的长度参数（第三个）如果是固定长度的类型，就填-1，如果是字符串等可变长度的就填其实际长度。Parameters是命令对象的一个容器，它的Append方法就是把创建的参数对象追加到该容器里。Append进去的参数按先后顺序与SQL语句中的问号从左至右一一对应。<BR><BR>　　(3). 执行命令打开记录集<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>// Open Recordset Object<BR>Rs1 = Cmd1-&gt;Execute( &amp;vtEmpty, &amp;vtEmpty2, adCmdText );</TD></TR></TBODY></TABLE><BR>　　但要注意，用Command和Connection对象的Execute方法得到的Recordset是只读的。因为在打开Recordset之前，我们无法设置它的LockType属性（其默认值为只读）。而在打开之后设置LockType不起作用。<BR><BR>　　我发现用上述方法得到记录集Rs1后，不但Rs1中的记录无法修改，即使直接用SQL语句修改同一表中任何记录都不行。<BR><BR>　　要想能修改数据，还是要用Recordset自己的Open方法才行，如：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>try{<BR>m_pRecordset-&gt;Open((IDispatch *) Cmd1, vtMissing,<BR>adOpenStatic, adLockOptimistic, adCmdUnspecified);<BR>}<BR>catch (_com_error &amp;e)<BR>{<BR>::MessageBox(NULL,"mytable表不存在。","提示",MB_OK │ MB_ICONWARNING);<BR>}</TD></TR></TBODY></TABLE><BR>　　Recordset对象的Open方法真是太好了，其第一个参数可以是SQL语句、表名字、命令对象指针等等。<BR><BR>　　9、响应ADO的通知事件<BR><BR>　　通知事件就是当某个特定事件发生时，由Provider通知客户程序，换句话说，就是由Provider调用客户程序中的一个特定的方法（即事件的处理函数）。所以为了响应一个事件，最关键的就是要实现事件的处理函数。<BR><BR>　　(1). 从ConnectionEventsVt接口派生出一个类<BR><BR>　　为了响应_Connection的通知事件，应该从ConnectionEventsVt接口派生出一个类：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>class CConnEvent : public ConnectionEventsVt<BR>{<BR>private:<BR>ULONG m_cRef;<BR>public:<BR>CConnEvent() { m_cRef = 0; };<BR>~CConnEvent() {};<BR><BR>STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);<BR>STDMETHODIMP_(ULONG) AddRef(void);<BR>STDMETHODIMP_(ULONG) Release(void);<BR>STDMETHODIMP raw_InfoMessage( <BR>struct Error *pError,<BR>EventStatusEnum *adStatus,<BR>struct _Connection *pConnection);<BR>STDMETHODIMP raw_BeginTransComplete( <BR>LONG TransactionLevel,<BR>struct Error *pError,<BR>EventStatusEnum *adStatus,<BR>struct _Connection *pConnection);<BR>......<BR>}; </TD></TR></TBODY></TABLE><BR>　　　(2). 实现每一个事件的处理函数(凡是带raw_前缀的方法都把它实现了)：<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>STDMETHODIMP CConnEvent::raw_InfoMessage( <BR>struct Error *pError,<BR>EventStatusEnum *adStatus,<BR>struct _Connection *pConnection)<BR>{<BR>*adStatus = adStatusUnwantedEvent;<BR>return S_OK;<BR>};</TD></TR></TBODY></TABLE><BR>　　有些方法虽然你并不需要，但也必须实现它，只需简单地返回一个S_OK即可。但如果要避免经常被调用，还应在其中将adStatus参数设置为adStatusUnwantedEvent，则在本次调用后，以后就不会被调用了。<BR>另外还必须实现QueryInterface, AddRef, 和Release三个方法: <BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>STDMETHODIMP CConnEvent::QueryInterface(REFIID riid, void ** ppv) <BR>{<BR>*ppv = NULL;<BR>if (riid == __uuidof(IUnknown) ││ <BR>riid == __uuidof(ConnectionEventsVt)) *ppv = this;<BR>if (*ppv == NULL)<BR>return ResultFromScode(E_NOINTERFACE);<BR>AddRef();<BR>return NOERROR;<BR>}<BR>STDMETHODIMP_(ULONG) CConnEvent::AddRef() { return ++m_cRef; };<BR>STDMETHODIMP_(ULONG) CConnEvent::Release()<BR>{ <BR>if (0 != --m_cRef) return m_cRef;<BR>delete this;<BR>return 0;<BR>}</TD></TR></TBODY></TABLE><BR>　　(3). 开始响应通知事件<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>// Start using the Connection events<BR>IConnectionPointContainer *pCPC = NULL;<BR>IConnectionPoint *pCP = NULL;<BR><BR>hr = pConn.CreateInstance(__uuidof(Connection));<BR>if (FAILED(hr)) return;<BR><BR>hr = pConn-&gt;QueryInterface(__uuidof(IConnectionPointContainer), <BR>(void **)&amp;pCPC);<BR>if (FAILED(hr)) return;<BR>hr = pCPC-&gt;FindConnectionPoint(__uuidof(ConnectionEvents), &amp;pCP);<BR>pCPC-&gt;Release();<BR>if (FAILED(hr)) return;<BR><BR>pConnEvent = new CConnEvent();<BR>hr = pConnEvent-&gt;QueryInterface(__uuidof(IUnknown), (void **) &amp;pUnk);<BR>if (FAILED(hr)) return rc; <BR>hr = pCP-&gt;Advise(pUnk, &amp;dwConnEvt);<BR>pCP-&gt;Release();<BR>if (FAILED(hr)) return;<BR><BR>pConn-&gt;Open("dsn=Pubs;", "sa", "", adConnectUnspecified); </TD></TR></TBODY></TABLE><BR>　　也就是说在连接(Open)之前就做这些事。<BR><BR>　　(4). 停止响应通知事件<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD>pConn-&gt;Close();<BR>// Stop using the Connection events<BR>hr = pConn-&gt;QueryInterface(__uuidof(IConnectionPointContainer), <BR>(void **) &amp;pCPC);<BR>if (FAILED(hr)) return;<BR>hr = pCPC-&gt;FindConnectionPoint(__uuidof(ConnectionEvents), &amp;pCP);<BR>pCPC-&gt;Release();<BR>if (FAILED(hr)) return rc;<BR>hr = pCP-&gt;Unadvise( dwConnEvt );<BR>pCP-&gt;Release();<BR>if (FAILED(hr)) return;</TD></TR></TBODY></TABLE><BR>　　在连接关闭之后做这件事。 </SPAN></SPAN></DIV><img src ="http://www.cppblog.com/ivenher/aggbug/2293.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-30 18:31 <a href="http://www.cppblog.com/ivenher/articles/2293.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>_variant_t 到　CString 转换</title><link>http://www.cppblog.com/ivenher/articles/2289.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 30 Dec 2005 08:45:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/2289.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/2289.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/2289.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/2289.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/2289.html</trackback:ping><description><![CDATA[<FONT size=2><FONT color=#333333><FONT face=宋体>数据类型转换函数<SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p></SPAN></FONT></FONT></FONT>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial">CString CZjyDlg::VariantToString(VARIANT var)<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial">{<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial">CString strValue;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>_variant_t var_t;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>_bstr_t bstr_t;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>time_t cur_time;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>CTime time_value;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>COleCurrency var_currency;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>switch(var.vt)<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>{<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_EMPTY:<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_NULL:strValue=_T("");break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_UI1:strValue.Format("%d",var.bVal);break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_I2:strValue.Format("%d",var.iVal);break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_I4:strValue.Format("%d",var.lVal);break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_R4:strValue.Format("%f",var.fltVal);break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_R8:strValue.Format("%f",var.dblVal);break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_CY:<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>var_currency=var;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>strValue=var_currency.Format(0);break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_BSTR:<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>var_t =var;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>bstr_t=var_t;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>strValue.Format("%s",(const char *)bstr_t);break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_DATE:<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>cur_time=var.date;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>time_value=cur_time;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>strValue.Format("%A,%B,%d,%Y");break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>case VT_BOOL:strValue.Format("%d",var.boolVal);break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>default:strValue=_T("");break;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial"><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>return strValue;<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: Arial">}<o:p></o:p></SPAN></P><img src ="http://www.cppblog.com/ivenher/aggbug/2289.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-30 16:45 <a href="http://www.cppblog.com/ivenher/articles/2289.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ado  出错处理</title><link>http://www.cppblog.com/ivenher/articles/2150.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Mon, 26 Dec 2005 12:13:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/2150.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/2150.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/2150.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/2150.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/2150.html</trackback:ping><description><![CDATA[//出错处理：<BR>3127——没有找到目标表<BR>3092——目标表已经存在<BR>例如：<BR>catch(const&nbsp;_com_error&nbsp;e)<BR>{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AfxMessageBox(e.Description());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;errorCode=e.WCode();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(3127==errorCode)&nbsp;AfxMessageBox("表不存在");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(3092==errorCode)&nbsp;AfxMessageBox("表已经存在");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;FALSE;<BR>}&nbsp;<BR><img src ="http://www.cppblog.com/ivenher/aggbug/2150.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-26 20:13 <a href="http://www.cppblog.com/ivenher/articles/2150.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用ADO操作数据库的方法步骤</title><link>http://www.cppblog.com/ivenher/articles/2149.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Mon, 26 Dec 2005 11:37:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/2149.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/2149.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/2149.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/2149.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/2149.html</trackback:ping><description><![CDATA[用ADO操作数据库的方法步骤<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>/***********************ADO接口简介 ***********************************/<BR>ADO库包含三个基本接口:_ConnectionPtr接口、_CommandPtr接口和_RecordsetPtr接口。 <BR><BR>_ConnectionPtr接口返回一个记录集或一个空指针。<BR>通常使用它来创建一个数据连接或执行一条不返回任何结果的SQL语句，如一个存储过程。<BR>使用_ConnectionPtr接口返回一个记录集不是一个好的使用方法。<BR>通常同Cdatabase一样，使用它创建一个数据连接，然后使用其它对象执行数据输入输出操作。<BR><BR>_CommandPtr接口返回一个记录集。<BR>它提供了一种简单的方法来执行返回记录集的存储过程和SQL语句。<BR>在使用_CommandPtr接口时，你可以利用全局_ConnectionPtr接口，也可以在_CommandPtr接口里直接使用连接串。<BR>如果你只执行一次或几次数据访问操作，后者是比较好的选择。<BR>但如果你要频繁访问数据库，并要返回很多记录集，那么，你应该使用全局_ConnectionPtr接口创建一个数据连接，<BR>然后使用_CommandPtr接口执行存储过程和SQL语句。<BR><BR><BR>_RecordsetPtr是一个记录集对象。<BR>与以上两种对象相比，它对记录集提供了更多的控制功能，如记录锁定，游标控制等<BR>。同_CommandPtr接口一样，它不一定要使用一个已经创建的数据连接，<BR>可以用一个连接串代替连接指针赋给_RecordsetPtr的connection成员变量，让它自己创建数据连接。<BR>如果你要使用多个记录集，最好的方法是同Command对象一样使用已经创建了数据连接的全局_ConnectionPtr接口<BR>，然后使用_RecordsetPtr执行存储过程和SQL语句。<BR><BR><BR>/***********************基本流程***********************************/<BR>(1)初始化COM库，引入ADO库定义文件<BR>(2)用Connection对象连接数据库<BR>(3)利用建立好的连接，通过Connection、Command对象执行SQL命令，或利用Recordset对象取得结果记<BR>&nbsp;&nbsp; 录集进行查询、处理。<BR>(4)使用完毕后关闭连接释放对象。<BR><BR><BR><BR>/***********************【1】COM库的初始化***********************************/<BR>我们可以使用AfxOleInit()来初始化COM库，这项工作通常在CWinApp::InitInstance()的重载函数中完<BR>成，请看如下代码:<BR><BR>BOOL CADOTest1App::InitInstance()<BR>{<BR>&nbsp;&nbsp;AfxOleInit();<BR>&nbsp;&nbsp;...... <BR>}<BR><BR><BR>/*****************【2】用#import指令引入ADO类型库**********************/<BR>我们在stdafx.h中加入如下语句：<BR>#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")<BR>其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。<BR><BR><BR><BR>/***************【3】创建Connection对象并连接数据库********************/<BR><BR>首先我们需要添加一个指向Connection对象的指针:<BR><BR>_ConnectionPtr m_pConnection;<BR><BR>BOOL CADOTest1Dlg::OnInitDialog()<BR>{<BR>CDialog::OnInitDialog();<BR>try<BR>{<BR>&nbsp;&nbsp;HRESULT hr = m_pConnection.CreateInstance("ADODB.Connection");//创建Connection对象<BR>&nbsp;&nbsp;if(SUCCEEDED(hr))<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp; hr = m_pConnection-&gt;Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);//连接数据库<BR>&nbsp;&nbsp; //上面一句中连接字串中的Provider是针对ACCESS2000环境的，对于ACCESS97,需要改为:Provider=Microsoft.Jet.OLEDB.3.51;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>}<BR>catch (_com_error e) //COM错误取得,当执行COM功能的时候，如果出错，可以捕捉到_com_error的异常<BR>{&nbsp;&nbsp;<BR>&nbsp;&nbsp;<FONT color=#006400>CString strComError;<BR>&nbsp;&nbsp;strComError.Format("错误编号: %08lx\n错误信息: %s\n错误源: %s\n错误描述: %s",<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.Error(),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 错误编号<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.ErrorMessage(),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 错误信息<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LPCSTR) e.Source(),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 错误源<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LPCSTR) e.Description());&nbsp;&nbsp;// 错误描述</FONT>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;::MessageBox(NULL,strComError,"错误",MB_ICONEXCLAMATION);<BR>}<BR>}<BR>也可以使用UDL文件进行连接。<BR>try<BR>{&nbsp;&nbsp;<BR>m_pConnection.CreateInstance(__uuidof(Connection));&nbsp;&nbsp; <BR>m_pConnection-&gt;ConnectionString ="File Name=e.udl"; <BR>m_pConnection-&gt;Open("","","",NULL); <BR>}<BR>catch(_com_error e)<BR>{....}<BR><BR>◆在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的，下面是该方法的原型<BR><BR>HRESULT Connection15::Open (_bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )<BR><BR>ConnectionString 为连接字串,<BR>UserID&nbsp;&nbsp;&nbsp;&nbsp;是用户名, <BR>Password&nbsp;&nbsp; 是登陆密码,<BR>Options&nbsp;&nbsp;&nbsp;&nbsp;是连接选项,用于指定Connection对象对数据的更新许可权, <BR>&nbsp;&nbsp;&nbsp;&nbsp; Options可以是如下几个常量:<BR><BR>&nbsp;&nbsp;adModeUnknown:&nbsp;&nbsp; 缺省。当前的许可权未设置<BR>&nbsp;&nbsp;adModeRead:&nbsp;&nbsp;&nbsp;&nbsp;只读<BR>&nbsp;&nbsp;adModeWrite:&nbsp;&nbsp; 只写<BR>&nbsp;&nbsp;adModeReadWrite:&nbsp;&nbsp;可以读写<BR>&nbsp;&nbsp;adModeShareDenyRead: 阻止其它Connection对象以读权限打开连接<BR>&nbsp;&nbsp;adModeShareDenyWrite: 阻止其它Connection对象以写权限打开连接<BR>&nbsp;&nbsp;adModeShareExclusive: 阻止其它Connection对象打开连接<BR>&nbsp;&nbsp;adModeShareDenyNone: 允许其它程序或对象以任何权限建立连接<BR><BR><BR>◆常用的数据库连接方法：<BR><BR>(1)通过JET数据库引擎对ACCESS2000数据库的连接<BR>m_pConnection-&gt;Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);<BR><BR>(2)通过DSN数据源对任何支持ODBC的数据库进行连接:<BR>m_pConnection-&gt;Open("Data Source=adotest;UID=sa;PWD=;","","",adModeUnknown);<BR>//m_pConnection-&gt;Open("DSN=test;","","",0);&nbsp;&nbsp; //连接叫作test的ODBC数据源 <BR><BR>(3)不通过DSN对SQL SERVER数据库进行连接： <BR>m_pConnection-&gt;Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);<BR>其中Server是SQL服务器的名称，DATABASE是库的名称<BR><BR>◆先介绍Connection对象中两个有用的属性ConnectionTimeOut与State<BR>ConnectionTimeOut用来设置连接的超时时间，需要在Open之前调用，例如:<BR>&nbsp;&nbsp;m_pConnection-&gt;ConnectionTimeout = 5; //设置超时时间为5秒<BR>&nbsp;&nbsp;m_pConnection-&gt;Open("Data Source=adotest;","","",adModeUnknown);<BR><BR>State属性指明当前Connection对象的状态，0表示关闭，1表示已经打开，我们可以通过<BR>读取这个属性来作相应的处理，例如:<BR>if(m_pConnection-&gt;State)<BR>&nbsp;&nbsp;m_pConnection-&gt;Close(); //如果已经打开了连接则关闭它<BR><BR><BR><BR><BR>4】/*****************执行SQL命令并取得结果记录集****************/<BR><BR>为了取得结果记录集，我们定义一个指向Recordset对象的指针:<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_RecordsetPtr m_pRecordset;<BR>并为其创建Recordset对象的实例:&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pRecordset.CreateInstance("ADODB.Recordset");<BR><BR>SQL命令的执行可以采用多种形式，下面我们一进行阐述。<BR><BR>◆(1)利用Connection对象的Execute方法执行SQL命令<BR>Execute方法的原型如下所示:<BR><BR>_RecordsetPtr Connection15::Execute (_bstr_t CommandText, VARIANT * RecordsAffected, long Options ) <BR>其中<BR>&nbsp;&nbsp;CommandText&nbsp;&nbsp; 是命令字串，通常是SQL命令。<BR>&nbsp;&nbsp;RecordsAffected&nbsp;&nbsp;是操作完成后所影响的行数, <BR>&nbsp;&nbsp;Options&nbsp;&nbsp;&nbsp;&nbsp;表示CommandText中内容的类型，Options可以取如下值之一：<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adCmdText:&nbsp;&nbsp;表明CommandText是文本命令<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adCmdTable:&nbsp;&nbsp;表明CommandText是一个表名<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adCmdProc:&nbsp;&nbsp;表明CommandText是一个存储过程<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adCmdUnknown: 未知<BR><BR>Execute执行完后返回一个指向记录集的指针，下面我们给出具体代码并作说明。&nbsp;&nbsp;<BR>try<BR>{<BR>_variant_t ra;<BR><BR>m_pConnection-&gt;Execute("CREATE TABLE 学生信息(学号 INTEGER,姓名 TEXT,年龄 INTEGER,生日 DATETIME)",&amp;ra,adCmdText);<BR>m_pConnection-&gt;Execute("INSERT INTO 学生信息(学号,姓名,年龄,生日) VALUES (112105, '程红秀',22,'1982-08-16')",&amp;ra,adCmdText);//往表格里面添加记录<BR>m_pRecordset = m_pConnection-&gt;Execute("SELECT COUNT(*) FROM 学生信息",&amp;ra,adCmdText);&nbsp;&nbsp; //执行SQL统计命令得到包含记录条数的记录集<BR><BR>_variant_t vCount = m_pRecordset-&gt;GetCollect((_variant_t)(long)(0)); //取得第一个字段的值放入vCount变量<BR><BR>m_pRecordset-&gt;Close(); <BR>CString message;<BR>message.Format("共有%d条记录",vCount.lVal);<BR>AfxMessageBox(message);&nbsp;&nbsp; <BR>}<BR><BR>catch (_com_error e) <BR>{ ...}<BR><BR><BR><BR>◆(2)利用 Command对象 来执行SQL命令<BR>try<BR>{<BR>&nbsp;&nbsp;_CommandPtr m_pCommand; <BR>&nbsp;&nbsp;m_pCommand.CreateInstance("ADODB.Command");<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;m_pCommand-&gt;ActiveConnection = m_pConnection; //关键的一句，将建立的连接赋值给它<BR><BR>&nbsp;&nbsp;m_pCommand-&gt;CommandText="INSERT INTO 学生信息(学号,姓名,年龄,生日) VALUES (112105, '程红秀',22,'1982-08-16')";<BR>&nbsp;&nbsp;m_pCommand-&gt;Execute(NULL,NULL,adCmdText);<BR><BR>&nbsp;&nbsp;m_pCommand-&gt;CommandText="SELECT COUNT(*) FROM 学生信息";<BR>&nbsp;&nbsp;m_pRecordset=m_pCommand-&gt;Execute(NULL,NULL,adCmdText);<BR><BR>&nbsp;&nbsp;_variant_t vCount = m_pRecordset-&gt;GetCollect((_variant_t)(long)0); //取得第一个字段的值<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;CString str;<BR>&nbsp;&nbsp;str.Format("共有%d条记录",vCount.lVal);<BR>&nbsp;&nbsp;AfxMessageBox(str);&nbsp;&nbsp; <BR><BR>&nbsp;&nbsp;m_pRecordset-&gt;Close(); <BR><BR>}<BR>catch (_com_error e) {...}<BR><BR>在这段代码中我们只是用Command对象来执行了SELECT查询语句，<BR>Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。 <BR><BR><BR>◆(3)直接用Recordset对象进行查询取得记录集 <BR>例如 <BR>&nbsp;&nbsp;m_pRecordset-&gt;Open("SELECT * FROM 学生信息",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);<BR><BR>Open方法的原型是这样的:<BR>HRESULT Recordset15::Open ( const _variant_t &amp; Source, const _variant_t &amp; ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) <BR>其中：<BR>①Source是数据查询字符串<BR>②ActiveConnection是已经建立好的连接（我们需要用Connection对象指针来构造一个_variant_t对象) <BR>③CursorType光标类型，它可以是以下值之一,请看这个枚举结构:<BR>&nbsp;&nbsp; enum CursorTypeEnum<BR>&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;adOpenUnspecified = -1, //不作特别指定<BR>&nbsp;&nbsp;&nbsp;&nbsp;adOpenForwardOnly = 0, //前滚静态光标。这种光标只能向前浏览记录集，比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用<BR>&nbsp;&nbsp;&nbsp;&nbsp;adOpenKeyset = 1,&nbsp;&nbsp;//采用这种光标的记录集看不到其它用户的新增、删除操作，但对于更新原有记录的操作对你是可见的。<BR>&nbsp;&nbsp;&nbsp;&nbsp;adOpenDynamic = 2,&nbsp;&nbsp;//动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。<BR>&nbsp;&nbsp;&nbsp;&nbsp;adOpenStatic = 3&nbsp;&nbsp;//静态光标。它为你的记录集产生一个静态备份，但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。<BR>&nbsp;&nbsp; };<BR>④LockType锁定类型，它可以是以下值之一，请看如下枚举结构：<BR>&nbsp;&nbsp; enum LockTypeEnum<BR>&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;adLockUnspecified = -1,&nbsp;&nbsp;//未指定<BR>&nbsp;&nbsp;&nbsp;&nbsp;adLockReadOnly = 1,&nbsp;&nbsp; //只读记录集<BR>&nbsp;&nbsp;&nbsp;&nbsp;adLockPessimistic = 2,&nbsp;&nbsp;//悲观锁定方式。数据在更新时锁定其它所有动作，这是最安全的锁定机制<BR>&nbsp;&nbsp;&nbsp;&nbsp;adLockOptimistic = 3,&nbsp;&nbsp;//乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作<BR>&nbsp;&nbsp;&nbsp;&nbsp;adLockBatchOptimistic = 4， //乐观分批更新。编辑时记录不会锁定，更改、插入及删除是在批处理模式下完成。<BR>&nbsp;&nbsp; }; <BR>⑤Options请参考本文中对Connection对象的Execute方法的介绍<BR><BR><BR><BR><BR>/***********************【5】记录集的遍历、更新*************************/<BR><BR>根据我们刚才通过执行SQL命令建立好的 学生信息 表，它包含四个字段:学号，姓名，年龄，生日<BR>以下的代码实现：打开记录集，遍历所有记录，删除第一条记录，添加三条记录，移动光标到第二条<BR>记录，更改其年龄，保存到数据库。<BR><BR>try<BR>{<BR>&nbsp;&nbsp;_variant_t vUsername,vBirthday,vID,vOld;<BR>&nbsp;&nbsp;_RecordsetPtr m_pRecordset;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;m_pRecordset.CreateInstance("ADODB.Recordset");<BR>&nbsp;&nbsp;m_pRecordset-&gt;Open("SELECT * FROM 学生信息",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;while(!m_pRecordset-&gt;adoEOF) <BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp; vID = m_pRecordset-&gt;GetCollect(_variant_t((long)0));&nbsp;&nbsp;&nbsp;&nbsp;//取得第1列的值,从0开始计数，你也可以直接给出列的名称，如下一行<BR>&nbsp;&nbsp; vUsername = m_pRecordset-&gt;GetCollect("姓名");&nbsp;&nbsp;//取得姓名字段的值<BR>&nbsp;&nbsp; vOld = m_pRecordset-&gt;GetCollect("年龄");<BR>&nbsp;&nbsp; vBirthday = m_pRecordset-&gt;GetCollect("生日");<BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp; TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",<BR>&nbsp;&nbsp;&nbsp;&nbsp;vID.lVal,<BR>&nbsp;&nbsp;&nbsp;&nbsp;(LPCTSTR)(_bstr_t)vUsername,<BR>&nbsp;&nbsp;&nbsp;&nbsp;vOld.lVal,<BR>&nbsp;&nbsp;&nbsp;&nbsp;(LPCTSTR)(_bstr_t)vBirthday);&nbsp;&nbsp;//在DEBUG方式下的OUTPUT窗口输出记录集中的记录<BR><BR>&nbsp;&nbsp; m_pRecordset-&gt;MoveNext();&nbsp;&nbsp;&nbsp;&nbsp;//移到下一条记录<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;m_pRecordset-&gt;MoveFirst();&nbsp;&nbsp;&nbsp;&nbsp;//移到首条记录<BR>&nbsp;&nbsp;m_pRecordset-&gt;Delete(adAffectCurrent);&nbsp;&nbsp;&nbsp;&nbsp;//删除当前记录<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;for(int i=0;i&lt;3;i++)&nbsp;&nbsp;&nbsp;&nbsp;//添加三条新记录并赋值<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp; m_pRecordset-&gt;AddNew();&nbsp;&nbsp;&nbsp;&nbsp;//添加新记录<BR>&nbsp;&nbsp; m_pRecordset-&gt;PutCollect("学号",_variant_t((long)(i+10)));<BR>&nbsp;&nbsp; m_pRecordset-&gt;PutCollect("姓名",_variant_t("王斌年"));<BR>&nbsp;&nbsp; m_pRecordset-&gt;PutCollect("年龄",_variant_t((long)21));<BR>&nbsp;&nbsp; m_pRecordset-&gt;PutCollect("生日",_variant_t("1930-3-15"));<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;m_pRecordset-&gt;Move(1,_variant_t((long)adBookmarkFirst));&nbsp;&nbsp;&nbsp;&nbsp;//从第一条记录往下移动一条记录,即移动到第二条记录处<BR>&nbsp;&nbsp;m_pRecordset-&gt;PutCollect(_variant_t("年龄"),_variant_t((long)45));&nbsp;&nbsp;&nbsp;&nbsp;//修改其年龄<BR>&nbsp;&nbsp;m_pRecordset-&gt;Update();&nbsp;&nbsp;&nbsp;&nbsp;//保存到库中<BR>&nbsp;&nbsp;<BR>} catch (_com_error e){}<BR><BR><BR><BR>/***********************【6】关闭记录集与连接 *************************/<BR>记录集或连接都可以用Close方法来关闭&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>m_pRecordset-&gt;Close(); //关闭记录集&nbsp;&nbsp;<BR>m_pConnection-&gt;Close(); //关闭连接 <BR><BR><BR><BR><BR>在stdafx.h中进行宏定义:<BR>#if !defined CATCH_ERROR<BR>#define CATCH_ERROR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \<BR>&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \<BR>&nbsp;&nbsp; CString strComError;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \<BR>&nbsp;&nbsp; strComError.Format("错误编号: %08lx\n错误信息: %s\n错误源: %s\n错误描述: %s", \<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.Error(),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.ErrorMessage(),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(LPCSTR) e.Source(),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(LPCSTR) e.Description());&nbsp;&nbsp;\<BR>&nbsp;&nbsp; ::MessageBox(NULL,strComError,"错误",MB_ICONEXCLAMATION); \<BR>&nbsp;&nbsp;}<BR>#endif<BR>使用方法：<BR>try<BR>{&nbsp;&nbsp;...}<BR>catch(_com_error e)<BR>{<BR>CATCH_ERROR;<BR>}<BR><img src ="http://www.cppblog.com/ivenher/aggbug/2149.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-26 19:37 <a href="http://www.cppblog.com/ivenher/articles/2149.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ADO数据库编程入门</title><link>http://www.cppblog.com/ivenher/articles/1773.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Thu, 15 Dec 2005 03:03:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/1773.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/1773.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/1773.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/1773.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/1773.html</trackback:ping><description><![CDATA[ADO 是目前在Windows环境中比较流行的客户端数据库编程技术。ADO是建立在OLE DB底层技术之上的高级编程接口，因而它兼具有强大的数据处理功能（处理各种不同类型的数据源、分布式的数据处理等等）和极其简单、易用的编程接口，因而得到了广泛的应用。而且按微软公司的意图，OLE DB和ADO将逐步取代 ODBC和DAO。现在介绍ADO各种应用的文章和书籍有很多，本文着重站在初学者的角度，简要探讨一下在VC++中使用ADO编程时的一些问题。我们希望阅读本文之前，您对ADO技术的基本原理有一些了解。<BR>一、在VC++中使用ADO编程<BR>ADO实际上就是由一组Automation对象构成的组件，因此可以象使用其它任何Automation对象一样使用ADO。ADO中最重要的对象有三个：Connection、Command和Recordset，它们分别表示连接对象、命令对象和记录集对象。如果您熟悉使用MFC中的ODBC类（CDatabase、CRecordset)编程，那么学习ADO编程就十分容易了。<BR>使用ADO编程时可以采用以下三种方法之一：<BR>1、使用预处理指令#import<BR>#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \<BR>&nbsp;&nbsp; no_namespace rename("EOF", "EndOfFile")<BR>但要注意不能放在stdAfx.h文件的开头，而应该放在所有include指令的后面。否则在编译时会出错。<BR>程序在编译过程中，VC++会读出msado15.dll中的类型库信息，自动产生两个该类型库的头文件和实现文件msado15.tlh和msado15.tli（在您的Debug或Release目录下）。在这两个文件里定义了ADO的所有对象和方法，以及一些枚举型的常量等。我们的程序只要直接调用这些方法就行了，与使用MFC中的COleDispatchDriver类调用Automation对象十分类似。<BR>2、使用MFC中的CIDispatchDriver<BR>就是通过读取msado15.dll中的类型库信息，建立一个COleDispatchDriver类的派生类，然后通过它调用ADO对象。<BR>3、直接用COM提供的API<BR>&nbsp;如使用如下代码：<BR>&nbsp;CLSID clsid;<BR>&nbsp;HRESULT hr = ::CLSIDFromProgID(L"ADODB.Connection", &amp;clsid);<BR>&nbsp;if(FAILED(hr))<BR>&nbsp;{...}<BR>&nbsp;::CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;pDispatch);<BR>&nbsp;if(FAILED(hr))<BR>&nbsp;{...}<BR>以上三种方法，第一和第二种类似，可能第一种好用一些，第三种编程可能最麻烦。但可能第三种方法也是效率最高的，程序的尺寸也最小，并且对ADO的控制能力也最强。<BR>据微软资料介绍，第一种方法不支持方法调用中的默认参数，当然第二种方法也是这样，但第三种就不是这样了。采用第三种方法的水平也最高。当你需要绕过ADO而直接调用OLE DB底层的方法时，就一定要使用第三种方法了。<BR>ADO编程的关键，就是熟练地运用ADO提供的各种对象(object)、方法(method)、属性(property)和容器（collection）。另外，如果是在MS SQL或Oracle等大型数据库上编程，还要能熟练使用SQL语言。<BR>二、使用#import方法的编程步骤<BR>这里建议您使用#import的方法，因为它易学、易用，代码也比较简洁。<BR>1、 添加#import指令<BR>打开stdafx.h文件，将下列内容添加到所有的include指令之后：<BR>#include &lt;icrsint.h&gt;&nbsp;&nbsp; //Include support for VC++ Extensions<BR>#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \<BR>&nbsp;&nbsp; no_namespace rename("EOF", "adoEOF")<BR>其中icrsint.h文件包含了VC++扩展的一些预处理指令、宏等的定义，用于COM编程时使用。<BR>2、定义_ConnectionPtr型变量，并建立数据库连接<BR>建立了与数据库服务器的连接后，才能进行其他有关数据库的访问和操作。ADO使用Connection对象来建立与数据库服务器的连接，所以它相当于MFC中的CDatabase类。和CDatabase类一样，调用Connection对象的Open方法即可建立与服务器的连接。<BR>数据类型&nbsp;_ConnectionPtr实际上就是由类模板_com_ptr_t而得到的一个具体的实例类，其定义可以到msado15.tlh、comdef.h 和comip.h这三个文件中找到。在msado15.tlh中有：<BR>_COM_SMARTPTR_TYPEDEF(_Collection, __uuidof(_Collection));<BR>经宏扩展后就得到了_ConnectionPtr类。_ConnectionPtr类封装了Connection对象的Idispatch接口指针，及一些必要的操作。我们就是通过这个指针来操纵Connection对象。类似地，后面用到的_CommandPtr和_RecordsetPtr类型也是这样得到的，它们分别表示命令对象指针和记录集对象的指针。<BR>（1）、连接到MS SQL Server<BR>注意连接字符串的格式，提供正确的连接字符串是成功连接到数据库服务器的第一步，有关连接字符串的详细信息参见微软MSDN Library光盘。<BR>本例连接字符串中的server_name，database_name，user_name和password在编程时都应该替换成实际的内容。<BR>&nbsp;_ConnectionPtr pMyConnect=NULL;<BR>&nbsp;HRESULT hr=pMyConnect.CreateInstance(__uuidof(Connection)));<BR>&nbsp;if(FAILED(hr))return;<BR>_bstr_t strConnect="Provider=SQLOLEDB; Server=server_name;"<BR>&nbsp;&nbsp;"Database=database_name; uid=user_name; pwd=password;";&nbsp;<BR>//connecting to the database server now:<BR>&nbsp;try{pMyConnect-&gt;Open(strConnect,"","",NULL);}<BR>&nbsp;catch (_com_error &amp;e)<BR>&nbsp;{<BR>&nbsp;&nbsp;::MessageBox(NULL,e.Description(),"警告",MB_OK | MB_ICONWARNING);<BR>&nbsp;}<BR><BR>注意Connection对象的Open方法中的连接字符串参数必须是BSTR或_bstr_t类型。另外，本例是直接通过OLE DB Provider建立连接，所以无需建立数据源。<BR>（2）、通过ODBC Driver连接到Database Server<BR>连接字符串格式与直接用ODBC编程时的差不多：<BR>_bstr_t strConnect="DSN=datasource_name; Database=database_name; uid=user_name; pwd=password;";<BR>此时与ODBC编程一样，必须先建立数据源。<BR>3、定义_RecordsetPtr型变量，并打开数据集<BR>定义_RecordsetPtr型变量，然后通过它调用Recordset对象的Open方法，即可打开一个数据集。所以Recordset对象与MFC中的CRecordset类类似，它也有当前记录、当前记录指针的概念。如：<BR>&nbsp;_RecordsetPtr m_pRecordset;<BR>&nbsp;if(!FAILED(m_pRecordset.CreateInstance( __uuidof( Recordset )))<BR>&nbsp;{<BR>&nbsp;&nbsp;m_pDoc-&gt;m_initialized=FALSE;<BR>&nbsp;&nbsp;return;<BR>&nbsp;}<BR>try{<BR>&nbsp;&nbsp;m_pRecordset-&gt;Open(_variant_t("mytable"),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _variant_t((IDispatch *)pMyConnect,true), adOpenKeyset,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adLockOptimistic, adCmdTable);<BR>&nbsp;}<BR>&nbsp;catch (_com_error &amp;e)<BR>&nbsp;{<BR>&nbsp;&nbsp;::MessageBox(NULL,"无法打开mytable表。","提示",<BR>MB_OK | MB_ICONWARNING);<BR>&nbsp;}<BR>Recordset对象的Open方法非常重要，它的第一个参数可以是一个SQL语句、一个表的名字或一个命令对象等等；第二个参数就是前面建立的连接对象的指针。此外，用Connection和Command对象的Execute方法也能得到记录集，但是只读的。<BR>4、读取当前记录的数据<BR>我认为读取数据的最方便的方法如下：<BR>&nbsp;try{<BR>&nbsp;&nbsp;m_pRecordset-&gt;MoveFirst();&nbsp;&nbsp; <BR>&nbsp;&nbsp;while(m_pRecordset-&gt;adoEOF==VARIANT_FALSE)&nbsp;<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;//Retrieve column's value:&nbsp;<BR>&nbsp;&nbsp;&nbsp;CString sName=(char*)(_bstr_t)(m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;&nbsp;(_variant_t("name"))-&gt;Value);<BR>&nbsp;&nbsp;&nbsp;short cAge=(short)(m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;&nbsp;(_variant_t("age"))-&gt;Value);<BR>&nbsp;&nbsp;&nbsp;//Do something what you want to do:<BR>&nbsp;&nbsp;&nbsp;......<BR>&nbsp;&nbsp;&nbsp;m_pRecordset-&gt;MoveNext();&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;}<BR>&nbsp;}//try<BR>&nbsp;catch (_com_error &amp;e)<BR>&nbsp;{<BR>&nbsp;&nbsp;CString str=(char*)e.Description();<BR>&nbsp;&nbsp;::MessageBox(NULL,str+"\n又出毛病了。","提示",<BR>MB_OK | MB_ICONWARNING);<BR>&nbsp;}<BR>
<P>本例中的name和age都是字段名，读取的字段值分别保存在sName和cAge变量内。例中的Fields是Recordset对象的容器，GetItem方法返回的是Field对象，而Value则是Field对象的一个属性（即该字段的值）。通过此例，应掌握操纵对象属性的方法。例如，要获得Field 对象的Value属性的值可以直接用属性名Value来引用它（如上例），但也可以调用Get方法，例如：<BR>CString sName=(char*)(_bstr_t)(m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;&nbsp;(_variant_t("name"))-&gt;GetValue());<BR>从此例还可以看到，判断是否到达记录集的末尾，使用记录集的adoEOF属性，其值若为真即到了结尾，反之则未到。判断是否到达记录集开头，则可用BOF属性。<BR>另外，读取数据还有一个方法，就是定义一个绑定的类，然后通过绑定的变量得到字段值（详见后面的介绍）。<BR>5、修改数据<BR>方法一：<BR>&nbsp;try{<BR>&nbsp;&nbsp;m_pRecordset-&gt;MoveFirst();&nbsp;&nbsp; <BR>&nbsp;&nbsp;while(m_pRecordset-&gt;adoEOF==VARIANT_FALSE)&nbsp;<BR>&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;&nbsp;(_variant_t("姓名"))-&gt;Value=_bstr_t("赵薇");<BR>&nbsp;&nbsp;&nbsp;......<BR>&nbsp;&nbsp;&nbsp;m_pRecordset-&gt;Update();<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;&nbsp;&nbsp;m_pRecordset-&gt;MoveNext();&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;}<BR>&nbsp;}//try<BR>改变了Value属性的值，即改变了字段的值。<BR>方法二：<BR>&nbsp;&nbsp;m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;&nbsp;(_variant_t("姓名"))-&gt;PutValue(_bstr_t("赵薇"));<BR>方法三：就是用定义绑定类的方法（详见后面的介绍）。<BR>6、添加记录<BR>新记录添加成功后，即自动成为当前记录。AddNew方法有两种形式，一个含有参数，而另一个则不带参数。<BR>方法一（不带参数）：<BR>&nbsp;// Add new record into this table:<BR>&nbsp;try{<BR>&nbsp;&nbsp;if(!m_pRecordset-&gt;Supports(adAddNew)) return;<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;&nbsp;m_pRecordset-&gt;AddNew();&nbsp;<BR>&nbsp;&nbsp;m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;(_variant_t("姓名"))-&gt;Value=_bstr_t("赵薇");<BR>&nbsp;&nbsp;m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;(_variant_t("性别"))-&gt;Value=_bstr_t("女");<BR>&nbsp;&nbsp;m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;(_variant_t("age"))-&gt;Value=_variant_t((short)20);<BR>&nbsp;&nbsp;m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;(_variant_t("marry"))-&gt;Value=_bstr_t("未婚");<BR>&nbsp;&nbsp;m_pRecordset-&gt;Update();&nbsp;&nbsp;<BR>&nbsp;}//try<BR>&nbsp;catch (_com_error &amp;e)<BR>&nbsp;{<BR>&nbsp;&nbsp;::MessageBox(NULL, "又出毛病了。","提示",MB_OK | MB_ICONWARNING);<BR>&nbsp;}<BR>这种方法弄完了还要调用Update()。<BR>方法二（带参数）：<BR>&nbsp;&nbsp;_variant_t varName[4],narValue[4];<BR>&nbsp;&nbsp;varName[0] = L"姓名";<BR>&nbsp;&nbsp;varName[1] = L"性别";<BR>&nbsp;&nbsp;varName[2] = L"age";<BR>&nbsp;&nbsp;varName[3] = L"marry";<BR>&nbsp;&nbsp;narValue[0]=_bstr_t("赵薇");<BR>&nbsp;&nbsp;narValue[1]=_bstr_t("女");<BR>&nbsp;&nbsp;narValue[2]=_variant_t((short)20);<BR>&nbsp;&nbsp;narValue[3]=_bstr_t("未婚");<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;&nbsp;const int nCrit = sizeof varName / sizeof varName[0];<BR>&nbsp;&nbsp;// Create SafeArray Bounds and initialize the array<BR>&nbsp;&nbsp;SAFEARRAYBOUND rgsaName[1],rgsaValue[1];<BR>&nbsp;&nbsp;rgsaName[0].lLbound = 0;&nbsp;&nbsp; <BR>&nbsp;&nbsp;rgsaName[0].cElements = nCrit;<BR>&nbsp;&nbsp;SAFEARRAY *psaName = SafeArrayCreate( VT_VARIANT, 1, rgsaName );<BR>&nbsp;&nbsp;rgsaValue[0].lLbound = 0;<BR>&nbsp;&nbsp;rgsaValue[0].cElements = nCrit;<BR>&nbsp;&nbsp;SAFEARRAY *psaValue = SafeArrayCreate( VT_VARIANT, 1, rgsaValue );<BR>&nbsp;&nbsp;// Set the values for each element of the array<BR>&nbsp;&nbsp;HRESULT hr1=S_OK.hr2=S_OK;<BR>&nbsp;&nbsp;for( long i = 0 ; i &lt; nCrit &amp;&amp; SUCCEEDED( hr1 ) &amp;&amp; SUCCEEDED( hr2 );i++)&nbsp;&nbsp; <BR>&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;hr1=SafeArrayPutElement(psaName, &amp;i,&amp;varName[i]);<BR>&nbsp;&nbsp;&nbsp;hr2=SafeArrayPutElement(psaValue, &amp;i,&amp;narValue[i]);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;// Initialize and fill the SafeArray<BR>&nbsp;&nbsp;VARIANT vsaName,vsaValue;&nbsp;&nbsp; <BR>&nbsp;&nbsp;vsaName.vt = VT_VARIANT | VT_ARRAY;<BR>&nbsp;&nbsp;vsaValue.vt = VT_VARIANT | VT_ARRAY;<BR>&nbsp;&nbsp;V_ARRAY(&amp;vsaName) = psaName;//&amp;vsaName-&gt;parray=psaName;<BR>&nbsp;&nbsp;&nbsp;//see definition in oleauto.h file.<BR>&nbsp;&nbsp;V_ARRAY(&amp;vsaValue) = psaValue;<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;// Add a new record:<BR>&nbsp;&nbsp;m_pRecordset-&gt;AddNew(vsaName,vsaValue);<BR>这种方法不需要调用Update，因为添加后，ADO会自动调用它。此方法主要是使用SafeArray挺麻烦。<BR>方法三：就是用定义绑定类的方法（详见后面的介绍）。<BR>7、删除记录<BR>调用Recordset的Delete方法就行了，删除的是当前记录。要了解Delete的其它用法请查阅参考文献。<BR>&nbsp;try{<BR>&nbsp;&nbsp;m_pRecordset-&gt;MoveFirst();&nbsp;&nbsp; <BR>&nbsp;&nbsp;while(m_pRecordset-&gt;adoEOF==VARIANT_FALSE)&nbsp;<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;CString sName=(char*)(_bstr_t)(m_pRecordset-&gt;Fields-&gt;GetItem<BR>&nbsp;&nbsp;&nbsp;&nbsp;(_variant_t("姓名"))-&gt;Value);<BR>&nbsp;&nbsp;&nbsp;if(::MessageBox(NULL,"姓名="+sName+"\n删除她吗？",<BR>&nbsp;&nbsp;&nbsp;&nbsp;"提示",MB_YESNO | MB_ICONWARNING)==IDYES)<BR>&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;m_pRecordset-&gt;Delete(adAffectCurrent);&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;m_pRecordset-&gt;Update();<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;m_pRecordset-&gt;MoveNext();&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;}<BR>&nbsp;}//try<BR>&nbsp;catch (_com_error &amp;e)<BR>&nbsp;{<BR>&nbsp;&nbsp;::MessageBox(NULL,"又出毛病了。","提示",MB_OK | MB_ICONWARNING);<BR>&nbsp;}<BR>8、使用带参数的命令<BR>Command对象所代表的就是一个Provider能够理解的命令，如SQL语句等。使用Command对象的关键就是把表示命令的语句设置到CommandText属性中，然后调用Command对象的Execute方法就行了。一般情况下在命令中无需使用参数，但有时使用参数，可以增加其灵活性和效率。<BR>(1). 建立连接、命令对象和记录集对象<BR>本例中表示命令的语句就是一个SQL语句（SELECT语句）。SELECT语句中的问号?就代表参数，如果要多个参数，就多放几个问号，每个问号代表一个参数。<BR>_ConnectionPtr&nbsp; Conn1;<BR>_CommandPtr&nbsp;&nbsp;&nbsp;&nbsp; Cmd1;<BR>ParametersPtr&nbsp;&nbsp; *Params1 = NULL;&nbsp;&nbsp; // Not an instance of a smart pointer.<BR>_ParameterPtr&nbsp;&nbsp; Param1;<BR>_RecordsetPtr&nbsp;&nbsp; Rs1;<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>try<BR>{<BR>&nbsp;// Create Connection Object (1.5 Version)<BR>&nbsp;Conn1.CreateInstance( __uuidof( Connection ) );<BR>&nbsp;Conn1-&gt;ConnectionString = bstrConnect;<BR>&nbsp;&nbsp;&nbsp; Conn1-&gt;Open( bstrEmpty, bstrEmpty, bstrEmpty, -1 );<BR>&nbsp;&nbsp;&nbsp; // Create Command Object<BR>&nbsp;&nbsp;&nbsp; Cmd1.CreateInstance( __uuidof( Command ) );<BR>&nbsp;&nbsp;&nbsp; Cmd1-&gt;ActiveConnection = Conn1;<BR>&nbsp;&nbsp;&nbsp; Cmd1-&gt;CommandText&nbsp; = _bstr_t("SELECT * FROM mytable WHERE age&lt; ?");<BR>}//try<BR>要注意命令对象必须与连接对象关联起来才能起作用，本例中将命令对象的ActiveConnection属性设置为连接对象的指针，即为此目的：<BR>Cmd1-&gt;ActiveConnection = Conn1;<BR>&nbsp;(2). 创建参数对象，并给参数赋值<BR>// Create Parameter Object<BR>Param1 = Cmd1-&gt;CreateParameter( _bstr_t(bstrEmpty),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adInteger,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adParamInput,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -1,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _variant_t( (long) 5) );<BR>Param1-&gt;Value = _variant_t( (long) 5 );<BR>Cmd1-&gt;Parameters-&gt;Append( Param1 );<BR>用命令对象的方法来创建一个参数对象，其中的长度参数（第三个）如果是固定长度的类型，就填-1，如果是字符串等可变长度的就填其实际长度。Parameters是命令对象的一个容器，它的Append方法就是把创建的参数对象追加到该容器里。Append进去的参数按先后顺序与SQL语句中的问号从左至右一一对应。<BR>(3). 执行命令打开记录集<BR>// Open Recordset Object<BR>Rs1 = Cmd1-&gt;Execute( &amp;vtEmpty, &amp;vtEmpty2, adCmdText );<BR>但要注意，用Command和Connection对象的Execute方法得到的Recordset是只读的。因为在打开Recordset之前，我们无法设置它的LockType属性（其默认值为只读）。而在打开之后设置LockType不起作用。<BR>我发现用上述方法得到记录集Rs1后，不但Rs1中的记录无法修改，即使直接用SQL语句修改同一表中任何记录都不行。<BR>要想能修改数据，还是要用Recordset自己的Open方法才行，如：<BR>&nbsp;try{<BR>&nbsp;&nbsp;&nbsp;m_pRecordset-&gt;Open((IDispatch *) Cmd1, vtMissing,<BR>&nbsp;&nbsp;&nbsp;&nbsp;adOpenStatic, adLockOptimistic, adCmdUnspecified);<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;catch (_com_error &amp;e)<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;::MessageBox(NULL,"mytable表不存在。","提示",MB_OK | MB_ICONWARNING);<BR>&nbsp;&nbsp;}<BR>Recordset对象的Open方法真是太好了，其第一个参数可以是SQL语句、表名字、命令对象指针等等。<BR>9、响应ADO的通知事件<BR>通知事件就是当某个特定事件发生时，由Provider通知客户程序，换句话说，就是由Provider调用客户程序中的一个特定的方法（即事件的处理函数）。所以为了响应一个事件，最关键的就是要实现事件的处理函数。<BR>(1). 从ConnectionEventsVt接口派生出一个类<BR>为了响应_Connection的通知事件，应该从ConnectionEventsVt接口派生出一个类：<BR>class CConnEvent : public ConnectionEventsVt<BR>{<BR>private:<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ULONG&nbsp;&nbsp; m_cRef;<BR>public:<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CConnEvent() { m_cRef = 0; };<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ~CConnEvent() {};<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STDMETHODIMP_(ULONG) AddRef(void);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STDMETHODIMP_(ULONG) Release(void);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STDMETHODIMP raw_InfoMessage( <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct Error *pError,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EventStatusEnum *adStatus,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct _Connection *pConnection);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STDMETHODIMP raw_BeginTransComplete( <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LONG TransactionLevel,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct Error *pError,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EventStatusEnum *adStatus,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct _Connection *pConnection);<BR>&nbsp;&nbsp;......<BR>};<BR>(2). 实现每一个事件的处理函数(凡是带raw_前缀的方法都把它实现了)：<BR>STDMETHODIMP CConnEvent::raw_InfoMessage( <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct Error *pError,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EventStatusEnum *adStatus,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct _Connection *pConnection)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *adStatus = adStatusUnwantedEvent;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return S_OK;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>有些方法虽然你并不需要，但也必须实现它，只需简单地返回一个S_OK即可。但如果要避免经常被调用，还应在其中将adStatus参数设置为adStatusUnwantedEvent，则在本次调用后，以后就不会被调用了。<BR>另外还必须实现QueryInterface, AddRef, 和Release三个方法:&nbsp;<BR>&nbsp;STDMETHODIMP CConnEvent::QueryInterface(REFIID riid, void ** ppv) <BR>&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *ppv = NULL;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (riid == __uuidof(IUnknown) || <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; riid == __uuidof(ConnectionEventsVt)) *ppv = this;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (*ppv == NULL)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ResultFromScode(E_NOINTERFACE);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AddRef();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return NOERROR;<BR>&nbsp;&nbsp; }<BR>&nbsp;&nbsp; STDMETHODIMP_(ULONG) CConnEvent::AddRef() { return ++m_cRef; };<BR>&nbsp;&nbsp; STDMETHODIMP_(ULONG) CConnEvent::Release()<BR>{ <BR>if (0 != --m_cRef) return m_cRef;<BR>delete this;<BR>return 0;<BR>}<BR>(3). 开始响应通知事件<BR>&nbsp;// Start using the Connection events<BR>&nbsp;IConnectionPointContainer&nbsp;&nbsp; *pCPC = NULL;<BR>&nbsp;IConnectionPoint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *pCP = NULL;<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;hr = pConn.CreateInstance(__uuidof(Connection));<BR>&nbsp;&nbsp; if (FAILED(hr)) return;<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;hr = pConn-&gt;QueryInterface(__uuidof(IConnectionPointContainer), <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (void **)&amp;pCPC);<BR>&nbsp;if (FAILED(hr)) return;<BR>&nbsp;hr = pCPC-&gt;FindConnectionPoint(__uuidof(ConnectionEvents), &amp;pCP);<BR>&nbsp;pCPC-&gt;Release();<BR>&nbsp;if (FAILED(hr)) return;<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;pConnEvent = new CConnEvent();<BR>&nbsp;&nbsp; hr = pConnEvent-&gt;QueryInterface(__uuidof(IUnknown), (void **) &amp;pUnk);<BR>&nbsp;&nbsp; if (FAILED(hr)) return rc; <BR>&nbsp;&nbsp; hr = pCP-&gt;Advise(pUnk, &amp;dwConnEvt);<BR>&nbsp;&nbsp; pCP-&gt;Release();<BR>&nbsp;&nbsp; if (FAILED(hr)) return;<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;pConn-&gt;Open("dsn=Pubs;", "sa", "", adConnectUnspecified); <BR>也就是说在连接(Open)之前就做这些事。<BR>(4). 停止响应通知事件<BR>&nbsp;pConn-&gt;Close();<BR>&nbsp;// Stop using the Connection events<BR>&nbsp; hr = pConn-&gt;QueryInterface(__uuidof(IConnectionPointContainer), <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (void **) &amp;pCPC);<BR>&nbsp;&nbsp; if (FAILED(hr)) return;<BR>&nbsp;&nbsp; hr = pCPC-&gt;FindConnectionPoint(__uuidof(ConnectionEvents), &amp;pCP);<BR>&nbsp;&nbsp; pCPC-&gt;Release();<BR>&nbsp;&nbsp; if (FAILED(hr)) return rc;<BR>&nbsp;&nbsp; hr = pCP-&gt;Unadvise( dwConnEvt );<BR>&nbsp;&nbsp; pCP-&gt;Release();<BR>&nbsp;&nbsp; if (FAILED(hr)) return;<BR>在连接关闭之后做这件事。 <BR>10、邦定数据<BR>定义一个绑定类，将其成员变量绑定到一个指定的记录集，以方便于访问记录集的字段值。<BR>(1). 从CADORecordBinding派生出一个类：<BR>class CCustomRs : public CADORecordBinding<BR>{<BR>BEGIN_ADO_BINDING(CCustomRs)<BR>&nbsp;&nbsp; ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_szau_fname, <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(m_szau_fname), lau_fnameStatus, false)<BR>&nbsp;&nbsp; ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_szau_lname, <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(m_szau_lname), lau_lnameStatus, false)<BR>&nbsp;&nbsp; ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_szphone,&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(m_szphone),&nbsp;&nbsp;&nbsp; lphoneStatus,&nbsp;&nbsp;&nbsp; true)<BR>END_ADO_BINDING()<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>public:<BR>&nbsp;&nbsp; CHAR&nbsp;&nbsp; m_szau_fname[22];<BR>&nbsp;&nbsp; ULONG&nbsp;&nbsp; lau_fnameStatus;<BR>&nbsp;&nbsp; CHAR&nbsp;&nbsp; m_szau_lname[42];<BR>&nbsp;&nbsp; ULONG&nbsp;&nbsp; lau_lnameStatus;<BR>&nbsp;&nbsp; CHAR&nbsp;&nbsp; m_szphone[14];<BR>&nbsp;&nbsp; ULONG&nbsp;&nbsp; lphoneStatus;<BR>};<BR>其中将要绑定的字段与变量名用BEGIN_ADO_BINDING宏关联起来。每个字段对应于两个变量，一个存放字段的值，另一个存放字段的状态。字段用从1开始的序号表示，如1，2，3等等。<BR>特别要注意的是：如果要绑定的字段是字符串类型，则对应的字符数组的元素个数一定要比字段长度大2（比如m_szau_fname[22]，其绑定的字段au_fname的长度实际是20），不这样绑定就会失败。我分析多出的2可能是为了存放字符串结尾的空字符null和BSTR字符串开头的一个字（表示BSTR的长度）。这个问题对于初学者来说可能是一个意想不到的问题。<BR>CADORecordBinding类的定义在icrsint.h文件里，内容是：<BR>class CADORecordBinding<BR>{<BR>public:<BR>&nbsp;STDMETHOD_(const ADO_BINDING_ENTRY*, GetADOBindingEntries) (VOID) PURE;<BR>};<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>BEGIN_ADO_BINDING宏的定义也在icrsint.h文件里，内容是：<BR>#define BEGIN_ADO_BINDING(cls) public: \<BR>typedef cls ADORowClass; \<BR>const ADO_BINDING_ENTRY* STDMETHODCALLTYPE GetADOBindingEntries() { \<BR>static const ADO_BINDING_ENTRY rgADOBindingEntries[] = { <FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>ADO_VARIABLE_LENGTH_ENTRY2宏的定义也在icrsint.h文件里：<BR>#define ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify)\<BR>&nbsp;{Ordinal, \<BR>&nbsp;DataType, \<BR>&nbsp;0, \<BR>&nbsp;0, \<BR>&nbsp;Size, \<BR>&nbsp;offsetof(ADORowClass, Buffer), \<BR>&nbsp;offsetof(ADORowClass, Status), \<BR>&nbsp;0, \<BR>&nbsp;classoffset(CADORecordBinding, ADORowClass), \<BR>&nbsp;Modify},<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;#define END_ADO_BINDING宏的定义也在icrsint.h文件里：<BR>&nbsp;#define END_ADO_BINDING()&nbsp;&nbsp; {0, adEmpty, 0, 0, 0, 0, 0, 0, 0, FALSE}};\<BR>&nbsp;return rgADOBindingEntries;}<BR>(2). 绑定<BR>_RecordsetPtr&nbsp;&nbsp; Rs1;<BR>IADORecordBinding&nbsp;&nbsp; *picRs=NULL;<BR>CCustomRs rs;<BR>......<BR>Rs1-&gt;QueryInterface(__uuidof(IADORecordBinding), <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LPVOID*)&amp;picRs));<BR>picRs-&gt;BindToRecordset(&amp;rs);<BR>派生出的类必须通过IADORecordBinding接口才能绑定，调用它的BindToRecordset方法就行了。<BR>(3). rs中的变量即是当前记录字段的值<BR>//Set sort and filter condition:<BR>// Step 4: Manipulate the data<BR>Rs1-&gt;Fields-&gt;GetItem("au_lname")-&gt;Properties-&gt;GetItem("Optimize")-&gt;Value = true; <BR>Rs1-&gt;Sort = "au_lname ASC";<BR>Rs1-&gt;Filter = "phone LIKE '415 5*'";<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>Rs1-&gt;MoveFirst();<BR>while (VARIANT_FALSE == Rs1-&gt;EndOfFile)<BR>{<BR>&nbsp;printf("Name: %s\t %s\tPhone: %s\n",&nbsp; <BR>&nbsp;&nbsp;(rs.lau_fnameStatus == adFldOK ? rs.m_szau_fname : ""), <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (rs.lau_lnameStatus == adFldOK ? rs.m_szau_lname : ""),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (rs.lphoneStatus == adFldOK ? rs.m_szphone&nbsp;&nbsp; : ""));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (rs.lphoneStatus == adFldOK)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strcpy(rs.m_szphone, "777");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TESTHR(picRs-&gt;Update(&amp;rs));&nbsp;&nbsp; // Add change to the batch<BR>&nbsp;&nbsp;Rs1-&gt;MoveNext();<BR>}<BR>Rs1-&gt;Filter = (long) adFilterNone;<BR>......<BR>if (picRs) picRs-&gt;Release();<BR>Rs1-&gt;Close();<BR>pConn-&gt;Close();<BR>只要字段的状态是adFldOK，就可以访问。如果修改了字段，不要忘了先调用picRs的Update（注意不是Recordset的Update），然后才关闭，也不要忘了释放picRs（即picRs-&gt;Release();）。<BR>(4). 此时还可以用IADORecordBinding接口添加新纪录<BR>&nbsp;if(FAILED(picRs-&gt;AddNew(&amp;rs)))<BR>&nbsp;......<BR>11. 访问长数据<BR>在Microsoft SQL中的长数据包括text、image等这样长类型的数据，作为二进制字节来对待。<BR>可以用Field对象的GetChunk和AppendChunk方法来访问。每次可以读出或写入全部数据的一部分，它会记住上次访问的位置。但是如果中间访问了别的字段后，就又得从头来了。<BR>请看下面的例子：<BR>//写入一张照片到数据库：<BR>VARIANT varChunk;<BR>SAFEARRAY *psa;<BR>SAFEARRAYBOUND rgsabound[1];<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>//VT_ARRAY | VT_UI1<BR>CFile f("h:\\aaa.jpg",CFile::modeRead);<BR>BYTE&nbsp; bVal[ChunkSize+1];<BR>UINT uIsRead=0;<BR>//Create a safe array to store the array of BYTES&nbsp; <BR>while(1)<BR>{<BR>&nbsp;uIsRead=f.Read(bVal,ChunkSize);<BR>&nbsp;if(uIsRead==0)break;<BR>&nbsp;rgsabound[0].cElements =uIsRead;<BR>&nbsp;&nbsp;&nbsp; rgsabound[0].lLbound = 0;<BR>&nbsp;psa = SafeArrayCreate(VT_UI1,1,rgsabound);<BR>&nbsp;for(long index=0;index&lt;uIsRead;index++)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;{<BR>&nbsp;&nbsp;if(FAILED(SafeArrayPutElement(psa,&amp;index,&amp;bVal[index])))<BR>&nbsp;&nbsp;::MessageBox(NULL,"啊，又出毛病了。","提示",MB_OK | MB_ICONWARNING);<BR>&nbsp;}<BR>&nbsp;varChunk.vt = VT_ARRAY|VT_UI1;<BR>&nbsp;varChunk.parray = psa;<BR>&nbsp;try{<BR>&nbsp;&nbsp;m_pRecordset-&gt;Fields-&gt;GetItem("photo")-&gt;AppendChunk(varChunk); <BR>&nbsp;}<BR>&nbsp;catch (_com_error &amp;e)<BR>&nbsp;{<BR>&nbsp;&nbsp;CString str=(char*)e.Description();<BR>&nbsp;&nbsp;::MessageBox(NULL,str+"\n又出毛病了。","提示",MB_OK | MB_ICONWARNING);<BR>&nbsp;}<BR>&nbsp;::VariantClear(&amp;varChunk);<BR>&nbsp;::SafeArrayDestroyData( psa);<BR>&nbsp;if(uIsRead&lt;ChunkSize)break;<BR>}//while(1)&nbsp; <BR>f.Close();<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>//从数据库读一张照片：<BR>CFile f;<BR>f.Open("h:\\bbb.jpg",CFile::modeWrite|CFile::modeCreate);<BR>long lPhotoSize = m_pRecordset-&gt;Fields-&gt;Item["photo"]-&gt;ActualSize;&nbsp; <BR>long lIsRead=0;<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>_variant_t varChunk;<BR>BYTE buf[ChunkSize];<BR>while(lPhotoSize&gt;0)<BR>{<BR>&nbsp;lIsRead=lPhotoSize&gt;=ChunkSize? ChunkSize:lPhotoSize;<BR>&nbsp;varChunk = m_pRecordset-&gt;Fields-&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Item["photo"]-&gt;GetChunk(lIsRead);<BR>&nbsp;for(long index=0;index&lt;lIsRead;index++)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;::SafeArrayGetElement(varChunk.parray,&amp;index,buf+index);&nbsp;&nbsp;&nbsp;<BR>&nbsp;}<BR>&nbsp;f.Write(buf,lIsRead);<BR>&nbsp;lPhotoSize-=lIsRead;<BR>}//while()<BR>f.Close();<BR>12. 使用SafeArray问题<BR>学会使用SafeArray也是很重要的，因为在ADO编程中经常要用。它的主要目的是用于automation中的数组型参数的传递。因为在网络环境中，数组是不能直接传递的，而必须将其包装成SafeArray。实质上SafeArray就是将通常的数组增加一个描述符，说明其维数、长度、边界、元素类型等信息。SafeArray也并不单独使用，而是将其再包装到VARIANT类型的变量中，然后才作为参数传送出去。在VARIANT的vt成员的值如果包含VT_ARRAY|...,那么它所封装的就是一个SafeArray，它的parray成员即是指向SafeArray的指针。SafeArray中元素的类型可以是VARIANT能封装的任何类型，包括VARIANT类型本身。&nbsp;<BR>使用SafeArray的具体步骤：<BR>方法一：<BR>&nbsp;包装一个SafeArray：<BR>(1). 定义变量，如：<BR>&nbsp;VARIANT varChunk;<BR>&nbsp;SAFEARRAY *psa;<BR>&nbsp;&nbsp;&nbsp; SAFEARRAYBOUND rgsabound[1];<BR>(2). 创建SafeArray描述符：<BR>&nbsp;uIsRead=f.Read(bVal,ChunkSize);//read array from a file.<BR>&nbsp;if(uIsRead==0)break;<BR>&nbsp;rgsabound[0].cElements =uIsRead;<BR>&nbsp;rgsabound[0].lLbound = 0;<BR>&nbsp;psa = SafeArrayCreate(VT_UI1,1,rgsabound);<BR>(3). 放置数据元素到SafeArray：<BR>&nbsp;for(long index=0;index&lt;uIsRead;index++)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;{<BR>&nbsp;&nbsp;if(FAILED(SafeArrayPutElement(psa,&amp;index,&amp;bVal[index])))<BR>&nbsp;&nbsp;&nbsp;::MessageBox(NULL,"出毛病了。","提示",MB_OK | MB_ICONWARNING);<BR>&nbsp;}<BR>&nbsp;一个一个地放，挺麻烦的。<BR>(4). 封装到VARIANT内：<BR>&nbsp;varChunk.vt = VT_ARRAY|VT_UI1;<BR>&nbsp;varChunk.parray = psa;<BR>&nbsp;这样就可以将varChunk作为参数传送出去了。<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;读取SafeArray中的数据的步骤：<BR>(1). 用SafeArrayGetElement一个一个地读<BR>&nbsp;BYTE buf[lIsRead];<BR>&nbsp;for(long index=0;index&lt;lIsRead;index++)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;::SafeArrayGetElement(varChunk.parray,&amp;index,buf+index);&nbsp;&nbsp;&nbsp;<BR>&nbsp;}<BR>&nbsp;就读到缓冲区buf里了。<BR>方法二：<BR>&nbsp;使用SafeArrayAccessData直接读写SafeArray的缓冲区：<BR>(1). 读缓冲区：<BR>&nbsp;BYTE *buf;<BR>&nbsp;SafeArrayAccessData(varChunk.parray, (void **)&amp;buf);<BR>&nbsp;f.Write(buf,lIsRead);<BR>&nbsp;SafeArrayUnaccessData(varChunk.parray);<BR>(2). 写缓冲区：<BR>&nbsp;BYTE *buf;<BR>&nbsp;::SafeArrayAccessData(psa, (void **)&amp;buf);<BR>&nbsp;for(long index=0;index&lt;uIsRead;index++)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;{<BR>&nbsp;&nbsp;buf[index]=bVal[index];&nbsp;&nbsp;<BR>&nbsp;}<BR>&nbsp;::SafeArrayUnaccessData(psa);<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>&nbsp;varChunk.vt = VT_ARRAY|VT_UI1;<BR>&nbsp;varChunk.parray = psa;<FONT color=#ffffcc>www.chinai tp 采吧采吧不是罪 ow er.comJuJIe</FONT></P>
<P>这种方法读写SafeArray都可以，它直接操纵SafeArray的数据缓冲区，比用SafeArrayGetElement和SafeArrayPutElement速度快。特别适合于读取数据。但用完之后不要忘了调用::SafeArrayUnaccessData(psa)，否则会出错的。<BR>13. 使用书签( bookmark )<BR>书签可以唯一标识记录集中的一个记录，用于快速地将当前记录移回到已访问过的记录，以及进行过滤等等。Provider会自动为记录集中的每一条记录产生一个书签，我们只需要使用它就行了。我们不能试图显示、修改或比较书签。ADO用记录集的Bookmark属性表示当前记录的书签。<BR>用法步骤：<BR>(1). 建立一个VARIANT类型的变量<BR>_variant_t VarBookmark;<BR>(2). 将当前记录的书签值存入该变量<BR>也就是记录集的Bookmark属性的当前值。<BR>&nbsp;VarBookmark = rst-&gt;Bookmark;<BR>(3). 返回到先前的记录<BR>将保存的书签值设置到记录集的书签属性中：<BR>&nbsp;// Check for whether bookmark set for a record<BR>&nbsp;if (VarBookmark.vt == VT_EMPTY)<BR>&nbsp;&nbsp;printf("No Bookmark set!\n");<BR>&nbsp;else <BR>&nbsp;&nbsp;rst-&gt;Bookmark = VarBookmark;<BR>设置完后，当前记录即会移动到该书签指向的记录。<BR>14、设置过滤条件<BR>Recordset对象的Filter属性表示了当前的过滤条件。它的值可以是以AND或OR连接起来的条件表达式（不含WHERE关键字）、由书签组成的数组或ADO提供的FilterGroupEnum枚举值。为Filter属性设置新值后Recordset的当前记录指针会自动移动到满足过滤条件的第一个记录。例如：<BR>rst-&gt;Filter&nbsp; = _bstr_t ("姓名='赵薇'&nbsp; AND&nbsp; 性别=’女’");<BR>在使用条件表达式时应注意下列问题：<BR>（1）、可以用圆括号组成复杂的表达式<BR>例如：<BR>rst-&gt;Filter&nbsp; =&nbsp; _bstr_t ("(姓名='赵薇'&nbsp; AND&nbsp; 性别=’女’)&nbsp; OR&nbsp; AGE&lt;25");<BR>但是微软不允许在括号内用OR，然后在括号外用AND，例如：<BR>rst-&gt;Filter&nbsp; = _bstr_t ("(姓名='赵薇'&nbsp; OR 性别=’女’)&nbsp; AND&nbsp; AGE&lt;25");<BR>必须修改为：<BR>rst-&gt;Filter&nbsp; = _bstr_t ("(姓名='赵薇'&nbsp; AND&nbsp; AGE&lt;25)&nbsp; OR&nbsp; (性别=’女’&nbsp; AND&nbsp; AGE&lt;25)");<BR>（2）、表达式中的比较运算符可以是LIKE<BR>LIKE后被比较的是一个含有通配符*的字符串，星号表示若干个任意的字符。<BR>字符串的首部和尾部可以同时带星号*<BR>rst-&gt;Filter&nbsp; =&nbsp; _bstr_t ("姓名 LIKE '*赵*' ");<BR>也可以只是尾部带星号：<BR>rst-&gt;Filter&nbsp; =&nbsp; _bstr_t ("姓名 LIKE '赵*' ");<BR>Filter属性值的类型是Variant，如果过滤条件是由书签组成的数组，则需将该数组转换为SafeArray，然后再封装到一个VARIANT或_variant_t型的变量中，再赋给Filter属性。<BR>15、索引与排序<BR>（1）、建立索引<BR>当以某个字段为关键字用Find方法查找时，为了加快速度可以以该字段为关键字在记录集内部临时建立索引。只要将该字段的Optimize属性设置为true即可，例如：<BR>pRst-&gt;Fields-&gt;GetItem("姓名")-&gt;Properties-&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetItem("Optimize")-&gt;PutValue("True");<BR>pRst-&gt;Find("姓名 = '赵薇'",1,adSearchForward);<BR>......<BR>pRst-&gt;Fields-&gt;GetItem("姓名")-&gt;Properties-&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetItem("Optimize")-&gt;PutValue("False");<BR>pRst-&gt;Close();<BR>说明：Optimize属性是由Provider提供的属性（在ADO中称为动态属性），ADO本身没有此属性。<BR>（2）、排序<BR>要排序也很简单，只要把要排序的关键字列表设置到Recordset对象的Sort属性里即可，例如：<BR>pRstAuthors-&gt;CursorLocation = adUseClient;<BR>pRstAuthors-&gt;Open("SELECT * FROM mytable",<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _variant_t((IDispatch *) pConnection),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adOpenStatic, adLockReadOnly, adCmdText);<BR>......<BR>pRst-&gt;Sort = "姓名 DESC, 年龄 ASC";<BR>关键字（即字段名）之间用逗号隔开，如果要以某关键字降序排序，则应在该关键字后加一空格，再加DESC（如上例）。升序时ASC加不加无所谓。本操作是利用索引进行的，并未进行物理排序，所以效率较高。<BR>但要注意，在打开记录集之前必须将记录集的CursorLocation属性设置为adUseClient，如上例所示。Sort属性值在需要时随时可以修改。<BR>16、事务处理<BR>ADO中的事务处理也很简单，只需分别在适当的位置调用Connection对象的三个方法即可，这三个方法是：<BR>（1）、在事务开始时调用<BR>pCnn-&gt;BeginTrans();<BR>（2）、在事务结束并成功时调用<BR>pCnn-&gt;CommitTrans ();<BR>（3）、在事务结束并失败时调用<BR>pCnn-&gt;RollbackTrans ();<BR>在使用事务处理时，应尽量减小事务的范围，即减小从事务开始到结束（提交或回滚）之间的时间间隔，以便提高系统效率。需要时也可在调用BeginTrans()方法之前，先设置Connection对象的IsolationLevel属性值，详细内容参见MSDN中有关ADO的技术资料。<BR>三、使用ADO编程常见问题解答<BR>以下均是针对MS SQL 7.0编程时所遇问题进行讨论。<BR>1、连接失败可能原因<BR>Enterprise Managemer内，打开将服务器的属性对话框，在Security选项卡中，有一个选项Authentication。<BR>如果该选项是Windows NT only，则你的程序所用的连接字符串就一定要包含Trusted_Connection参数，并且其值必须为yes，如：<BR>"Provider=SQLOLEDB;Server=888;Trusted_Connection=yes"<BR>&nbsp;&nbsp;";Database=master;uid=lad;";<BR>如果不按上述操作，程序运行时连接必然失败。<BR>如果Authentication选项是SQL Server and Windows NT，则你的程序所用的连接字符串可以不包含Trusted_Connection参数，如：<BR>"Provider=SQLOLEDB;Server=888;Database=master;uid=lad;pwd=111;";<BR>因为ADO给该参数取的默认值就是no，所以可以省略。我认为还是取默认值比较安全一些。<BR>2、改变当前数据库的方法<BR>使用Tansct-SQL中的USE语句