VC中使用ADO访问数据库技术程序实现方法

时间:2021-04-15 08:17:50
 
一,数据库访问技术
目前数据库接口问技术主要有DAO,RDO,ODBC,OLE DB和ADO
ODBC,开发数据库互连。为数据库开发提供统一的接口,可以与任何具有ODBC驱动程序的数据库通信。和其他数据库访问技术相比,属于比较底层的数据库接口。只能和关心数据库进行通信,不能访问非关系数据库。
DAO,数据访问对象。基于COM的面向对象数据库编程模型。是一组基于Mircrosoft Access Jet引擎的COM自动化接口。直接与Access/Jet数据库通信,通过Jet引擎也可以与其他数据库通信。效率不高。最初是作为ODBC API的抽象,为Visual Basic程序员提供的编程对象。使用ODBC API对数据源进行操作。不需要经过Jet引擎,比DAO效率高。
OLE DB,对象链接嵌入数据库。未来数据库访问的发展模式。提供COM接口,与其他数据库访问技术相比,具有更好的健壮性和灵活性,更高的容错能力。属于底层访问技术。可以与关系和非关系型数据库进行通信。
ADO,ActiveX Data Object。对OLE DB的高层次封装。简化了OLE DB,属于高层的数据库接口。另外同OLE DB相比,能够使用ADO的编程语言更多。ADO提供一个自动化接口,使VBScript和JavaScript等脚本语言可以使用ADO。
二,ADO访问基础
1.引入ADO类型库

    在Stdafx.h中使用如下指令引入类型库:
    #import "c:/program files/common files/system/ado/msado15.dll" no_namespace rename("EOF","adoEOF")
    根据操作系统的不同,msado*.dll的版本不同,目前最高版本是2.1吧。rename("EOF","adoEOF")是为了防止和别的结束标示重名。
    在编译过程中不用理会下面的编译警告:
    warning: unary minus operator applied to unsigned type, result still unsigned
    如果不想此警告出现,可以在 StdAfx.h 文件中加入这样一行代码以禁止此警告:     #pragma warning(disable:4146)
对于指定ADO版本
//如果使用 ADO 2.0 加入下面代码
#import "C:/Program Files/Common Files/System/Ole DB/msdasc.dll" no_namespace
//如果使用 ADO 2.1 加入下面代码
#import "C:/Program Files/Common Files/System/Ole DB/oledb32.dll" no_namespace
注:ADO2.0 或 ADO2.1 中,如果其中一个编译不成功,则用另一个
2.初始化COM库
    一般中应用程序类初始化函数中初始化COM库,当然也可以在别的地方。只要是在使用之前初始化就可以。
AfxOleInit();    // 初始化COM环境
上面是MFC环境下注册COM的方法,如果非MFC环境使用方法如下:
 CoInitialize(NULL);
 CoUnInitialize();
3.创建Connection对象并连接数据库
 针对链接数据库的方法主要有两种,静态链接和动态链接。
静态链接:
_ConnectionPtr m_pConnection;
    在初始化函数中初始化它。
    try
   {
          hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
          if(SUCCEEDED(hr))
          {
              hr = m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
      ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为            :Provider=Microsoft.Jet.OLEDB.3.51;
          }
  }
  catch(_com_error e)///捕捉异常
  {
      CString errormessage;
      errormessage.Format("连接数据库失败!/r/n错误信息:%s",e.ErrorMessage());
      AfxMessageBox(errormessage);///显示错误信息
  }
 
  //假设数据库名称test.mdb;数据源为test,下面使用不同的方法连接数据库
  使用ADO连接ACCESS2000数据库
  m_pConnection->Open("Provider=Mircrosoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);
  使用ADO连接ACCESS97数据库
  m_pConnection->Open("Provider=Mircrosoft.Jet.OLEDB.3.51;Data Source=test.mdb","","",adModeUnKnown);
  使用DSN数据源对任何支持ODBC的数据库进行连接
  m_pConnection->Open("Data Source=test;UID=sa;PWD=","","",adModeUnknown);
  不通过数据源对SQL SERVER数据库进行连接
  m_pConnection->Open("driver={SQL SERVER};Server=127.0.0.1;DATABASE=TEST;UID=sa;PWD=","","",adModeUnknown);
 
  设置数据库连接超时,需要在Open之前调用
  m_pConnection->ConnectionTimeOut = 30;//秒
动态链接:
HRESULT hr;
IDataSourceLocatorPtr dlPrompt=NULL;
_RecordsetPtr rs=NULL;
try
{
  // 初始化DataLinks对象
  hr=dlPrompt.CreateInstance(__uuidof(DataLinks));
  if(FAILED(hr))
 throw(_com_error(hr,NULL));

  // 建立连接
  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();
}