分享
 
 
 

Visual C++中使用OLE DB读写SQL Server简明指南

王朝vc·作者佚名  2006-01-10
窄屏简体版  字體: |||超大  

©2005 黄友生。本文由原作者发布于MSN Space、CSDN。你可以保存、在非商业软件中使用、在非盈利性文章中引用本文中的部分或全部文字,但请注明作者及原文地址。要用于其它用途,请先联系作者(eien@eyou.com)。作者不保证本文完全正确无误、不对因本文中的理论或代码缺陷造成的损失负责。

在需要对数据库进行操作时,OLE DB总是被认为是一种效率最高但最难的方法。但是以我最近使用OLE DB的经验看来,OLE DB的效率高则高矣,但却一点都不难。说它难恐怕主要是因为可参考的中文资料太少,为了帮助以后需要接触OLE DB的同行,我撰写了这篇文章。本文包含如下内容:

1. OLE DB写数据库;

2. OLE DB读数据库;

3. OLE DB对二进制数据(text、ntext、image等)的处理。

首先来看看对SQL Server进行写操作的代码,有一定VC基础的读者应该可以很顺利地看懂。OLE DB写数据库,就是这么简单!

注:

1.以下代码中使用的模板类EAutoReleasePtr<T>与ATL中的CComPtr<T>类似,是一个在析构时自动调用Release的类。CComPtr<T>的代码在ATLBASE.H中定义。

2.以下代码均在UNICODE环境下编译,因为执行的SQL语句必须是UNICODE的。设置工程为UNICODE的方法是:首先在project->settings->C/C++的属性页中的Preprocessor中,删除_MBCS写入UNICODE,_UNICODE。然后在link属性页中Category中选择output,在Entry-Point symbol 中添加wWinMainCRTStartup。

EAutoReleasePtr<IDBInitialize> pIDBInitialize;

HRESULT hResult = ConnectDatabase( &pIDBInitialize, _T("127.0.0.1"), _T(“sa”), _T("password") );

if( FAILED( hResult ) )

{

//失败,可能是因为数据库没有启动、用户名密码错等等

return;

}

EAutoReleasePtr<IOpenRowset> pIOpenRowset;

hResult = CreateSession( pIDBInitialize, &pIOpenRowset );

if( FAILED( hResult ) )

{

//出错

return;

}

EAutoReleasePtr<ICommand> pICommand;

EAutoReleasePtr<ICommandText> pICommandText;

hResult = CreateCommand( pIOpenRowset, &pICommand, &pICommandText );

if( FAILED( hResult ) )

{

//出错

return;

}

hResult = ExecuteSQL( pICommand, pICommandText, _T("USE PBDATA") );

if( FAILED( hResult ) )

{ //如果这里失败,那就是SQL语句执行失败。在此处,就是PBDATA还未创建

return;

}

// 创建表

ExecuteSQL( pICommand, pICommandText, _T("CREATE TABLE 2005_1(Volume real NOT NULL,ID int NOT NULL IDENTITY)") );

// 添加记录

ExecuteSQL( pICommand, pICommandText, _T("INSERT INTO 2005_1 VALUES(100.0)") );

//...

其中几个函数的代码如下:

HRESULT ConnectDatabase( IDBInitialize** ppIDBInitialize, LPCTSTR pszDataSource, LPCTSTR pszUserID, LPCTSTR pszPassword )

{

ASSERT( ppIDBInitialize != NULL && pszDataSource != NULL && pszUserID != NULL && pszPassword != NULL );

UINT uTimeout = 15U; // 连接数据库超时(秒)

TCHAR szInitStr[1024];

VERIFY( 1023 >= wsprintf( szInitStr, _T("Provider=SQLOLEDB;Data Source=%s;Initial Catalog=master;User Id=%s;Password=%s;Connect Timeout=%u"), pszDataSource, pszUserID, pszPassword, uTimeout ) );

//Initial Catalog=master指明连接成功后,"USE master"。

EAutoReleasePtr<IDataInitialize> pIDataInitialize;

HRESULT hResult = ::CoCreateInstance( CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER,

IID_IDataInitialize, ( void** )&pIDataInitialize );

if( FAILED( hResult ) )

{

return hResult;

}

EAutoReleasePtr<IDBInitialize> pIDBInitialize;

hResult = pIDataInitialize->GetDataSource( NULL, CLSCTX_INPROC_SERVER, ( LPCOLESTR )szInitStr,

IID_IDBInitialize, ( IUnknown** )&pIDBInitialize );

if( FAILED( hResult ) )

{

return hResult;

}

hResult = pIDBInitialize->Initialize( );

if( FAILED( hResult ) )

{

return hResult;

}

* ppIDBInitialize = pIDBInitialize.Detach( );

return S_OK;

}

