首页  编辑  

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

Tags: /超级猛料/Database.数据库相关/ODBC、ADO、Access/   Date Created:
在需要对数据库进行操作时,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 );
    至此,要讲的已全部讲完,希望对你能有所帮助。文中贴出的代码都是可以复制使用的,只是某些地方需要加入返回值判断、错误处理代码。
img_27094.bmp (1.0KB)
img_8887.bmp (1.2KB)