VC开发数据库基础之ADO篇 (转载)

VC开发数据库基础之ADO篇
 
一、ADO简介
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。

二、基本流程
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
(1)初始化COM库,引入ADO库定义文件
(2)用Connection对象连接数据库
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
(4)使用完毕后关闭连接释放对象。

准备工作:
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
下面我们将详细介绍上述步骤并给出相关代码。
【1】COM库的初始化
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:


BOOL CADOTest1App::InitInstance()
{
AfxOleInit();
......

【2】用#import指令引入ADO类型库
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。

几点说明:
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned

【3】创建Connection对象并连接数据库
首先我们需要添加一个指向Connection对象的指针:
_ConnectionPtr m_pConnection;
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。


BOOL CADOTest1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
HRESULT hr;
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);///显示错误信息
}

在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
Options可以是如下几个常量:
adModeUnknown:缺省。当前的许可权未设置
adModeRead:只读
adModeWrite:只写
adModeReadWrite:可以读写
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
adModeShareExclusive:阻止其它Connection对象打开连接
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接

我们给出一些常用的连接方式供大家参考:
(1)通过JET数据库引擎对ACCESS2000数据库的连接

m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);

(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
m_pConnection->Open("Data Source=adotest;UID=sa;PWD=;","","",adModeUnknown);

(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);

其中Server是SQL服务器的名称,DATABASE是库的名称

Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);


State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
if(m_pConnection->State)
m_pConnection->Close(); ///如果已经打开了连接则关闭它


【4】执行SQL命令并取得结果记录集
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
SQL命令的执行可以采用多种形式,下面我们一进行阐述。

(1)利用Connection对象的Execute方法执行SQL命令
Execute方法的原型如下所示:
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
adCmdText:表明CommandText是文本命令
adCmdTable:表明CommandText是一个表名
adCmdProc:表明CommandText是一个存储过程
adCmdUnknown:未知
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);
///往表格里面添加记录
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, ''''''''Washington'''''''',25,''''''''1970/1/1'''''''')",&RecordsAffected,adCmdText);
///将所有记录old字段的值加一
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
///执行SQL统计命令得到包含记录条数的记录集
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
_variant_t vIndex = (long)0;
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
m_pRecordset->Close();///关闭记录集
CString message;
message.Format("共有%d条记录",vCount.lVal);
AfxMessageBox(message);///显示当前记录条数


(2)利用Command对象来执行SQL命令
_CommandPtr m_pCommand;
m_pCommand.CreateInstance("ADODB.Command");
_variant_t vNULL;
vNULL.vt = VT_ERROR;
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集

在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。


(3)直接用Recordset对象进行查询取得记录集
例如

m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);

Open方法的原型是这样的:
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
其中:
①Source是数据查询字符串
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
enum CursorTypeEnum
{
adOpenUnspecified = -1,///不作特别指定
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
};
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
enum LockTypeEnum
{
adLockUnspecified = -1,///未指定
adLockReadOnly = 1,///只读记录集
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
};
⑤Options请参考本文中对Connection对象的Execute方法的介绍


【5】记录集的遍历、更新
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。


_variant_t vUsername,vBirthday,vID,vOld;
_RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
{
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
vOld = m_pRecordset->GetCollect("old");
vBirthday = m_pRecordset->GetCollect("birthday");
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
m_pRecordset->MoveNext();///移到下一条记录
}
m_pRecordset->MoveFirst();///移到首条记录
m_pRecordset->Delete(adAffectCurrent);///删除当前记录
///添加三条新记录并赋值
for(int i=0;i<3;i++)
{
m_pRecordset->AddNew();///添加新记录
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
m_pRecordset->PutCollect("username",_variant_t("叶利钦"));
m_pRecordset->PutCollect("old",_variant_t((long)71));
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
}
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
m_pRecordset->Update();///保存到库中

【6】关闭记录集与连接
记录集或连接都可以用Close方法来关闭
m_pRecordset->Close();///关闭记录集
m_pConnection->Close();///关闭连接

至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
点这里下载示例代码

后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等


//--------------------------------------------------------------------------------------
又一个说明:
在vc中怎么使用ADO访问数据库?

1、#import "msado15.dll" no_namespace rename("EOF","ADOEOF")
  引入"msado15.dll"动态库。

2、定义一个变量  _ConnectionPtr m_AdoCon;代表一个代表与数据源进行的唯一会话

3、打开数据库
        _bstr_t connString;   //连接字符串

