现代数据库系统除了支持一些标准的通用数据类型以外,大多数还支持一种称之为BLOB型的数据。

BLOB全称为big large object bytes, 大二进制对象类型,这种类型的数据通常用于存储文档、图片、音频等文件,这些文件一般体积较大,保存这些文件可以很方便的管理和检索这类信息。在MS SQLSERVER中常见的BLOB数据类型有text、ntext(n表示unicode)、image、nvarchar、varchar、varbinary等。其中image基本可以用来保存一切二进制文件,比如word、Excel、音频、视频等等类型。

针对BLOB型数据,OLEDB也提供了对它的支持

使用BLOB型数据的利弊

一般数据库对BLOB型数据有特殊的处理方式,比如压缩等等,在数据库中存储BLOB数据可以方便的进行检索,展示,备份等操作。但是由于BLOB型数据本身比较大,存储量太大时数据量太大容易拖慢数据库性能,所以一般的说法都是尽量不要在数据库中存储这类信息。特别是图片,音视频。针对这类文件一般的做法是将其保存在系统的某个路径钟中,而在数据库中存储对应的路径

操作BLOB型数据的一般方法

一般针对BLOB不能像普通数据那样操作,而需要一些特殊的操作,在OLEDB中通过设置绑定结构中的一些特殊值最终指定获取BLOB型数据的一个ISequentialStream接口指针,最终会通过这个接口来进行BLOB型数据的读写操作

判断一个列是否是BLOB型数据

判断某个列是否是BLOB型数据一般通过如下两个条件:

  1. pColumnInfo[i].wType == DBTYPE_IUNKNOW : 包含当列信息的DBCOLUMNSINFO 结构体对象的wType值为DBTYPE_IUNKNOW,该列的类型为DBTYPE_IUNKNOW,该条件也被称为列类型判定
  2. pColumnInfo[i].dwFlags & DBCOLUMNFLAGS_ISLONG :当列信息中的dwFlag值为DBCOLUMNFLAGS_ISLONG,也就是说该列的标识中包含DBCOLUMNFLAGS_ISLONG属性,该判定条件也被称之为列标识判定

    当这两个条件之一成立之时,我们就可以断定这列为BLOB型数据

BLOG型数据的绑定

在进行BLOB型数据的绑定也有特殊要求,主要体现在下面几点:

  1. 绑定结构的cbMaxLength 需要设置为0
  2. 绑定结构的wType设置为DBTYPE_IUNKNOW
  3. 为结构的pObject指针分配内存,大小等于DBOBJECT结构的大小
  4. 指定pObject的成员

    pObject->iid = IID_ISequentialStream

    pObject->dwFlags = STGM_READ
  5. 为行缓冲长度加上一个IStream指针的长度,此时数据源不再提供查询到的数据而提供一个接口指针,后续对BLOB数据的操作都使用该指针进行

    最后使用完后记得释放pObject所指向的内存空间

读取BLOB数据

根据前面所说的创建绑定结构,并为绑定结构赋值,最终可以从结果集中获取到一个ISequentialStream接口指针。调用接口的Read方法可以读取到BLOB列中的数据,而BLOB数据的长度存储在绑定时指定的数据长度内存偏移处,这与普通列的长度存放返回方式是一样的,一般BLOB数据都比较长,这个时候就需要分段读取。

在使用ISequentialStream接口操作BLOB型数据时需要注意的一个问题是,有的数据库不支持在一个访问器中访问多个BLOB数据列。一般BLOB数据列及其的消耗资源,并且数据库鼓励我们在设计数据库表结构的时候做到一行只有一列BLOB数据,因此很多数据库并不支持在一个访问器中读取多个BLOB数据。

要判断数据库是否支持在一个访问器中读取多个BLOB数据,可以获取DBPROP_MULTIPLESTORAGEOBJECTS属性,该属性属于属性集DBPROPSET_ROWSET,它是一个只读属性,如果该属性的值为TRUE表示支持,为FALSE表示不支持。