HRESULT CreateSession( IDBInitialize* pIDBInitialize, IOpenRowset** ppIOpenRowset )

{

ASSERT( pIDBInitialize != NULL && ppIOpenRowset != NULL );

EAutoReleasePtr<IDBCreateSession> pSession;

HRESULT hResult = pIDBInitialize->QueryInterface( IID_IDBCreateSession, ( void** )&pSession );

if( FAILED( hResult ) )

{

return hResult;

}

EAutoReleasePtr<IOpenRowset> pIOpenRowset;

hResult = pSession->CreateSession( NULL, IID_IOpenRowset, ( IUnknown** )&pIOpenRowset );

if( FAILED( hResult ) )

{

return hResult;

}

* ppIOpenRowset = pIOpenRowset.Detach( );

return S_OK;

}

HRESULT CreateCommand( IOpenRowset* pIOpenRowset, ICommand** ppICommand, ICommandText** ppICommandText )

{

ASSERT( pIOpenRowset != NULL && ppICommand != NULL && ppICommandText != NULL );

HRESULT hResult;

EAutoReleasePtr<ICommand> pICommand;

{

EAutoReleasePtr<IDBCreateCommand> pICreateCommand;

hResult = pIOpenRowset->QueryInterface( IID_IDBCreateCommand, ( void** )&pICreateCommand );

if( FAILED( hResult ) )

{

return hResult;

}

hResult = pICreateCommand->CreateCommand( NULL, IID_ICommand, (IUnknown**)&pICommand );

if( FAILED( hResult ) )

{

return hResult;

}

}

EAutoReleasePtr<ICommandText> pICommandText;

hResult = pICommand->QueryInterface( &pICommandText );

if( FAILED( hResult ) )

{

return hResult;

}

* ppICommand = pICommand.Detach( );

* ppICommandText = pICommandText.Detach( );

return S_OK;

}

HRESULT ExecuteSQL( ICommand* pICommand, ICommandText* pICommandText, LPCTSTR pszCommand, LONG* plRowsAffected )

{

ASSERT( pICommand != NULL && pICommandText != NULL && pszCommand != NULL && pszCommand[0] != 0 );

HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )pszCommand );

if( FAILED( hResult ) )

{

return hResult;

}

LONG lAffected;

hResult = pICommand->Execute( NULL, IID_NULL, NULL, plRowsAffected == NULL ? &lAffected : plRowsAffected, ( IUnknown** )NULL );

return hResult;

}

以上就是写数据库的全部代码了,是不是很简单呢?下面再来读的。

// 先用与上面代码中一样的步骤获取pICommand,pICommandText。此处省略

HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )_T("SELECT Volume FROM 2005_1 WHERE ID = @@IDENTITY") ); //取我们刚刚添加的那一条记录

if( FAILED( hResult ) )

{

return;

}

LONG lAffected;

EAutoReleasePtr<IRowset> pIRowset;

hResult = pICommand->Execute( NULL, IID_IRowset, NULL, &lAffected, ( IUnknown** )&pIRowset );

if( FAILED( hResult ) )

{

return;

}

EAutoReleasePtr<IAccessor> pIAccessor;

hResult = pIRowset->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );

if( FAILED( hResult ) )

{

return;

}

// 一个根据表中各字段的数值类型而定义的结构,用于存储返回的各字段的值 struct CLoadLastFromDB

{

DBSTATUS dwdsVolume;

DWORD dwLenVolume;

float fVolume;

};