try
{
connString = _T("Provider=MSDASQL.1;Data Source=kong;Persist Security Info=False");

m_AdoCon.CreateInstance(__uuidof(Connection));//???

m_AdoCon->Open(connString, "", "", -1);
}
catch (_com_error& comerr)
{
IErrorInfo* pErrorInfo = comerr.ErrorInfo();
HRESULT hr = comerr.Error();

if (pErrorInfo)
{
BSTR bsDesc = NULL;

pErrorInfo->GetDescription( &bsDesc );
_bstr_t sDesc( bsDesc, false );

AfxMessageBox(sDesc.operator LPCTSTR());

pErrorInfo->Release();
}
           }

4、使用
CString strIp, SQL_STR, strTemp, strResult;

UINT port;

_RecordsetPtr   AdoRst;               //RecordSet的实例
_StreamPtr AdoStream;                 //Ado数据流

SQL_STR = m_strText;                   //SQL语句

if (m_AdoCon->State == adStateClosed) //判断连接是否已经关闭
{
strTemp = "Ado connection hasn't set up.";
m_pWnd->SendMessage(WM_FRESHWATCHLIST, (WPARAM)(&strTemp));

SendMsg(strTemp, FALSE);

return;
}

try
{
//Create Recordset Interface
AdoRst.CreateInstance(__uuidof(Recordset));

AdoRst->Open((LPCTSTR)SQL_STR, m_AdoCon.GetInterfacePtr(),
adOpenDynamic,adLockOptimistic,adCmdUnknown);       //打开并且执行SQL(SQL_STR)语句

if (strTemp != "SELECT")
strResult =_T("OK!");
else
{
//Save the xmlResult to strResult
AdoStream.CreateInstance(__uuidof(Stream));        //

AdoRst->Save(AdoStream.GetInterfacePtr(), adPersistXML);
strResult = ((BSTR)AdoStream->ReadText(adReadAll));      //转换为XML格式
}
}
catch (_com_error& comerr)
{
//catch the COM exception
IErrorInfo* pErrorInfo = comerr.ErrorInfo();
HRESULT hr = comerr.Error();

if (pErrorInfo)
{
BSTR bsDesc = NULL;
pErrorInfo->GetDescription( &bsDesc );
_bstr_t sDesc( bsDesc, false);

strResult = sDesc.operator LPCTSTR();
pErrorInfo->Release();
}
}


5.取得表中的字段 GetCollect()
int nItem;
_variant_t vUsername,vBirthday,vID,vOld;
try
{
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)theApp.m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
m_bSuccess = TRUE;
while(!m_pRecordset->adoEOF)
{
vID = m_pRecordset->GetCollect("ID");
vUsername = m_pRecordset->GetCollect("username");
vOld = m_pRecordset->GetCollect("old");
vBirthday = m_pRecordset->GetCollect("birthday");
nItem=m_userlist.InsertItem(0xffff,(_bstr_t)vID);
m_userlist.SetItem(nItem,1,1,(_bstr_t)vUsername,NULL,0,0,0);
m_userlist.SetItem(nItem,2,1,(_bstr_t)vOld,NULL,0,0,0);
m_userlist.SetItem(nItem,3,1,(_bstr_t)vBirthday,NULL,0,0,0);

m_pRecordset->MoveNext();
}
}

6.添加数据 PutCollect()
if(!m_pRecordset->adoEOF && m_nCurrentSel >= 0 && m_bAutoSave)
{
vUserID = (long)m_nUserID;
vUsername = m_sUsername;
vOld = (long)m_nOld;
vBirthday = m_tBirthday;
m_pRecordset->PutCollect("ID",vUserID);
m_pRecordset->PutCollect("username",vUsername);
m_pRecordset->PutCollect("old",vOld);
m_pRecordset->PutCollect("birthday",vBirthday)
        }

//--------------------------------------------------------------------------------------
在ado中获取access中表的信息