下面是一个读取BLOB型数据的例子,数据库中的表结构为:id(int)、text(image)、png(image)、jpg(image)

void ReadBLOB(IRowset *pIRowset)
{
COM_DECLARE_INTERFACE(IColumnsInfo);
COM_DECLARE_INTERFACE(IAccessor); DBORDINAL cColumns = 0;
DBCOLUMNINFO* rgColumnsInfo = NULL;
LPOLESTR lpszColumnsName = NULL;
DBBINDING* rgBindings = NULL;
DBBINDING** ppBindings = NULL; //绑定结构数组
DWORD *puDataLen = NULL; //当前访问器所需内存大小
DWORD *pulColCnt = NULL; //当前访问器中包含的项
ULONG ulBindCnt = 0; //访问器的数量
ULONG uBlob = 0; //当前有多少blob数据
HACCESSOR* phAccessor = NULL;
HROW* hRow = NULL;
DBCOUNTITEM ulGetRows = 0;
ULONG uCols = 0; PVOID pData1 = NULL; //第1个访问器中数据的缓冲
PVOID pData2 = NULL; //第2个访问器中数据的缓冲
PVOID pData3 = NULL; //第3个访问器中数据的缓冲 HRESULT hRes = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo);
COM_SUCCESS(hRes, _T("查询接口pIColumnsInfo失败,错误码为:%08x\n"), hRes);
hRes = pIColumnsInfo->GetColumnInfo(&cColumns, &rgColumnsInfo, &lpszColumnsName);
COM_SUCCESS(hRes, _T("获取结果集列信息失败,错误码为:%08x\n"), hRes); ppBindings = (DBBINDING**)COM_ALLOC(sizeof(DBBINDING*));
rgBindings = (DBBINDING*)COM_ALLOC(sizeof(DBBINDING) * cColumns);
pulColCnt = (DWORD*)COM_ALLOC(sizeof(DWORD));
puDataLen = (DWORD*)COM_ALLOC(sizeof(DWORD));
for (int i = 0; i < cColumns; i++)
{
//如果当前访问器对应的绑定结构的数组的首地址为空,将当前绑定结构指针作为绑定结构数组的首地址
if (NULL == ppBindings[ulBindCnt])
{
ppBindings[ulBindCnt] = &rgBindings[i];
} ++pulColCnt[ulBindCnt];
rgBindings[i].bPrecision = rgColumnsInfo[i].bPrecision;
rgBindings[i].bScale = rgBindings[i].bScale;
rgBindings[i].cbMaxLen = 10 * sizeof(WCHAR);
rgBindings[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBindings[i].dwPart = DBPART_LENGTH | DBPART_STATUS | DBPART_VALUE;
rgBindings[i].eParamIO = DBPARAMIO_NOTPARAM;
rgBindings[i].iOrdinal = rgColumnsInfo[i].iOrdinal;
rgBindings[i].obStatus = puDataLen[ulBindCnt];
rgBindings[i].obLength = puDataLen[ulBindCnt] + sizeof(DBSTATUS);
rgBindings[i].obValue = rgBindings[i].obLength + sizeof(ULONG);
rgBindings[i].wType = DBTYPE_WSTR; if (rgColumnsInfo[i].wType == DBTYPE_IUNKNOWN ||
rgColumnsInfo[i].dwFlags & DBCOLUMNFLAGS_ISLONG)
{
rgBindings[i].cbMaxLen = 0;
rgBindings[i].wType = DBTYPE_IUNKNOWN;
rgBindings[i].pObject = (DBOBJECT*)COM_ALLOC(sizeof(DBOBJECT));
rgBindings[i].pObject->iid = IID_ISequentialStream;
rgBindings[i].pObject->dwFlags = STGM_READ;
uBlob++;
} //记录下每个访问器所需内存的大小
puDataLen[ulBindCnt] = rgBindings[i].obValue + rgBindings[i].cbMaxLen;
if (rgBindings[i].wType == DBTYPE_IUNKNOWN)
{
puDataLen[ulBindCnt] = rgBindings[i].obValue + sizeof(ISequentialStream*);
} puDataLen[ulBindCnt] = UPGROUND(puDataLen[ulBindCnt]);
//判断当前是否需要创建单独的访问器
if ((uBlob || rgBindings[i].iOrdinal == 0))
{
ulBindCnt++;
ppBindings = (DBBINDING**)COM_REALLOC(ppBindings, sizeof(DBBINDING*) * (ulBindCnt + 1));
puDataLen = (DWORD*)COM_REALLOC(puDataLen, sizeof(DWORD) * (ulBindCnt + 1));
pulColCnt = (DWORD*)COM_REALLOC(pulColCnt, sizeof(DWORD) * (ulBindCnt + 1));
}
} //创建访问器
phAccessor = (HACCESSOR*)COM_ALLOC( (ulBindCnt + 1) * sizeof(HACCESSOR));
hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor);
COM_SUCCESS(hRes, _T("查询IAccessor接口失败,错误码为:%08x\n"), hRes);
for (int i = 0; i < ulBindCnt; i++)
{
hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, pulColCnt[i], ppBindings[i], 0, &phAccessor[i], NULL);
COM_SUCCESS(hRes, _T("创建访问器失败,错误码为:%08x\n"), hRes);
} //读取其中的一行数据
hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, 1, &ulGetRows, &hRow);
COM_SUCCESS(hRes, _T("读取行数据失败,错误码为:%08x\n"), hRes); //读取第一个绑定结构中的信息
pData1 = COM_ALLOC(puDataLen[0]);
hRes = pIRowset->GetData(hRow[0], phAccessor[0], pData1);
for(int i = 0; i < pulColCnt[0]; i++)
{
if (ppBindings[0][i].wType == DBTYPE_IUNKNOWN)
{
DBSTATUS dbStatus = *(DBSTATUS*)((BYTE*)pData1 + ppBindings[0][i].obStatus);
if (dbStatus == DBSTATUS_S_OK)
{
ULONG uFileLen = *(ULONG*)((BYTE*)pData1 + ppBindings[0][i].obLength);
if (uFileLen > 0)
{
DWORD dwReaded = 0;
PVOID pFileData = COM_ALLOC(uFileLen);
ZeroMemory(pFileData, uFileLen);
ISequentialStream *pSeqStream = *(ISequentialStream**)((BYTE*)pData1 + ppBindings[0][i].obValue);
pSeqStream->Read(pFileData, uFileLen, &dwReaded); WriteFileData(_T("1.txt"), pFileData, dwReaded);
}
}
}
} //后续的部分就不再写出来了,写法与上面的代码类似
pIRowset->ReleaseRows(1, hRow, NULL, NULL, NULL);
__CLEAR_UP:
//后面是清理的代码

