OLE DB简介:
OLE DB是Microsot的新数据接口,他给予组件对象模型(Component Object Model, COM).这种接口是Microsoft对齐其列数据库API的最新补充之一。
OLE DB的特点在于对所有的数据库文件提供过了一个统一的调用接口,既可以访问关系数据库,也可以访问非关系数据库。OLE DB标准的核心内容就是要求各种数据存储(DataStore)都提供一种相同的访问接口,使得数据的使用者(应用程序)可以使用同样的方法访问各种数据库,而不用考虑数据的具体存储地点、格式或类型。
OLE DB的API是符合COM标准、基于对象的。因此,OLE DB标准实际上是规定了数据使用者和数据提供者之间的一种应用层协议(Application-Level Protocol)。
OLE DB编程模型
OLE DB将传统的数据库系统划分为多个逻辑组件:
- 数据提供者(Data Provider),提供数据存储的软件组件,小到普通的文本文件,大到主机上的复杂数据库,或者电子邮件存储,都是数据提供者。
- 数据服务提供商(Data Service Privider),位于数据提供者之上,从过去的数据库管理系统中分离出来,独立运行的功能组件,例如查询处理器和游标引擎 ,这些组件使得数据提供者提供的数据以表状数据的形式向外表示,而不管真实的物理数据是如何组织和存储的,并实数据的查询和修改功能。
- 业务组件(Bussiness Component)。利用数据服务提供者,专门完成某种特定信息业务处理、可以重用的功能组件。典型应用:分布式数据库应用系统中的中间层。
- 数据消费者(Data Consumer)。任何需要访问数据的系统程序或应用程序,除了典型的数据库应用程序之外,还包括需要访问各种数据源的开发工具或语言。
OLE DB对象的结构
OLE DB包括7各基本的对象:
- 枚举器(Enumerator):用于列举可用的数据源和其他可用的枚举器;
- 数据源对象(Data Source Object):数据源对象用于管理对数据源的连接,它是具体的数据源的抽象;
- 会话(Session):当创建了数据源对象后,为了处理与数据源对象有关的数据,需要使用数据源对象创建会话对象;
- 事务(Transaction),用于将多个操作合并为一个单一事务处理。事务对象缓存了对数据源的变化,是以用程序有机会选择提交还是退出以往的操作。事务能够提高系统访问数据源时的性能。
- 命令(Command):命令对象用于给数据源发送文本命令,主要用于执行SQL命令;
- 行集(Rowset):用表格的形式显示数据,如索引。行集可以从会话或者命令对象产生。
- 错误(Error):可以被任何OLE DB对象的任何接口产生,错误对象中封装了当访问数据提供程序时发生的错误。错误对象也能够获得扩展的返回码和状态信息。
OLE DB客户模板
直接使用OLE DB的对象和接口设计数据库应用程序需要书写大量的代码。为了简化程序设计,Vsual C++提供了ATL模板用于设计OLE DB数据库用用程序和
数据提供程序。VC中提供了用于OLE DB的ATL模板可分为数据提供程序的模板和数据使用程序的模板(客户模板)。下面将对OLE DB客户模板做一些介绍。
1、会话类
(1)CDataSource CDataSource类对应于OLE DB数据资源对象,代表一个通过服务器到数据资源的连接。
(2)CEnumerator CEnumerator相当于OLE DB规范中的枚举器对象,用来查询在系统中可用的数据提供程序的信息。其定义如下: class CEnumerator : public CAccessorRowset >
(3)CSession CSession对象代表一个单数据库访问会话。一个或多个会话可以与每一个由CDataSource对象代表的服务器连接(数据资源)相关联。CSession提供了StartTransaction方法。一旦一个事务开始了,就能够用Commit方法提交这个事务,或者用Abort方法撤销这个事务。
(4)CEnumeratorAccessor CEnumeratorAccessor类被CEnumerator类使用,用来从枚举器中返回的行集中的数据提供程序的信息。枚举器中所返回的行集中包含了系统中可用的数据提供程序以及程序中可见的枚举器信息。
2、存储器类
(1)CAccessorBase CAccessorBase是所有存储器类的基类。CAccessorBase允许一个行集合管理器管理多个存储器。它的参数和输出列都提供了绑定。
(2)CAccessor CAccessor类代表了存储器类型之一,支持一个行集合上的多个存储器。当知道数据库的结构和类型时,用这个存储器类型。其定义如下: templateclass CAccessor : public T, CAccessorBase
(3)CDynamicAccessor CDynamicAccessor类是可以在程序运行时动态产生的访问器类。当不知道数据源的列结构信息时,可以在程序中动态地查询数据源结构,然后根据得到的信息调用此类,将变量与数据源的列绑定。 class CDynamicAccessor : public CAccessorBase
(4)CDynamicParameterAccessor 当参数的信息不知道时,使用访问器类CDynamicParameterAccessor。如果数据提供程序支持,可以在程序中通过调用ICommandWithParameter接口获得参数信息,然后将变量与参数绑定。其定义如下: class CDynamicParameterAccessor : public CDynamicAccessor
(5)CManualAccessor CManualAccessor类是一个既可以处理列信息与变量绑定又可以处理参数与变量绑定的访问器。通过该类,可以使用任何数据提供程序支持或者可以转换为的数据类型。其定义如下: class CManualAccessor : public CAccessorBase
3、行集类
(1)CRowset 在OLE DB中,行集合是对象,通过它程序建立和检索数据库。CRowset封装了一个OLE DB行集合对象和几个相关接口,并提供了操作行集合数据的方法。
- (2)CArrayRowset
- CArrayRowset类用于当要使用数组下标来访问数据时。定义如下:
- template class CArrayRowset
- public CVirtualBuffer , public TRowset
(3)CBulkRowset CBulkRowset类用来在一次调用中成批地取得、操作获证查询多个行集的句柄。定义如下: class CBulkRowset : public CRowset
- (4)CAccessorRowset
- CAccessorRowset类将一个行集合和它的相关存储器封装在一个类里。定义如下:
- template class CAccessorRowset
- public TAccessor , public TRowset
- (5)CRestrictions
- CRestrictions用来对行集的计划进行限制。其定义如下:
- template class CRestrictions
- public CShemaRowset
4、命令类
(1)CCommand类用来设置、执行一个带参数的OLE DB命令。 template class CCommand : public CAccessorRowset , public CCommandBase
(2)CTable类用来访问一个被简答命令打开的行集。其定义如下: template class CTable : public CAccessorRowset
5、属性类
(1)CDBPropIDSet OLE DB客户用DBPROPIDSET结构来传递一组客户想得到属性信息的属性标识。CDBPropIDSet继承了DBPROPIDSET的结构并添加了一个构造程序,用来初始化关键字段和AddPropertyID访问方法。其定义如下: class CDBPropIDSet : public tagDBPROPIDSET
(2)CDBPropSet OLE DB服务器和客户用DBPROPSET结构来传递DBPROP结构数组。每一个DBPROP结构代表了可以被设置的单属性。CDBPropSet继承了DBPROPSET的结构并添加了一个构造程序,用来初始化关键字段和AddProperty访问方法。其定义如下: class CDBPropSet : public tagDBPROPSET
6、书签类
书签类CBookMark,用来对行集使用索引进行访问。其定义如下: template
class CBookmark : public CBookmarkBase template<> class CBookmark<0> : public CBookmarkBase
7、错误类
错误类包括CDBErrorInfo,此类用来查询OLE DB中的错误信息,相当与OLE DB规范中的错误对象。
OLE DB位图存储原理
在OLE DB通过普通的数据类型无法完成位图之类的大型数据存储。能够完成这项任务的,就是其提供的另一种数据对象:BLOB(ling binary data)。该数据类型是专门用来在数据库中存储大型数据,其数据是以二进制形式进行存储的。BLOB存取实现
由VC中的ATL OLE DB Comsumer Template Wizard创建的BLOB数据域只能实现读取,不能实现些操作。如果想实现操作,需要手动修改Template Wizard生成的代码。
在OLEDB中可以使用DBTYPE_BYTES来获取BLOB数据,也可以通过ISequentialStream 。但是,ATL中默认实现的ISequentialStream只能实现读操作。所 以需要通过特定的方法替换系统中OLE DB数据服务提供者的ISequentialStream指针,然后用自己的ISequentialStream进行替换。数据服务提供者会调用ISequentialStream::Read接口方法读取数据,直到道数据库中没有数据返回。替换后,消费者和数据服务提供者的角色会得到交换。
为了实现BLOB的写操作,需要对数据消费者类定义的代码做如下修改:
// 修改前,由ATL OLE DB Comsumer Template Wizar自动生成: BEGIN_COLUMN_MAP(...) // Various fields.. BLOB_ENTRY(2, IID_ISequentialStream,STGM_READ, m_BLOBDATA) // Various fields... END_COLUMN_MAP()</p> <p>// 修改之后: ULONG m_BLOBDATA_LENGTH; ULONG m_BLOBDATA_STATUS;</p> <p>BEGIN_COLUMN_MAP(...) // Various fields... BLOB_ENTRY_LENGTH_STATUS(2, IID_ISequentialStream, STGM_READ, m_BLOBDATA, m_BLOBDATA_LENGTH, m_BLOBDATA_STATUS) // Various fields... END_COLUMN_MAP()</p> <p>
BLOB_ENTRY_LENGTH_STATUS()宏的定义如下:
</p> <h1>define BLOB_ENTRY_LENGTH_STATUS(nOrdinal, IID, flags, data, length, status)</h1> <p>_BLOB_ENTRY_CODE_EX(nOrdinal, IID, flags, offsetbuf(data), offsetbuf(length), offsetbuf(status));</p> <p>
_BLOB_ENTRY_CODE_EX()宏的定义如下:
</p>
<h1>define _BLOB_ENTRY_CODE_EX(nOrdinal, IID, flags, dataOffset, lengthOffset, statusOffset)</h1>
<p>if (pBuffer != NULL)
{
CAccessorBase::FreeType(DBTYPE_IUNKNOWN, pBuffer + dataOffset);
}
else if (pBinding != NULL)
{
DBOBJECT* pObject = NULL;
ATLTRY(pObject = new DBOBJECT);
if (pObject == NULL)
return E_OUTOFMEMORY;
pObject->dwFlags = flags;
pObject->iid = IID;
CAccessorBase::Bind(pBinding, nOrdinal, DBTYPE_IUNKNOWN, sizeof(IUnknown*), 0, 0, eParamIO,
dataOffset, lengthOffset, statusOffset, pObject);
pBinding++;
}
nColumns++;</p>
<p>
该宏的主要作用的替换系统提供的BLOB_ENTRY的默认行为,提供自定义的ISequentialStream的实现,并新加了两个状态变量:m_BLOBDATA_LENGTH, m_BLOBDATA_STATUS,用于指示BLOB域的长度和状态。
经过修改后,BLOB域的读取需要遵循以下规范:
- 打开修改后的ATL OLE DB consumer类;
- 移动到目标数据记录;
- 检查BLOB域的状态。如果状态指示变量没有被设置为DB_STATUS_OK,客户代码将会得到一个空的BLOB域。这时不能使用ISequentialStream指针进行任何操作,直到状态变量变为DB_STATUS_OK;否则,其将会是一个非法指针;
- 循环在块单元中调用ISequentialStream::Read方法直到所有的数据均被读取出来。此时,客户代码可以将在循环中对获得的数据进行分批处理,而可以将其合并到一个更大的缓冲区中,在循环结束后进行数据处理;
- 调用FreeRecordMemory(),释放所有的数据域缓冲区;
- 移动到下一条记录。
实现位图读取的代码如下:
</p>
<p>//移动到目标记录集
...</p>
<p>// Read data from blob field.
do
{
// Call ISequentialStream::Read using provider's pointer.
hr = rs.m_BLOBDATA->Read( rgBuffer, sizeof(rgBuffer), &ulBytesRead );
if ( FAILED(hr) )
{
DisplayOLEDBErrorRecords( hr );
goto LoadHourGlassExit;
}</p>
<p>// Stuff data into helper class which is used as a dynamic buffer here.
hr = ISSHelper.Write( rgBuffer, sizeof(rgBuffer), NULL );
if ( FAILED(hr) )
{
DisplayOLEDBErrorRecords( hr );
goto LoadHourGlassExit;
};
}
while ( sizeof(rgBuffer) == ulBytesRead );</p>
<p>// Release ISequentialStream interface for each record you read or write to
// before moving to another record.
rs.FreeRecordMemory();
rs.Close();
//进行位图的显示等操作,此时位图的数据位于ISSHelper.m_pBuffer指向的缓冲区中</p>
<p>
BLOB数据的写操作也需要按照一定的规范进行:
- 打开修改后的ATL OLE DB consumer类;
- 移动到目标数据记录;
- 检查BLOB域的状态。如果状态指示变量没有被设置为DB_STATUS_OK,客户代码将会得到一个空的BLOB域。当状态变量变为DB_STATUS_OK后,客户代码必须释放数据服务提供着提供的ISequentialStream指针;
- 使用CISSHelper对象将数据写入到BLOB域中。该对象实现了ISequentialStream::Write方法。
- 将需要写入的数据长度传递给CISSHelper对象;
- 设置BLOB域的状态变量为DB_STATUS_OK;
- 将BLOB成员域(即ISequentialStream指针)指向CISSHelper对象;
- 在consumer类中调用SetData方法使数据库得到更新。此时,数据服务提供商会对CISSHelper对象调用ISequentialStream::Read方法,将所有的数据载入到comsumer类中绑定的对象中。
- 调用FreeRecordMemory(),释放所有的数据域缓冲区;
- 移动到下一条记录。
实现位图存入的代码如下:
//hDIB为Bitmap转换得到的DIB句柄</p>
<p>// Copy data in hDIB into ISSHelper
pData = GlobalLock( hDIB );
// Stuff data into ISequentialStream helper class.
hr = ISSHelper.Write( pData, GlobalSize( hDIB ), NULL );
if ( FAILED(hr) )
{
DisplayOLEDBErrorRecords( hr );
goto SaveHourGlassExit;
};</p>
<p>// Clean up memory handle for bitmap and null pData so we don't delete it later.
GlobalFree( hDIB );
pData = NULL;</p>
<p>// Open rowset's session object using our initialized data source object.
hr = rs.m_session.Open( m_DataSource );
if ( FAILED(hr) )
{
DisplayOLEDBErrorRecords( hr );
goto SaveHourGlassExit;
}</p>
<p>//
// Insert 1 record. This will fail due to primary key violation if a record
// already exists.
//
ExecuteSQL( "insert into BLOB1234 (ID,BLOBDATA) values (1, null)" );</p>
<p>// Open rowset. This will fail if BLOB1234 table does not exist.
hr = rs.OpenRowset();
if ( FAILED(hr) )
{
DisplayOLEDBErrorRecords( hr );
goto SaveHourGlassExit;
}</p>
<p>// Position rowset to first (one and only) record.
hr = rs.MoveNext();
if ( S_OK != hr ) // This can return DB_S_ENDOFROWSET which is not a failure.
{
DisplayOLEDBErrorRecords( hr );
goto SaveHourGlassExit;
}</p>
<p>// Release existing STGM_READ ISequentialStream pointer
// if one is given to us by provider.
if ( DBSTATUS_S_OK == rs.m_BLOBDATA_STATUS ) rs.m_BLOBDATA->Release();</p>
<p>// Set ISequentialStream pointer to our implementation.
rs.m_BLOBDATA = (ISequentialStream*) &ISSHelper;</p>
<p>// Set length field and status (required by some providers).
rs.m_BLOBDATA_LENGTH = ISSHelper.m_ulLength;
rs.m_BLOBDATA_STATUS = DBSTATUS_S_OK;</p>
<p>// Call SetData to trigger update.
hr = rs.SetData();
if ( FAILED(hr) )
{
DisplayOLEDBErrorRecords( hr );
goto SaveHourGlassExit;
}</p>
<p>// Close rowset and inform user of success.
rs.FreeRecordMemory();
rs.Close();</p>
<p>
以上所有代码均在VC6.0(SP6)下编译通过。由于在VC7.1以后的ATL对OLE DB模板类进行了调整,所以在VC7.1下编译的代码不能正常运行,运行时会抛出OLE DB 错误。我会继续关注,对其进行分析和修改,使其能够在VC7.1及以后版本上编译通过并顺利运行。
原文参考:AOTBLOB Read/Writes BLOB Using OLE DB Consumer Template(http://support.microsoft.com/kb/190958/en-us) 转载请注明出处:http://foremire.com/blog/archives/30

好久没有这样写技术方面的文章了,没想到这么一次就这么多。 写得不是很好,疏漏之处,还希望大家体谅。
写的很好 哪些代码只是粗略看了一下
很好,有见解