void OpenSchemaX(TCHAR *TableName)
{
    HRESULT  hr = S_OK;

IADORecordBinding   *picRs = NULL;

_RecordsetPtr pRstSchema("ADODB.Recordset");
_ConnectionPtr pConnection("ADODB.Connection" );


pConnection->ConnectionString = TableName;
pConnection->Provider = "Microsoft.Jet.OLEDB.4.0";

   try
    {
pConnection->Open(pConnection->ConnectionString, "", "", adModeUnknown);
  pRstSchema->QueryInterface(
          __uuidof(IADORecordBinding), (LPVOID*)&picRs);

        pRstSchema = pConnection->OpenSchema(adSchemaTables);//枚举表的名称处理

        while(!(pRstSchema->EndOfFile))
        {
CString strTableType;

            _bstr_t table_name = pRstSchema->Fields->
                GetItem("TABLE_NAME")->Value;//获取表的名称

            _bstr_t table_type = pRstSchema->Fields->
                GetItem("TABLE_TYPE")->Value;//获取表的类型


            strTableType.Format("%s",(LPCSTR) table_type);

if(!lstrcmp(strTableType,_T("TABLE")))
            {
m_cbTeam.AddString((LPCSTR) table_name);//添加表的名称
}

            pRstSchema->MoveNext();
        }
        // Clean up objects before exit.

        pRstSchema->Close();
        pConnection->Close();
    }

    catch (_com_error &e)
    {
        // Notify the user of errors if any.
        // Pass a connection pointer accessed from the Connection.       
        PrintProviderError(pConnection);
        PrintComError(e);
    }
}


--------------------------------------------------------------------------------
CSDN VC编程经验总结

 


ADO 2.8 Samples 

See Also
Clear Method | Command Object | Connection Object | Errors Collection | Execute Method (ADO Command) | Execute Method (ADO Connection) | Requery Method
Execute, Requery, and Clear Methods Example (VC++)
This example demonstrates the Execute method when run from both a Command object and a Connection object. It also uses the Requery method to retrieve current data in a recordset, and the Clear method to clear the contents of the Errors collection. The ExecuteCommand and PrintOutput functions are required for this example to run.

// BeginExecuteCpp
#include <ole2.h>
#include <stdio.h>

#import "c:\Program Files\Common Files\System\ADO\msado15.dll" \
    no_namespace rename("EOF", "EndOfFile")

// Function declarations
inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
void ExecuteX(void);
void ExecuteCommand(_CommandPtr pCmdTemp, _RecordsetPtr pRstTemp);
void PrintOutput(_RecordsetPtr pRstTemp);
void PrintProviderError(_ConnectionPtr pConnection);
void PrintComError(_com_error &e);

////////////////////////////////
//      Main Function         //
////////////////////////////////

void main()
{
    if(FAILED(::CoInitialize(NULL)))
        return;

    ExecuteX();

    ::CoUninitialize();
}

///////////////////////////////////
//      ExecuteX Function        //
///////////////////////////////////

void ExecuteX(void)
{
   HRESULT    hr = S_OK;

    // Define string variables.
   _bstr_t strSQLChange("UPDATE Titles SET Type = "
            "'self_help' WHERE Type = 'psychology'");
   _bstr_t strSQLRestore("UPDATE Titles SET Type = "
            "'psychology' WHERE Type = 'self_help'");
   _bstr_t strCnn("Provider='sqloledb';Data Source='MySqlServer';"
            "Initial Catalog='pubs';Integrated Security='SSPI';");

    // Define ADO object pointers.
    // Initialize pointers on define.
    // These are in the ADODB::  namespace.
    _ConnectionPtr  pConnection = NULL;
    _CommandPtr     pCmdChange  = NULL;
    _RecordsetPtr   pRstTitles  = NULL;

    try
    {
        // Open connection.
        TESTHR(pConnection.CreateInstance(__uuidof(Connection)));
        pConnection->Open (strCnn, "", "", adConnectUnspecified);

        // Create command object.
        TESTHR(pCmdChange.CreateInstance(__uuidof(Command)));
        pCmdChange->ActiveConnection = pConnection;
        pCmdChange->CommandText = strSQLChange;

        // Open titles table, casting Connection pointer to an
        // IDispatch type so converted to correct type of variant.
        TESTHR(pRstTitles.CreateInstance(__uuidof(Recordset)));
        pRstTitles->Open ("Titles", _variant_t((IDispatch *) pConnection,
            true), adOpenStatic, adLockOptimistic, adCmdTable);

        // Print report of original data.
        printf(
            "\n\nData in Titles table before executing the query: \n");

        // Call function to print loop recordset contents.
        PrintOutput(pRstTitles);

        // Clear extraneous errors from the Errors collection.
        pConnection->Errors->Clear();

        // Call ExecuteCommand subroutine to execute pCmdChange command.
        ExecuteCommand(pCmdChange, pRstTitles);

        // Print report of new data.
        printf(
            "\n\n\tData in Titles table after executing the query: \n");
        PrintOutput(pRstTitles);

        // Use the Connection object's execute method to
        // execute SQL statement to restore data.
        pConnection->Execute(strSQLRestore, NULL, adExecuteNoRecords);

        // Retrieve the current data by requerying the recordset.
        pRstTitles->Requery(adCmdUnknown);

        // Print report of restored data.
        printf(
            "\n\n\tData after exec. query to restore original info: \n");
        PrintOutput(pRstTitles);
    }
    catch (_com_error &e)
    {
        PrintProviderError(pConnection);
        PrintComError(e);
    }

    // Clean up objects before exit.
    if (pRstTitles)
        if (pRstTitles->State == adStateOpen)
            pRstTitles->Close();
    if (pConnection)
        if (pConnection->State == adStateOpen)
            pConnection->Close();
}