由于我们事先知道数据表的结构,它有3个BLOB型数据,所以这里直接定义了3个缓冲用来接收3个BLOB型数据。为了方便检测,我们另外写了一个的函数,将读取出来的BLOB数据写入到文件中,事后以文件显示是否正确来测试这段代码

首先还是与以前一样,获取数据表的结构,然后进行绑定,注意这里由于使用的是SQL Server,它不支持一个访问器中访问多个BLOB,所以这里没有判断直接绑定不同的访问器。

在绑定的时候使用ulBindCnt作为当前访问器的数量,在循环里面有一个判断当(uBlob || rgBindings[i].iOrdinal == 0) && (ulBindCnt != cColumns - 1)条件成立时将访问器的数量加1,该条件表示之前已经有blob型数据(之前SQL不支持一个访问器访问多个BLOB,如果之前已经有BLOB数据了,就需要另外创建访问器)或者当前是第0行(因为第0行只允许读,所以将其作为与BLOB型数据一样处理),当这些条件成立时会新增一个访问器,而随着访问器的增加,需要改变ppBindings数组中的元素,该数组存储的是访问器对应的绑定结构开始的指针。数组puDataLen表示的是当前访问器所需内存的大小,pulColCnt表示当前访问器中共有多少列,针对这个表最终这些结构的内容大致如下图:

