文档库 最新最全的文档下载
当前位置:文档库 › MFC运用ADO操作数据库

MFC运用ADO操作数据库

MFC运用ADO操作数据库
MFC运用ADO操作数据库

MFC运用ADO操作数据库

一,数据库访问技术

目前数据库接口问技术主要有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_namespacerename("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对象并连接数据库

针对链接数据库的方法主要有两种,静态链接和动态链接。

静态链接:

_ConnectionPtrm_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)///捕捉异常

{

CStringerrormessage;

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;

IDataSourceLocatorPtrdlPrompt=NULL;

_RecordsetPtrrs=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对象。

_RecordsetPtrm_pRecordset;

m_pRecordset.CreateInstance("ADODB.Recordset");

或者直接写成:

_RecordsetPtrpRecordset ("ADODB.Recordset");

也可以这样写:

_RecordsetPtrpRecordset;

pRecordset.CreateInstance(__uuidof(_Recordset));

注意,如果变量是一个类的实例则用"."操作符,若是一个指向实例的指针则应使用"->"操作符。

一个变量能通过两种方式被使用。因为"->"操作符被重载,允许一个对象实例类似一个接口指针那样被使用;"->"操作符返回该指针;而由这个返回的指针访问_Recordset对象的成员。

如下代码,获得表中所有记录到Recordset对象

CStringstrSQL = "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_tvValue = 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,

enumCursorTypeEnumCursorType,

enumLockTypeEnumLockType,

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

{

_RecordsetPtrpRs("ADODB.Recordset");

_ConnectionPtrpCn("ADODB.Connection");

_variant_tvtTableName("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

{

_ConnectionPtrpConn("ADODB.Connection");

_RecordsetPtrpRst("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;

CStringFilePathName;

CFileException e;

CFileDialogdlg(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文件

{

intnSize = 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();

}

CClientDCdc(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) );//显示出来看看 }

相关文档