// 此处我们只查询了一个字段。如果要查询多个字段,CLoadLastFromDB中要添加相应的字段定义,下面的dbBinding也要相应扩充。dbBinding[].iOrdinal要分别指向各个字段,dbBinding[].wType要根据字段类型赋合适的值。

DBBINDING dbBinding[1];

dbBinding[0].iOrdinal = 1; // Volume 字段的位置,从 1 开始

dbBinding[0].obValue = offsetof( CLoadLastFromDB, fVolume );

dbBinding[0].obLength = offsetof( CLoadLastFromDB, dwLenVolume );

dbBinding[0].obStatus = offsetof( CLoadLastFromDB, dwdsVolume );

dbBinding[0].pTypeInfo = NULL;

dbBinding[0].pObject = NULL;

dbBinding[0].pBindExt = NULL;

dbBinding[0].dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;

dbBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;

dbBinding[0].eParamIO = DBPARAMIO_NOTPARAM;

dbBinding[0].cbMaxLen = 0;

dbBinding[0].dwFlags = 0;

dbBinding[0].wType = DBTYPE_R4; // float就是DBTYPE_R4,int就是DBTYPE_I4。参见MSDN

dbBinding[0].bPrecision = 0;

dbBinding[0].bScale = 0;

HACCESSOR hAccessor = DB_NULL_HACCESSOR;

DBBINDSTATUS dbs[1];

hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, dbBinding, sizeof( CLoadLastDataFromDB ), &hAccessor, dbs );

if( FAILED( hResult ) )

{

return;

}

ASSERT( dbs[0] == DBBINDSTATUS_OK );

ULONG uRowsObtained = 0;

HROW hRows[1]; // 这里我们只查询了最新的那一条记录

HROW* phRows = hRows;

CLoadLastFromDB rmd;

hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );

if( SUCCEEDED( hResult ) && uRowsObtained != 0U )

{

hResult = pIRowset->GetData( phRows[0], hAccessor, &rmd );

if( FAILED( hResult ) )

{

ASSERT( FALSE );

}

ASSERT( rmd.dwdsVolume == DBSTATUS_S_OK );

// rmd.fVolume 就是我们要取的值

}

pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );

pIAccessor->ReleaseAccessor( hAccessor, NULL );

pIAccessor.Release( );

pIRowset.Release( );

读操作也完成了,是不是仍然很简单呢?下面我们再来看看最麻烦的二进制数据(text、ntext、image等)的读写。要实现BLOB数据的读写,我们需要一个辅助的类,定义如下:

class CSequentialStream : public ISequentialStream // BLOB 数据访问类

{

public:

CSequentialStream( );

virtual ~CSequentialStream( );

virtual BOOL Seek( ULONG uPosition );

virtual BOOL Clear( );

virtual ULONG GetLength( ) { return m_uBufferUsed; };

virtual operator void* const( ) { return m_pBuffer; };

STDMETHODIMP_( ULONG ) AddRef( ) { return ++ m_uRefCount; };

STDMETHODIMP_( ULONG ) Release( ) { ASSERT( m_uRefCount != 0U ); -- m_uRefCount; if( m_uRefCount == 0U ) { delete this; } return m_uRefCount; };

STDMETHODIMP QueryInterface( REFIID riid, LPVOID* ppv );

STDMETHODIMP Read( void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbRead );

STDMETHODIMP Write( const void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbWritten );

void ResetPosition( ) { m_uPosition = 0U; };

HRESULT PreAllocBuffer( ULONG uSize );

private:

ULONG m_uRefCount; // reference count

void* m_pBuffer; // buffer

ULONG m_uBufferUsed; // buffer used

ULONG m_uBufferSize; // buffer size

ULONG m_uPosition; // current index position in the buffer

};

实现如下:

CSequentialStream::CSequentialStream( ) : m_uRefCount( 0U ), m_pBuffer( NULL ), m_uBufferUsed( 0U ), m_uBufferSize( 0U ), m_uPosition( 0U )

{

AddRef( );

}

CSequentialStream::~CSequentialStream( )

{

Clear( );

}

HRESULT CSequentialStream::QueryInterface( REFIID riid, void** ppv )