绑定完成之后,后面就是根据数组中的内容创建对应的访问器,然后绑定、读取数据,针对BLOB数据,我们还是一样从对应缓冲的obValue偏移处得到接口指针,然后调用接口的Read方法读取,最后写入文件

BLOB数据的写入:

要写入BLOB型数据也需要使用ISequentialStream接口,但是它不像之前可以直接使用接口的Write方法,写入的对象必须要自己从ISequentialStream接口派生,并指定一段内存作为缓冲,以便供OLEDB组件调用写方法时作为数据缓冲。这段缓冲必须要保证分配在COM堆上,也就是要使用CoTaskMemory分配内存。

这里涉及到的对象主要有IStream、ISequentialStream、IStorage、ILockBytes,同样,并不是所有数据源都支持这4类对象,具体支持哪些可以查询DBPROPSET_DATASOURCEINFO属性集中的DBPROP_STRUCTUREDSTORAGE属性来判定,目前SQL Server中支持ISequentialStream接口。

虽然我们可以使用这种方式来实现读写BLOB,但是每种数据源支持的程度不同,而且有的数据源甚至不支持这种方式,为了查询对读写BLOB数据支持到何种程度,可以查询DBPROPSET_DATASOURCEINFO属性集合的DBPROP_OLEOBJECTS属性来判定

通常有以下几种支持方式(DBPROP_OLEOBJECTS属性的值,按位设置):

  1. DBPROPVAL_OO_BLOB: 就是之前介绍的接口方式,使用接口的方式来读写BLOB数据

    DBPROPVAL_OO_DIRECTBIND: 可以直接绑定在行中,通过行访问器像普通列一样访问,也就是说它不需要获取专门的指针来操作,他可以就像操作普通数据那样,分配对应内存就可以访问,但是要注意分配内存的大小,每行中对应列中BLOB的数据长度差别可能会很明显,比如有的可能是一部长达2小时的电影文件,而有的可能是一部短视频,它们之间的差距可能会达到上G,而按照最小的来可能会发生截断,按最大的分配可能会发生多达好几个G的内存浪费

    DBPROPVAL_OO_IPERSIST:通过IPersistStream, IPersistStreamInit, or IPersistStorage三个接口的Persist对象访问

    DBPROPVAL_OO_ROWOBJECT: 支持整行作为一个对象来访问,通过结果集对象的IGetRow接口来获得行对象,但是这种模式会破坏第三范式,所以一般数据库都不支持

    DBPROPVAL_OO_SCOPED: 通过IScopedOperations接口来暴露行对象,通过这个接口可以暴露一个树形的结果集对象

    DBPROPVAL_OO_SINGLETON: 直接通过ICommand::Execute和IOpenRowset::OpenRowset来打开行对象

    下面是插入BLOB数据的一个实例