//////////////////////////////////////////
//      ExecuteCommand Function         //
//////////////////////////////////////////

void ExecuteCommand(_CommandPtr pCmdTemp, _RecordsetPtr pRstTemp)
{
    try
    {
        // CommandText property already set before function was called.
        pCmdTemp->Execute(NULL, NULL, adCmdText);

        // Retrieve the current data by requerying the recordset.
        pRstTemp->Requery(adCmdUnknown);
    }

    catch(_com_error &e)
    {
        // Notify user of any errors that result from
        // executing the query.
        // Pass a connection pointer accessed from the Recordset.
        PrintProviderError(pRstTemp->GetActiveConnection());
        PrintComError(e);
    }
}

/////////////////////////////////////
//      PrintOutput Function       //
/////////////////////////////////////

void PrintOutput(_RecordsetPtr pRstTemp)
{
    // Ensure at top of recordset.
    pRstTemp->MoveFirst();

    // If EOF is true, then no data and skip print loop.
    if( pRstTemp->EndOfFile )
    {
        printf("\tRecordset empty\n");
    }
    else
    {
        // Define temporary strings for output conversions.
        // Initialize to first record's values.
        _bstr_t bstrTitle;
        _bstr_t bstrType;

        // Enumerate Recordset and print from each.
        while(!(pRstTemp->EndOfFile))
            {
            // Convert variant string to convertable string type.
            bstrTitle = pRstTemp->Fields->GetItem("Title")->Value;
            bstrType  = pRstTemp->Fields->GetItem("Type")->Value;
            printf("\t%s, %s \n",
                (LPCSTR) bstrTitle,
                (LPCSTR) bstrType);
   
            pRstTemp->MoveNext();
        }
    }
}

///////////////////////////////////////////////
//      PrintProviderError Function          //
///////////////////////////////////////////////

void PrintProviderError(_ConnectionPtr pConnection)
{
    // Print Provider Errors from Connection object.
    // pErr is a record object in the Connection's Error collection.
    ErrorPtr  pErr = NULL;

    if( (pConnection->Errors->Count) > 0)
    {
        long nCount = pConnection->Errors->Count;
        // Collection ranges from 0 to nCount -1.
        for(long i = 0; i < nCount; i++)
        {
            pErr = pConnection->Errors->GetItem(i);
            printf("\t Error number: %x\t%s", pErr->Number,
                pErr->Description);
        }
    }
}

//////////////////////////////////////
//      PrintComError Function      //
//////////////////////////////////////

void PrintComError(_com_error &e)
{
    _bstr_t bstrSource(e.Source());
    _bstr_t bstrDescription(e.Description());
   
    // Print Com errors.
    printf("Error\n");
    printf("\tCode = %08lx\n", e.Error());
    printf("\tCode meaning = %s\n", e.ErrorMessage());
    printf("\tSource = %s\n", (LPCSTR) bstrSource);
    printf("\tDescription = %s\n", (LPCSTR) bstrDescription);
}
// EndExecuteCpp

【 原文由 wesley 所发表 】
本文摘自http://www.codeguru.com/mfc_database/Ado_Aok.shtml。作者:Bob Place
 
翻译改写:wesley at 水木清华。 Email: wesley@video.mdc.tsinghua.edu.cn
 
前言:
    有人经常问:现在最好的数据访问方法是什么?回答当然是ADO!
    M$花了不少时间,推出了一种叫 UDA(Universal Data Access)的东东,还有一套看
起来蛮简单的数据访问对象ADO(ActiveX Data Object),用来替代过时的 DAO(Data Ac
cess Object)、RDO(Remote Data Object)。
    DAO 的底层是 JET 引擎,主要用来提供对 ACCESS 数据库的访问,比较新的版本也