{

if( riid == IID_IUnknown || riid == IID_ISequentialStream )

{

* ppv = this;

( ( IUnknown* )*ppv )->AddRef( );

return S_OK;

}

* ppv = NULL;

return E_NOINTERFACE;

}

BOOL CSequentialStream::Seek( ULONG uPosition )

{

ASSERT( uPosition < m_uBufferUsed );

m_uPosition = uPosition;

return TRUE;

}

BOOL CSequentialStream::Clear( )

{

m_uBufferUsed = 0U;

m_uBufferSize = 0U;

m_uPosition = 0U;

( m_pBuffer != NULL ? CoTaskMemFree( m_pBuffer ) : 0 );

m_pBuffer = NULL;

return TRUE;

}

HRESULT CSequentialStream::PreAllocBuffer( ULONG uSize )

{

if( m_uBufferSize < uSize )

{

m_uBufferSize = uSize;

m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );

if( m_pBuffer == NULL )

{

Clear( );

return STG_E_INSUFFICIENTMEMORY;

}

}

return S_OK;

}

HRESULT CSequentialStream::Read( void* pv, ULONG cb, ULONG* pcbRead )

{

( pcbRead != NULL ? ( * pcbRead = 0U ) : 0 );

if( pv == NULL ) { return STG_E_INVALIDPOINTER; }

if( cb == 0U ) { return S_OK; }

ASSERT( m_uPosition <= m_uBufferUsed );

ULONG uBytesLeft = m_uBufferUsed - m_uPosition;

if( uBytesLeft == 0U ) { return S_FALSE; } //no more bytes

ULONG uBytesRead = ( cb > uBytesLeft ? uBytesLeft : cb );

memcpy( pv, ( BYTE* )m_pBuffer + m_uPosition, uBytesRead );

m_uPosition += uBytesRead;

( pcbRead != NULL ? ( * pcbRead = uBytesRead ) : 0 );

return ( cb != uBytesRead ? S_FALSE : S_OK );

}

HRESULT CSequentialStream::Write( const void* pv, ULONG cb, ULONG* pcbWritten )

{

if( pv == NULL ) { return STG_E_INVALIDPOINTER; }

( pcbWritten != NULL ? ( * pcbWritten = 0U ) : 0 );

if( cb == 0U ){ return S_OK; }

ASSERT( m_uPosition <= m_uBufferUsed );

if( m_uBufferSize < m_uPosition + cb )

{

m_uBufferSize = m_uPosition + cb;

m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );

if( m_pBuffer == NULL )

{

Clear( );

return STG_E_INSUFFICIENTMEMORY;

}

}

m_uBufferUsed = m_uPosition + cb;

memcpy( ( BYTE* )m_pBuffer + m_uPosition, pv, cb );

m_uPosition += cb;

( pcbWritten != NULL ? ( * pcbWritten = cb ) : 0 );

return S_OK;

}

下面我们开始往一个包含ntext字段的表中添加记录。假设这个表(News)的结构为:ID int NOT NULL IDENTITY、Title nchar(80)、 Contents ntext。

// 先将记录添加进去,ntext字段留空。我们稍后再更新ntext的内容。

HRESULT hResult = ExecuteSQL( pICommand, pICommandText, _T("INSERT INTO News VALUES('TEST','')") );

DBPROP dbProp;

dbPropSet.guidPropertySet = DBPROPSET_ROWSET;

dbPropSet.cProperties = 1;

dbPropSet.rgProperties = &dbProp;

DBPROPSET dbPropSet;

dbPropSet.rgProperties[0].dwPropertyID = DBPROP_UPDATABILITY;

dbPropSet.rgProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;

dbPropSet.rgProperties[0].dwStatus = DBPROPSTATUS_OK;

dbPropSet.rgProperties[0].colid = DB_NULLID;

dbPropSet.rgProperties[0].vValue.vt = VT_I4;

V_I4( &dbPropSet.rgProperties[0].vValue ) = DBPROPVAL_UP_CHANGE;

EAutoReleasePtr<ICommandProperties> pICommandProperties;

hResult = pICommandText->QueryInterface( IID_ICommandProperties, ( void** )&pICommandProperties );