//自定义一个
class CSeqStream : public ISequentialStream
{
public:
// Constructors
CSeqStream();
virtual ~CSeqStream();
public:
virtual BOOL Seek(ULONG iPos); //将当前内存指针偏移到指定位置
virtual BOOL CompareData(void* pBuffer); //比较两段内存中的值
virtual ULONG Length()
{
return m_cBufSize;
};
virtual operator void* const()
{
return m_pBuffer;
};
public:
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppv); //读写内存的操作,这些是必须实现的函数
STDMETHODIMP Read(
/* [out] */ void __RPC_FAR *pv,
/* [in] */ ULONG cb,
/* [out] */ ULONG __RPC_FAR *pcbRead); STDMETHODIMP Write(
/* [in] */ const void __RPC_FAR *pv,
/* [in] */ ULONG cb,
/* [out]*/ ULONG __RPC_FAR *pcbWritten);
private:
ULONG m_cRef; // reference count
void* m_pBuffer; // buffer
ULONG m_cBufSize; // buffer size
ULONG m_iPos; // current index position in the buffer
};
//插入数据第一列BLOB数据
//这里由于已经事先知道每列的数据结构,因此采用偷懒的方法,一行行的插入
pData1 = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pdwDataLen[nCol]);
for(int i = 0; i < pulColCnt[nCol]; i++)
{
if (DBTYPE_IUNKNOWN == ppBindings[nCol][i].wType)
{
*(DBSTATUS*)((BYTE*)pData1 + ppBindings[nCol][i].obStatus) = DBSTATUS_S_OK;
CSeqStream *pSeqStream = new CSeqStream();
GetFileData(_T("test.txt"), dwFileLen, pFileData);
pSeqStream->Write(pFileData, dwFileLen, &dwWritten);
pSeqStream->Seek(0); //写这个操作将缓存的指针偏移到了最后,需要调整一下,以便OLEDB组件在插入BLOB数据时从缓存中读取
HeapFree(GetProcessHeap(), 0, pFileData);
*(ULONG*)((BYTE*)pData1 + ppBindings[nCol][i].obLength) = dwFileLen;
*(ISequentialStream**)((BYTE*)pData1 + ppBindings[nCol][i].obValue) = pSeqStream;
//此处不用release pSeqStream,COM组件会自动释放
}else
{
//根据数据库定义,此处应该为ID
*(ULONG*)((BYTE*)pData1 + ppBindings[nCol][i].obLength) = 10;
if (DBTYPE_WSTR == ppBindings[nCol][i].wType)
{
StringCchCopy((LPOLESTR)((BYTE*)pData1 + ppBindings[nCol][i].obValue), 10, SysAllocString(OLESTR("1")));
}
}
} hRes = pIRowsetChange->InsertRow(DB_NULL_HCHAPTER, phAccessor[nCol], pData1, &hNewRow);
COM_SUCCESS(hRes, _T("插入第1列BLOB数据失败,错误码为:%08x\n"), hRes);

在上面的代码中首先定义一个派生类,用来进行BLOB数据的读写,然后在后面的代码中演示了如何使用它

在后面的一段代码中,基本步骤和之前一样,经过连接数据源、创建回话对象,打开表,然后绑定,获取行访问器,这里由于代码基本不变,为了节约篇幅所以省略它们,只贴出最重要的部分。

在插入的代码中,首先查找访问器中的各个列的属性,如果是BLOB数据就采用BLOB数据的插入办法,否则用一般数据的插入办法。插入BLOB数据时,首先创建一个派生类的对象,注意此处由于后续要交给OLEDB组件调用,所以不能用栈内存。我们先调用类的Write方法将内存写入对应的缓冲中,然后调用Seek函数将内存指针偏移到缓冲的首地址,这个指针的作用就相当于文件的文件指针,COM组件在调用对应函数将它插入数据库时会采用这个内存的指针,所以必须将其置到首地址处。让后将对象的指针放入到对应的obvalues偏移中,设置对应的数据大小为BLOB数据的大小,最后只要像普通数据类型那样调用对应的更新方法即可实现BLOB数据的插入

最后贴上两个例子的详细代码地址

示例1:BLOB数据的读取

示例2:BLOB数据的插入

