// 建立连接
pConn=dlPrompt->PromptNew();
// 如果 conn 为 NULL
if(pConn==NULL)
return;
// 打开连接
pConn->Open(pConn->ConnectionString,L"",L"",-1);
// 清除列表框
while(m_tblList.GetCount()>0)
m_tblList.DeleteString(0);
// 获取数据库中表集
rs=pConn->OpenSchema(adSchemaTables);
while(!rs->adoEOF)
{
m_tblList.AddString((char*)(_bstr_t)rs->Fields->Item[L"TABLE_NAME"]->Value);
rs->MoveNext();
}
rs=NULL;
dlPrompt.Release();
}
catch (_com_error &e)
{
AfxMessageBox(e.ErrorMessage());
}
需要注意的是,在建立工程时,要选上Automation选项。(具体情况看)
三,ADO的数据操作
1.Recordset对象操作数据
Recordset对象是操作数据最简单的方法,也比较直接,特别可以直接用SQL语句来进行操作。我本人也最常用Recordset对象。
_RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance("ADODB.Recordset");
或者直接写成:
_RecordsetPtr pRecordset ("ADODB.Recordset");
也可以这样写:
_RecordsetPtr pRecordset;
pRecordset.CreateInstance(__uuidof(_Recordset));
注意,如果变量是一个类的实例则用"."操作符,若是一个指向实例的指针则应使用"->"操作符。
一个变量能通过两种方式被使用。因为"->"操作符被重载,允许一个对象实例类似一个接口指针那样被使用;"->"操作符返回该指针;而由这个返回的指针访问_Recordset对象的成员。
如下代码,获得表中所有记录到Recordset对象
CString strSQL = "select * from student";
m_pRecordset->Open(strSQL,_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
第一个参数是要执行的SQL字符串,第二个是活动连接
第三个参数是光标类型。可以取如下值
adOpenUnspecified = -1,///不作特别指定
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark, RecordCount,AbsolutePosition,AbsolutePage都不能使用
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你记录集来说是不可见的。
第四个参数,锁定类型。
adLockUnspecified = -1,///未指定
adLockReadOnly = 1,///只读记录集
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
第五个参数。取如下值之一:
adCmdText:表明CommandText是文本命令
adCmdTable:表明CommandText是一个表名
adCmdProc:表明CommandText是一个存储过程
adCmdUnknown:未知
取得某条记录上某字段的值
_variant_t vValue = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
vValue = m_pRecordset->GetCollect("FieldName");///取得username字段的值
删除满足条件的记录。移动光标到要删除的记录。如:
m_pRecordset->MoveFirst();///移到首条记录
m_pRecordset->Delete(adAffectCurrent);///删除当前记录
添加记录
m_pRecordset->AddNew();
m_pRecordset->PutCollect("ID", _variant_t("value"));
m_pRecordset->Updata();
修改记录
m_pRecordset->PutCollect("ID", _variant_t("value"));
m_pRecordset->Updata();
2.属性
对于类中的每个操作(或称方法、属性调用),都有一个声明以保证能直接调用它(或称作操作的源形式),以及另一个声明来调用这个源操作并在操作失败时抛出一个COM错误。如果操作是一个属性,那么编译指示符可以为该操作创建一个可交互的类似VB的语法形式。
返回/设置属性的操作有对应的形式化的名字—GetProperty/PutPropert,而设置一个指向某个ADO对象的指针型属性值时则是PutRefProperty。你将使用如下的形式读写属性的值:
variable = objectPtr->GetProperty(); // 读取属性的值
objectPtr->PutProperty(value); // 设置属性的值
objectPtr->PutRefProperty(&value); // 设置一个指针型的属性的值
直接使用属性
__declspec(property...)编译指示符是微软定义的一个针对C语言的扩展,使一个函数象一个属性那样被使用。这样你就可以采用如下的语法形式象在使用VB一样读写一个属性的值: objectPtr->property = value; // 设置属性的值
variable = objectPtr->property; // 读取属性的值
__declspec(property...)编译指示符只能针对属性的读写函数使用,并根据属性是否可供读写自动生成对应的调用形式。每个属性可能有 GetProperty, PutProperty,PutRefProperty三个函数,但这个编译符只能生成其中的两种交互形式。比如,Command对象的 ActiveConnection属性有GetActiveConnection和PutRefActiveConnection这两个读写函数。而 PutRef-的形式在实践中是个好的选择,你可以将一个活动的Connection对象的指针保存在这个属性中。另一方面,Recordset对象则有 Get-, Put-, and PutRefActiveConnection操作,但却没有可交互的语法形式。
Collections,GetItem方法和Item属性
ADO定义了几种集合Collection,包括Fields,Parameters,Properties,和Errors。在Visual C++中,GetItem(index)方法返回Collection中的某个成员。Index是一个Variant型的参数,内容可以是一个该成员对应的序数,也可以是一个包括其名称的字符串。
__declspec(property...)编译指示符为Item属性生成对应于GetItem()方法的直接使用形式(上文提到的可交互的语法形式)。这种形式类似于引用数组元素时使用[]的语法形式:
collectionPtr->GetItem(index);
collectionPtr->Item[index];
举例说明,要给一个Recordset对象rs中的某个字段赋值,而这个Recordset对象派生于pubs数据库中的authors表。使用Item ()属性访问这个Recordset的Fields集合中的第三个字段(集合总是从0开始编号,假设第三个字段名为au_fname)。然后调用 Value()方法为该字段赋一个字符串值。
rs->Fields->GetItem(2)->PutValue("value");
rs->Fields->GetItem("au_fname")->PutValue("value");
或者:
rs->Fields->Item[2]->Value = "value";
rs->Fields->Item["au_fname"]->Value = "value";
3. COM特定的数据类型
专属于COM使用的数据类型则有:Variant, BSTR, and SafeArray。
Variant
Variant是一个结构化的数据类型,包含了一个成员值及其数据类型的表示。Variant可以表示相当多的数据类型,甚至另一个Variant, BSTR, Boolean, Idispatch或Iunknown指针,货币,日期等等。同时COM也提供了许多方法使数据类型间的转换更简单化。
_variant_t类封装并管理Variant这一数据类型。
当ADO的一个方法或属性要使用一个参数时,通常意味着需要一个_variant_t类型的参数。作为例外的是,有时则会要求操作数是一个标准的数据类型,比如Long或Byte, 或者一个枚举值。另一个例外是要求操作数是一个字符串String。
BSTR
BSTR (Basic STRing)也是一个结构化的数据类型,包括了串及串的长度。COM提供了方法进行串的空间分配、操作、释放。
_bstr_t类封装并管理BSTR这一数据类型。
一个方法或属性要使用一个字符串参数时,通常意味着需要一个类_bstr_t型的参数。
_variant_t和_bstr_t类的强制类型转换
通常当传递一个_variant_t或_bstr_t参数给一个操作时并不需要显式的类型转换代码。如果_variant_t或_bstr_t类提供了对应于该参数类型的构造函数,那么编译器将会自动生成适当的_variant_t或_bstr_t值。
然而,当参数模棱两可时,即对应了多个构造函数时,你就必须显式地调用正确的构造函数以获得正确的参数。比如,Recordset::Open方法的函数声明如下:
HRESULT Open (
const _variant_t & Source,
const _variant_t & ActiveConnection,
enum CursorTypeEnum CursorType,
enum LockTypeEnum LockType,
long Options );
其中参数ActiveConnection就是针对一个variant_t型变量的引用,它可以是一个连接串或者一个指向已打开的Connection对象的指针。
正确的_variant_t型参数会被构造,无论你传递的是一个类似"DSN=pubs;uid=sa;pwd=;"这样的字符串,或者是一个类似" (IDispatch *) pConn"的指针。或者你还可以显式的编写"_variant_t((IDispatch *) pConn, true)"这样的代码来传递一个包含指针的_variant_t变量。这里的强制类型转换(IDispatch *)避免了可能调用IUnknown接口构造函数的模棱两可性。虽然很少提及但特别重要的是,ADO总是一个IDispatch接口。任何被传递的被包含在Variant中的指针都必须被转换为一个IDispatch接口指针。
最后需要说明的是构造函数的第二个逻辑参数是可选择的,它的缺省值是True。这个参数将决定Variant的构造函数是否调用内嵌的AddRef()方法,并在完成ADO的方法或属性调用后是否自动调用_variant_t::Release()方法
SafeArray
SafeArray也是一种结构化的数据类型,包含了一个由其它数据类型的数据元素组成的数组。之所以称之为安全的数组是因为它包含了每一维的边界信息,并限制在边界内进行数组元素的访问。
一个方法或属性要使用或者返回一个数组时,通常意味着是一个SafeArray数组,而非一个本地化的C/C++数组。
比如,Connection对象的OpenSchema方法的第二个参数需要一个由Variant值组成的数组。这些Variant值必须作为一个SafeArray数组的元素进行传递。而这个SafeArray数组本身又被作为一个Variant进行传递。
更进一步的,Find方法的第一个参数是一个指向一维SafeArray数组的Variant;AddNew方法的可选的第一与第二个参数也是一个一维的SafeArray数组;GetRows方法的返回值则是一个包含二维SafeArray数组的Variant。
以下的代码演示了如何通过一个_variant_t使用一个SafeArray数组。注意注释对应了编码的步骤。
1.再一次的,TESTHR()内置函数被定义以利用预存的错误处理机制。
2.如果你只需要一个一维数组,你可以使用SafeArrayCreateVector,而非SAFEARRAYBOUND声明与SafeArrayCreate函数。下面的代码使用了SafeArrayCreate:
SAFEARRAYBOUND sabound[1];
sabound[0].lLbound = 0;
sabound[0].cElements = 4;
pSa = SafeArrayCreate(VT_VARIANT, 1, sabound);
3.枚举常量adSchemaColumns定义的模式,决定了与TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME和COLUMN_NAME四列相联系。为此,一个有四个Variant元素的数组被创建。而对应于第三列TABLE_NAME的值被设置。
由若干列组成的返回的Recordset只是对应的所有列的一个子集,并且每一行的值保持了一一对应。
4.熟悉SafeArrays的人也许会对退出前没有调用SafeArrayDestroy()感到惊奇。实际上,在这种情况下调用 SafeArrayDestroy()会导致一个运行时的异常发生。这是因为vtCriteria的析构函数会在_variant_t超出使用范围时调用 VariantClear(),从而释放SafeArray。只调用SafeArrayDestroy,而没有手动清除_variant_t,将会导致析构函数试图去清除一个无效的SafeArray指针。如果要调用SafeArrayDestroy(),那么代码应该象这样:
TESTHR(SafeArrayDestroy(pSa));
vtCriteria.vt = VT_EMPTY;
vtCriteria.parray = NULL;
实际更像是让_variant_t管理SafeArray。
完整的代码如下:
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
no_namespace rename("EOF", "EndOfFile")
#include
// Note 1
inline void TESTHR( HRESULT _hr )
{ if FAILED(_hr) _com_issue_error(_hr); }
void main(void)
{
CoInitialize(NULL);
try
{
_RecordsetPtr pRs("ADODB.Recordset");
_ConnectionPtr pCn("ADODB.Connection");
_variant_t vtTableName("authors"),
vtCriteria;
long ix[1];
SAFEARRAY *pSa = NULL;
pCn->Open("DSN=pubs;User ID=sa;pwd=;Provider=MSDASQL;", "", "",
adConnectUnspecified);
// Note 2, Note 3
pSa = SafeArrayCreateVector(VT_VARIANT, 1, 4);
if (!pSa) _com_issue_error(E_OUTOFMEMORY);
// 为第三个元素赋值TABLE_NAME(索引值2).
ix[0] = 2;
TESTHR(SafeArrayPutElement(pSa, ix, &vtTableName));
// 由于Variant没有SafeArray的构造函数,所以手工设置Variant的数据类型和值。
vtCriteria.vt = VT_ARRAY | VT_VARIANT;
vtCriteria.parray = pSa;
pRs = pCn->OpenSchema(adSchemaColumns, vtCriteria, vtMissing);
long limit = pRs->GetFields()->Count;
for (long x = 0; x < limit; x++)
printf("%d: %s/n", x+1,
((char*) pRs->GetFields()->Item[x]->Name));
// Note 4
pRs->Close();
pCn->Close();
}
catch (_com_error &e)
{
printf("Error:/n");
printf("Code = %08lx/n", e.Error());
printf("Code meaning = %s/n", (char*) e.ErrorMessage());
printf("Source = %s/n", (char*) e.Source());
printf("Description = %s/n", (char*) e.Description());
}
CoUninitialize();
}
利用(IDispatch *)转换ADO对象的指针类型
1.在一个Variant中显式地封装一个活动的Connection对象,然后用(IDispatch *)进行类型转换确保正确的构造函数被调用。同时明确地设置第二个参数为缺省的true,使该对象的引用计数在Recordset::Open操作完成后仍得到正确的维护。
2.表达式(_bstr_t)不是一个类型转换,而是一个_variant_t的操作符,用以从中提取一个_bstr_t字符串。
表达式(char*)也不是一个类型转换,而是一个_bstr_t的操作符,用以从中提取封装在_bstr_t中的字符串的指针。
下面这些代码演示了_variant_t和_bstr_t的一些常见操作。
#import "c:/Program Files/Common Files/System/ADO/msado15.dll" /
no_namespace rename("EOF", "EndOfFile")
#include
void main(void)
{
CoInitialize(NULL);
try
{
_ConnectionPtr pConn("ADODB.Connection");
_RecordsetPtr pRst("ADODB.Recordset");
pConn->Open("Provider=sqloledb;Data Source=a-tima10;"
"Initial Catalog=pubs;User Id=sa;Password=;",
"", "", adConnectUnspecified);
// Note 1
pRst->Open(
"authors",
_variant_t((IDispatch *) pConn, true),
adOpenStatic,
adLockReadOnly,
adCmdTable);
pRst->MoveLast();
// Note 2
printf("Last name is '%s %s'/n",
(char*) ((_bstr_t) pRst->GetFields()->GetItem("au_fname")->GetValue()),
(char*) ((_bstr_t) pRst->Fields->Item["au_lname"]->Value));
pRst->Close();
pConn->Close();
}
catch (_com_error &e)
{
printf("Description = '%s'/n", (char*) e.Description());
}
::CoUninitialize();
}