您的位置:首页 > 数据库

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

2011-11-19 23:25 866 查看
 
一,数据库访问技术

目前数据库接口问技术主要有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();

}

4. COM出错处理
在COM中,大多数的操作总是返回一个HRESULT值说明该函数是否被成功完成。编译指示符#import为所有源方法和属性提供了封装好的代码并检查返回的HRESULT值。如果HRESULT指示失败,这些封装代码将会通过调用以HRESULT为参数的_com_issue_errorex()抛出一个COM错误。COM错误对象将在try-catch块中被捕获(出于效率的考虑,实际捕获的是一个_com_error对象的引用指针)。

记住,由ADO操作失败产生的错误才是ADO错误。由下层提供者返回的错误以Connection对象中Errors集合中的一个Error对象的形式出现。

编译指示符#import只能为在ADO.dll中声明的方法和属性提供错误处理例程。因此,你可以基于同样的错误处理机制编写自己的错误检查宏或内置函数

5.大数据的保存
 在数据库中可能需要保存图片或大文本等一些大数据量的资料,这时使用通常方法可能行不通的。下面是一个jpg文件保存的实例,看一下就知道实现方法:AppendChunk。

 CFile f;

 CString  FilePathName;

 CFileException e;

 CFileDialog dlg(TRUE,NULL,NULL,0,"jpg Files (*.jpg)|*.jpg||",this);

 if(dlg.DoModal()==IDOK)

 {

  FilePathName=dlg.GetPathName();

  if(m_Pic.m_IPicture != NULL) m_Pic.FreePictureData(); // Important - Avoid Leaks...

  if(f.Open(FilePathName, Cfile::modeRead | Cfile::typeBinary, &e)) //打开了一个jpg文件

  {

   int nSize = f.GetLength();          //先得到jpg文件长度

   BYTE * pBuffer = new BYTE [nSize];  //按文件的大小在堆上申请一块内存

 

   if (f.Read(pBuffer, nSize) > 0 )    //把jpg文件读到pBuffer(堆上申请一块内存)

   {

    BYTE *pBuf = pBuffer;     ///下面这一大段是把pBuffer里的jpg数据放到库中

    VARIANT   varBLOB;

    SAFEARRAY  *psa;

    SAFEARRAYBOUND rgsabound[1];

   

    m_pRecordset->AddNew(); 

      

    if(pBuf)

    {   

     rgsabound[0].lLbound = 0;

     rgsabound[0].cElements = nSize;

     psa = SafeArrayCreate(VT_UI1, 1, rgsabound);

     for (long i = 0; i < (long)nSize; i++)

     SafeArrayPutElement (psa, &i, pBuf++);

     varBLOB.vt = VT_ARRAY | VT_UI1;

     varBLOB.parray = psa;

     m_pRecordset->GetFields()->GetItem("j")->AppendChunk(varBLOB);

    }

    m_pRecordset->Update();

     

   (m_Pic.LoadPictureData(pBuffer, nSize));//接作调用函数读pBuffer的jpg数据准备显示

   delete [] pBuffer;     //删掉堆上申请的那一块内存

   pBuf=0;                //以防二次乱用

   }

   f.Close();

  }

  CClientDC dc(this);       

  m_Pic.UpdateSizeOnDC(&dc); // Get Picture Dimentions In Pixels

  m_Pic.Show(&dc, CRect(200,0,200+m_Pic.m_Width,m_Pic.m_Height) );//显示出来看看

 }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息