OLEDB存取BLOB型数据的更多相关文章

  1. mysql存取blob类型数据

    参考网址:http://www.cnblogs.com/jway1101/p/5815658.html 首先是建表语句,需要实现将表建立好. CREATE TABLE `blobtest` ( `pr ...

  2. XML Schema格式的"日期型数据”数据库存取

    对于XML Schema格式的"日期型数据"在数据库中存于datetime字段的时候,出现错误 mysql> select @@sql_mode; +------------ ...

  3. mysql之整型数据int

    mysql数据库设计,其中,对于数据性能优化,字段类型考虑很重要,mysql整型bigint.int.mediumint.smallint 和 tinyint的语法介绍,如下:1.bigint 从 - ...

  4. 【机器学习实战】第8章 预测数值型数据:回归(Regression)

    第8章 预测数值型数据:回归 <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/ ...

  5. 【C语言入门教程】2.3 整型数据

    没有小数位或指数的数据类型被称为整型数据,根据使用方法的分类,整型数据可分为整型常量和整型变量.根据定义或显示的数制分类,可分为十进制.八进制和十六进制. 2.3.1 整型常量 整型常量是在运算中不可 ...

  6. 2016年11月3日JS脚本简介数据类型: 1.整型:int 2.小数类型: float(单精度) double(双精度) decimal () 3.字符类型: chr 4.字符串类型:sting 5.日期时间:datetime 6.布尔型数据:bool 7.对象类型:object 8.二进制:binary 语言类型: 1.强类型语言:c++ c c# java 2.弱类型语

    数据类型: 1.整型:int 2.小数类型: float(单精度) double(双精度) decimal () 3.字符类型: chr 4.字符串类型:sting 5.日期时间:datetime 6 ...

  7. C#使用oledb方式将excel数据导入到datagridview后数据被截断为 255 个字符

    问题描述:在使用oledb方式将excel数据导入到datagridview中,在datagridview单元格中的数据没有显示全,似乎只截取了数据源中的一段 解决方案:1.关于该问题,微软官方答案: ...

  8. HotSpot关联规则算法(2)-- 挖掘连续型和离散型数据

    本篇代码可在 http://download.csdn.net/detail/fansy1990/8502323下载. 前篇<HotSpot关联规则算法(1)-- 挖掘离散型数据>分析了离 ...

  9. jmeter从外部文件取值问题,如果文件中的参数值为纯数字形式的,jmeter会默认将其识别成int型数据

    如果你通过CSV Data Set Config或者_StringFromFile函数来参数化你的请求,需要特别注意当参数为纯数字时,jmeter会默认将其识别成int型数据,说明jmeter并不是默 ...

随机推荐

  1. avascript的匿名函数

    一.什么是匿名函数? 在Javascript定义一个函数一般有如下三种方式: 函数关键字(function)语句: function fnMethodName(x){alert(x);} 函数字面量( ...

  2. C语言中静态断言的使用

    编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作异常处理的高级形式,用于代码调试. #define _CRT_SECURE_NO_WARNINGS //关闭安全监察 ...

  3. 月薪3万+的大数据人都在疯学Flink,为什么?

    身处大数据圈近5年了,在我的概念里一直认为大数据最牛的两个东西是Hadoop和Spark.18年下半年的时候,我突然发现身边很多大数据牛人都是研究学习Flink,甚至连Spark都大有被冷落抛弃的感觉 ...

  4. ubutu16.04修改分辨率

    http://blog.csdn.net/oiken/article/details/71088230 vmware不能自动适应Ubuntu16.04的分辨率,而且Ubuntu16.04的displa ...

  5. 精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能)

    原文:精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能) 本篇博客主要说明如何使用 UI Virtualization(以下简称为 UIV) 来提 ...

  6. maven No compiler is provided environment

    eclipse maven操作正常出现的No compiler is provided in this environment. Perhaps you are running on a JRE ra ...

  7. Java 实现发送邮件

    javax.mail.AuthenticationFailedException: 530 Error: A secure connection is requiered(such as ssl). ...

  8. Python web前端 07 函数及作用域

    Python web前端 07 函数及作用域 一.函数 1.有名函数和匿名函数 #函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块 #函数就是包裹在花括号里面的代码块,前面使用了关键字fun ...

  9. C# 调用键盘

    首先我们说的键盘指的是:tabtip和osk.但这两个所在的位置不同,样貌也不一样 C:\Windows\System32\osk.exe C:\Program Files\Common Files\ ...

  10. 04-树6 Complete Binary Search Tree (30 分)

    A Binary Search Tree (BST) is recursively defined as a binary tree which has the following propertie ...