// 设置 Rowset 属性为“可以更新某字段的值”

hResult = pICommandProperties->SetProperties( 1, &dbPropSet );

hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )L"SELECT Contents FROM News WHERE ID = @@IDENTITY" );

LONG lAffected;

EAutoReleasePtr<IRowsetChange> pIRowsetChange;

hResult = pICommand->Execute( NULL, IID_IRowsetChange, NULL, &lAffected, ( IUnknown** )&pIRowsetChange );

EAutoReleasePtr<IAccessor> pIAccessor;

hResult = pIRowsetChange->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );

struct BLOBDATA

{

DBSTATUS dwStatus;

DWORD dwLength;

ISequentialStream* pISeqStream;

};

// 有关DBOBJECT、DBBINDING的设置,建议参考MSDN,很容易懂。

DBOBJECT dbObj;

dbObj.dwFlags = STGM_READ;

dbObj.iid = IID_ISequentialStream;

DBBINDING dbBinding;

dbBinding.iOrdinal = 1; // BLOB 字段的位置,从 1 开始

dbBinding.obValue = offsetof( BLOBDATA, pISeqStream );

dbBinding.obLength = offsetof( BLOBDATA, dwLength );

dbBinding.obStatus = offsetof( BLOBDATA, dwStatus );

dbBinding.pTypeInfo = NULL;

dbBinding.pObject = &dbObj;

dbBinding.pBindExt = NULL;

dbBinding.dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;

dbBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;

dbBinding.eParamIO = DBPARAMIO_NOTPARAM;

dbBinding.cbMaxLen = 0;

dbBinding.dwFlags = 0;

dbBinding.wType = DBTYPE_IUNKNOWN;

dbBinding.bPrecision = 0;

dbBinding.bScale = 0;

HACCESSOR hAccessor = DB_NULL_HACCESSOR;

DBBINDSTATUS dbs;

hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, &dbBinding, sizeof( BLOBDATA ), &hAccessor, &dbs );

EAutoReleasePtr<IRowset> pIRowset;

hResult = pIRowsetChange->QueryInterface( IID_IRowset, ( void** )&pIRowset );

ULONG uRowsObtained = 0;

HROW* phRows = NULL;

hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );

CSequentialStream* pss = new CSequentialStream;

pss->PreAllocBuffer( 1024 ); // 预先分配好内存,并读入数据

pss->Write( pszSomebuffer, 512, NULL ); // pss->Write可以连续调用

pss->Write( pszSomebuffer+512, 512, NULL );

pss->ResetPosition( );

BLOBDATA bd;

bd.pISeqStream = ( ISequentialStream* )pss;

bd.dwStatus = DBSTATUS_S_OK;

bd.dwLength = pss->GetLength( );

// 将 BLOB 数据写入到数据库

hResult = pIRowsetChange->SetData( phRows[0], hAccessor, &bd );

pIAccessor->ReleaseAccessor( hAccessor, NULL );

pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );

// pss was released by pIRowsetChange->SetData.

这样,我们就完成了一条记录的添加。读取BLOB字段的代码跟上面的完全类似,只要把

hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );

后面的那些改成下面的代码即可。

BLOBDATA bd;

hResult = pIRowset->GetData( phRows[0], hAccessor, &bd );

if( bd.dwStatus == DBSTATUS_S_ISNULL )

{

// 此字段为空

}

else if( bd.dwStatus != DBSTATUS_S_OK || bd.pISeqStream == NULL )

{

// 失败

}

else

{

// 从系统分配的 ISequentialStream 接口读入 BLOB 数据

BYTE szReadBuffer[1024];

for( ULONG uRead = 0U; ; )

{

if( FAILED( bd.pISeqStream->Read( szReadBuffer, 1024, &uRead ) ) )

{

break;

}

//szReadBuffer中就包含了BLOB字段的数据

if( uRead != 1024 )

{

break;

}

}

bd.pISeqStream->Release( );

}

pIAccessor->ReleaseAccessor( hAccessor, NULL );

pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );

至此,要讲的已全部讲完,希望对你能有所帮助。文中贴出的代码都是可以复制使用的,只是某些地方需要加入返回值判断、错误处理代码。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有