支持访问其他数据库,不过对于其他数据库,需经过 JET 的中间层,访问速度比较差。
在所有对 ACCESS 数据库的访问方法中, JET是最快的。最新的 JET Engine版本为4.0
,对应的 DAO 版本为3.6,可以访问 ACCESS 2000 的数据库。MFC里面 CDAODatabase
和 CDAORecordset 即为 DAO 的 MFC 包装。
    RDO 的底层是 ODBC,RDO仅仅是对ODBC API的一个薄包装层,薄得简直有点不象样
,很多人都用 ODBC API写过程序 (我的第一个SQL Server客户端程序就是用ODBC API写
的一个Console Application),把那些并不复杂的API跟RDO一比就能发现,包装得简直
没有专业精神。也正因为薄,所以速度较快,在ADO出现以前,访问MS SQL Server最快
的方法就是 RDO 了(不要跟我说还有DB-Library,不论是看MS的文档,还是我亲自实验
,DB-Library都没有ODBC/RDO快)。最新的 RDO 版本为 2.0,说是最新,似乎也有好几
年没有更新了,原因是MS早已经决定将其淘汰。不过遗憾的是,现在大家都还在用的 C
Database、CRecordset 就是 RDO 的 MFC 包装。现在还用这两个类,就象有了宝马奔驰
,还骑破永久上下班,只是因为不会开车。 :-(
    ADO 的底层是 OLE DB,不仅能访问关系型数据库,也可以访问非关系型数据库,这
可是现在最快速的数据库访问中间层啊!ADO对OLE DB的包装可以说相当成功,对象模型
简明扼要,没有一点多余的东西,功能还远超DAO、RDO。直到此时,我才算是有点佩服
MS,OLE DB里面数十个密密麻麻的接口对我来说实在是太恐怖了,还是乖乖的用ADO吧。
 
    有一点很不幸,微软提供的ADO文档几乎没有有关VC的内容,象我这样的VC菜鸟,坐
进了宝马舒适的驾驶仓,不知道该怎么上手下脚,郁闷之极!
    这篇文章真是救黎民于水火之中,不容我不把它贡献出来让大家共享。
 
开始:
    在用ADO以前,一定得让你的程序知道去哪里找ADO。在stdafx.h文件里,需要加上
下面的代码:
 #import "c:\program files\common files\system\ado\msado15.dll" no_namespace
 rename("EOF", "adoEOF")
    这行代码的作用是,告诉编译器去哪里找ADO的库文件(可能在你的机器上路径有所
不同),然后说明不用namespace,并且将 EOF 更名为 adoEOF(如果不这样干,很有可能
会碰到常量冲突)。
    只要加了这句话,准备工作就全干完了,很简单是吗?不用包含任何头文件,不用
为link指定任何lib文件,我也觉得有点神奇。//shrug
 
_ConnectionPtr, _CommandPtr, 和 _RecordsetPtr (本文中未提及 _CommandPtr):
    ADO,和 CDAODatabase、CDatabase 非常相似,也分这么几块,不同的是,ADO 以
 COM 为基础,这几块都是标准的COM组件,而 CDatabase 等等则是 MFC 类。有一点必
须提请注意,要学习ADO编程,学点COM是不可避免的了,不过这是件好事,现在如果不
会一点COM、OLE什么的,估计很难适应Windows编程的形势。ADO里面的三个组成部份就
是三个COM组件:Connection、Command、Recordset。(还有两个暂时用不上的:)
    Connection用于建立数据库连接,执行不返回任何结果集的SQL语句。
    Command用于返回结果集,并提供简单的方法执行存储过程或者任何返回结果集的S
QL语句。
    Recordset就是结果集,可进行数据的存取、滚动操作。
    如果给Command和Recordset正确的Connection string,而不是一个Connection对象
的指针,它们一样可以打开记录集,这种情况适用于单数据库操作。当程序里需要频繁
进行数据库操作时,最好还是预先定义一个Connection对象,用它连接数据库,而用Re
cordset处理数据。本文中将大量讨论这两个对象。
    _ConnectionPtr是一个Connection的接口,与CDatabase和CDAODatabase类似,实际
工作原理也差不多。在程序里创建它的实例,通过某个OLE DB provider指向一个数据源
,并开启连接。下面的代码是CDAODatabase和_ConnectionPtr的开启实例:
 
DAO:
    CDaoDatabase MyDb = new CDaoDatabase();
    m_DaoServerDB.Open(NULL,FALSE,FALSE,"ODBC;DSN=SAMS_SVR;UID=admin;PWD=adm
in");
 
ADO:
    _ConnectionPtr MyDb;
    MyDb.CreateInstance(__uuidof(Connection));
    MyDb->Open("DSN=SAMS_SVR;UID=admin;PWD=admin","","",-1);
 
    _RecordsetPtr是记录集接口,与CDAORecordset类似。先看看它的开启方式与CDAO
Recordset有多么相似:
 
DAO:
    CDaoRecordset MySet = new CDaoRecordset(MyDb);
    MySet->Open(AFX_DAO_USE_DEFAULT_TYPE,"SELECT * FROM some_table");
 
ADO:
    _RecordsetPtr MySet;
    MySet.CreateInstance(__uuidof(Recordset));
    MySet->Open("SELECT * FROM some_table", MyDb.GetInterfacePtr(), adOpenDy
namic, adLockOptimistic, adCmdText);
 
    (译者注:请注意ADO在Open Recordset的时候,使用了MyDb.GetInterfacePtr()作
为参数之一,当时我用了_ConnectionPtr, &(_ConnectionPtr)都不行,原来要这么用,
真是不服不行。:()
    ADO只是略微的麻烦一点,不过花这点功夫获得ADO的多多好处还是很值的。
    现在有了一个Connection和一个Recordset,下面该用这两个东东取点数据出来了。
不妨先假定有一个名为m_List的Listbox,我们把数据取出来往里塞。
 
DAO:
    VARIANT *vFieldValue;
    COleVariant covFieldValue;
    CString Holder;
    while(!MySet->IsEOF())
    {
        MySet->GetFieldValue("FIELD_1", covFieldValue);
        vFieldValue = (LPVARIANT)covFieldValue;
        if(vFieldValue->vt!-VT_NULL)
        {
            Holder.Format("%s",vFieldValue->pbVal);
            m_List.AddString(Holder);
        }
        MySet.MoveNext();
    }
 
ADO:
    _variant_t Holder
    while(!MySet->adoEOF)
    {
        Holder = MySet->GetCollect("FIELD_1");
        if(Holder.vt!=VT_NULL)
        m_List.AddString((char*)_bstr_t(Holder));
        MySet->MoveNext();
    }
 
    注意:在微软所有的文档里,都没找到GetCollect方法。我找了所有的地方,也从
没人提起过,在文档里的示例方式是这样:
        Holder = MySet->GetFields->Field->(_variant_t(FieldNumber))->Value;
    你喜欢哪一种呢,反正我是喜欢GetCollect。(译者注:鬼知道他从哪里搞到这个方
法,难道是从MS自己人的程序里?I 服了 him。)
 
动态绑定 vs DFX(CRecordset 和 CDAORecordset的预先字段绑定,Data Field Exchan
ge):
    动态绑定即使用SQL语句动态构造结果字段,而不是象 CDAORecordset 里面用DFX去
把所有原始字段映射成成员变量。动态绑定的例子:
    SELECT (SUM(field_1) + SUM(field_2)) AS answer FROM some_table
    如果用DFX,估计就得在程序里自己写了:
    m_answer = m_field_1 + m_field2;
    (译者注:尽管很多人喜欢 DFX 的字段预先绑定,宁可这么写代码,不过我很少用
 DFX,当然在 VB 里面更是从来不会用到,好象用得比较多的也就是在 Delphi 里面,
用起来还比较爽,大概是 MS 的文档里让我别用,而 Borland 的破文档里却没说吧。
:-)
    相比之下,动态绑定确有其优越的地方,减少了代码量,也让程序更小、更易维护
。再说,这也是MS推荐的获取数据方式,更灵活,速度更快,更易维护,我们还能要求
什么呢?
    对于大多数程序来说,都是创建一个全局的Connection,然后用Recordset来处理数
据,如果Recordset数量很多,可以想象光是花费在DFX上面的代码就有多少。如果使用
动态绑定,这些都省掉了。
 
_variant_t 和 _bstr_t 到底是什么玩意?
    很不幸,我们喜爱的CString类在COM里用不了(CStringEx也一样),因为COM必须设
计成跨平台,它需要一种更普遍的方式来处理字符串以及其他数据。这就是VARIANT数据
类型的来历,还有BSTR类型。VARIANT就是一个巨大的 union,包含了你能想得到的所有
的数据类型,除了char*,不过还好,BSTR取代了char*。
    (译者注:似乎VARIANT是个很慢的东西,大家都不愿意使它,不过按我看来,情况
没这么糟糕,union照理说不应该慢到哪去,要说慢,也是慢在给VARIANT分配地址空间
上,这点在VC里面做得比VB要好
    这些东西看起来的确有点恐怖,不过实在用不着怕,等下面熟悉了这两个东西之后
,你会很快喜欢的)
    简单来说,_variant_t是一个类,包装了VARIANT数据类型,并允许我们简单的对之
进行强制类型转换(相信大家都喜欢这个),_bstr_t对BSTR干了同样的事情。在下面的例
子里,你将看到怎么用GetCollect把数据取到VARIANT里,又怎么把它放到_bstr_t里,
最后强制转换成char*,以及把_variant_t强制转换成long、double或者其他一切东西:
 
    _variant_t Holder;
    // first get the VARIANT and put it into the _variant_t
    Holder = MySet->GetCollect("FIELD_1");
    // now put it into a _bstr_t and cast it to a char*
    m_List.AddString((char*)_bstr_t(Holder));
 
    对比一下没有用 _variant_t 和 _bstr_t 的代码:
 
    COleVariant covFieldValuel
    VARIANT vFieldValue
    CString Holder;
    MySet->GetFieldValue("FIELD_1", covFieldValue);
    vFieldValue = (LPVARIANT)covFieldValue;
    Holder.Format("%s",vFieldValue->pbVal);
    m_List.AddString(Holder);
 
    区别大了! 

世界上既无所谓幸福也无所谓不幸
只有一种状况和另一种状况的比较
只有体验过极度不幸的人
才能品尝到极度的幸福 
 
Update,Insert,Delete:
    当我进行Update,Insert,Delete操作时,通常我喜欢用Connection和Command对象
,原因是,用CString来构造SQL语句简单一些,然后直接用Connection.Execute就行了
。当然,用Recordset干这些也是可以的。
 
    Update方法有下面三种方法调用:
    1: 给某个Field对象(或某些个)的Value属性赋值,然后调用Update方法;
    2: 将字段名和字段值作为参数传给Update方法;
    3: 将字段名和字段值的数组作为参数传给Update方法。
 
    AddNew方法如下调用:
    1: 直接调用,然后同Update调用方法一;
    2: 将字段名数组、字段值数组作为参数传给AddNew方法。
 
    Delete方法最简单,直接调用就行了,删除当前记录!
 
    做完这些事情,可能需要调用Requery方法才能看到效果。
    下面的示例代码需要我们创建一个简单的MFC Application,然后在CWinApp类里面
声明三个接口的对象:
 
    // Global ADO Objects
    // connection
    _ConnectionPtr m_pConnection;
    _CommandPtr  m_pCommand;
    _RecordsetPtr m_pRecordset;
 
    在VC6里面有一点很有意思,如果敲 "m_pConnection.",你会看到一个方法和属性
列表,如果敲"m_pConnection->",还会看到一个方法和属性列表,当然里面的内容完全
不同,因为你实际是在指向两个不同的东西。下面就是这两种混用的代码:
 
    _ConnectionPtr MyDb;
    MyDb.CreateInstance(__uuidof(Connection));
    MyDb->Open("DSN=SAMS_SVR;UID=admin;PWD=admin","","",-1);
 
    回到示例代码,在 application 的 InitInstance 方法里,我打开数据连接,指向我
机器上的一个数据库,你需要更改ConnectionString,使用你自己的ODBC数据源或指定
一个OLE DB provider。
 
    // When we open the application we will open the ADO connection
    m_pConnection.CreateInstance(__uuidof(Connection));
    m_pConnection->Open("DSN=ADOTest","","",-1);
 
    如果你打开about对话框,就会看到一个Listbox,还有一个叫button1的按钮,这里
面包含了 ADO 调用的核心代码。我创建了一个_RecordsetPtr接口的实例,打开我需要
的记录集,然后遍历所有记录,将它们塞到Listbox里去:
 
    _variant_t TheValue;
    theApp.m_pRecordset.CreateInstance(__uuidof(Recordset));
    try
    {
        theApp.m_pRecordset->Open("SELECT DISTINCT FLDESC FROM tblFALines",
                theApp.m_pConnection.GetInterfacePtr(),
                adOpenDynamic,
                adLockOptimistic,
                adCmdText);
        while(!theApp.m_pRecordset->adoEOF)
        {
                TheValue = theApp.m_pRecordset->GetCollect("FLDESC");
                if(TheValue.vt!=VT_NULL)
                m_List.AddString((char*)_bstr_t(TheValue));
                theApp.m_pRecordset->MoveNext();
        }
        theApp.m_pRecordset->Close();
    }
    catch(_com_error *e)
    {
        CString Error = e->ErrorMessage();
        AfxMessageBox(e->ErrorMessage());
    }
    catch(...)
    {
        MessageBox("Whoa this is bad");
    }
 
    记得一定要用try和catch,否则ADO调用错误有可能使你的程序崩溃,一定要随时记
得捕捉_com_error例外以及其它错误。
    我尽可能的使代码简单,所以省略了很多细节,尤其是忽略了很多好的编程习惯(比
如检查大多数COM方法都返回的HRESULT值)。本文的目的是想说,ADO并没什么难的,CO
M也一样,而不是想表现ADO能做的所有事情。我甚至都没仔细想过ADO能为你带来什么,
不过我肯定一点,那就是ADO比DAO、RDO更快、更容易使用、并且功能强大得多。看看本
站点其它的文章,你就会知道,通过ADO调用存储过程有多么容易!
    最后,我想向大家推荐两本书,其中有一本是完全免费的,在www.informit.com
以找到电子版,另一本必须得付钱买了,不过我还是推荐两本都买,除非你家浴池里、
床头边也放了计算机。 :)
    免费的那本是<Learn Database Programming with Visual C++ in 21 days>。哦,
我知道你在想什么,我也知道那些'in 21 days'的书通常都很烂,而且当着其它程序员
的面买这样的书的确有点丢面子,并且还很不好意思把这样的书摆在书架上显眼的位置
。不过这一本绝对是个例外!里面的内容简直太棒了!
    要花钱的那本是<ADO 2.0>,由WROC出版... (译者注:细节就省了吧,反正我们也
不会花美元去买英文书)。
 
    译者注:一年以前,我在www.mcp.com(现在已经更名为informit)看到这本书,看过
之后觉得实在是本难得一见的好书,不仅讲ADO深入浅出,COM的来历、基础、发展都说
得很清楚,还有不少C++的高级知识,这也是我看过的唯一一本“21天”的书,与其它的
“21天”简直是天壤之别。强烈建议大家买一本。
    中译本:Visual C++ 数据库编程...(一时记不起了),清华大学出版社出版,清华
书店有卖,大家快去买啊!注意不要买机械工业出版社那本 VC 数据库编程的书,看起
来名字很象,不过内容烂得可以。
    如果要看相关的文章,请访问站点www.codeguru.com,祝大家早日成为编程高手,
已经是高手的更加高!

在stdafx.h中添加:
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")

正式代码:
_RecordsetPtr m_pRs;
_ConnectionPtr m_pConn;
try
{
 CoInitialize(NULL);

 m_pConn.CreateInstance(__uuidof(Connection));
 m_pRs.CreateInstance(__uuidof(Recordset));
 
 //设置服务器端游标
 m_pConn->CursorLocation = adUseServer;//adUseClient;adUseNone
  
 //连接ORACLE数据库
 m_pConn->Open(L"Provider=OraOLEDB.Oracle.1;User ID=pkuwh_za;password=a;Data Source=213;Persist Security Info=False",
      L"",L"",adOpenUnspecified);
 //上面的语句按下面的方式写可以不用配置oracle数据源
        //m_pConn->Open(L"Provider=OraOLEDB.Oracle.1;User ID=pkuwh_za;Password=a;Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.3.213)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME = ZAGL)));Persist Security Info=False",L"",L"",adOpenUnspecified);
 
 m_pRs->PutRefActiveConnection(m_pConn);
 CString t = "select * from case_acceptinfo where rownum <= 3";
 m_pRs->Open(_variant_t(t),_variant_t((IDispatch*)m_pConn,true),adOpenDynamic,adLockOptimistic,adCmdUnknown);
 
 while(!m_pRs->adoEOF)
 {
  CString m_s1 = _com_util::ConvertBSTRToString((_bstr_t)m_pRs->GetCollect("reporttime"));
  MessageBox(m_s1);

  m_pRs->MoveNext();
 }

 m_pRs->Close();
 m_pConn->Close();

 m_pRs=NULL;
 m_pConn=NULL;
 CoUninitialize();
}
catch(_com_error &e)
{
 AfxMessageBox(e.Description());
}

 

 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=218477

posted @ 2011-12-20 16:44  树——城  阅读(4500)  评论(0编辑  收藏  举报