首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
Java
emacs
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
308
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
16
篇与
的结果
2018-03-25
OLEDB 参数化查询
一般情况下,SQL查询是相对固定的,一条语句变化的可能只是条件值,比如之前要求查询二年级学生信息,而后面需要查询三年级的信息,这样的查询一般查询的列不变,后面的条件只有值在变化,针对这种查询可以使用参数化查询的方式来提高效率,也可以时SQL操作更加安全,从根本上杜绝SQL注入的问题。参数化查询的优势:提高效率:之前说过,数据库在执行SQL的过程中,每次都会经过SQL的解析,编译,调用对应的数据库组件,这样如果执行多次同样类型的SQL语句,解析,编译的过程明显是在浪费资源,而参数化查询就是使用编译好的过程(也就是提前告诉数据库要调用哪些数据库组件),这样就跳过了对SQL语句的解析,编译过程,提高了效率(这个过程我觉得有点类似于C/C++语言的编译执行与脚本语言的解释执行)。更加安全:从安全编程的角度来说,对于防范SQL注入方面,它比关键字过滤更有效,实现起来也更加方便。科普SQL注入和安全编程什么是SQL注入:所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。举个例子来说在用户登录时会输入用户名密码,这个时候在后台就可以执行这样的SQL语句select count(*) from user where username = 'haha' and password = '123456'只有输入对了用户名和密码才能登录,但是如果没有对用户输入进行校验,当用户输入一些SQL中的语句,而后台直接将用户输入进行拼接并执行,就会发生注入,比如此时用户输入 'haha' or 1 = 1 -- ,此时再后台执行的sql语句就变成了这样:select count(*) from user where username = 'haha' or 1 = 1 -- and password = ''这样用户就可以不用密码,直接使用用户名就登录了。而防范这类攻击,一般采用的是关键字过滤的方式,但是关键字过滤并不能杜绝这类工具,当一时疏忽忘记了过滤某个关键字仍然会产生这类问题。而且关键字过滤一般采用正则表达式,而正则表达式并不是一般人可以驾驭的。而防范SQL注入最简单也是最一劳永逸的方式就是参数化查询。为什么参数化查询能够从根本上解决SQL注入发生SQL注入一般的原因是程序将用户输入当做SQL语句的一部分进行执行,但是参数化查询它只是将用户输入当做参数,当做查询的条件,从数据库的层面上来说,它不对应于具体的数据库组件,它只是一组数据,而不会执行。这里可以简单的将传统的SQL拼接方式理解为C语言中的宏,宏也可以有参数,但是它不对参数进行校验,只是简单的进行替换,那么我可以使用一些指令作为参数传入,但是函数就不一样,函数的参数就是具体类型的变量或者常量。所以参数化查询从根本上解决的SQL注入的问题。参数化查询的使用前面说了这么多参数化查询的好处,那么到底怎么使用它呢?在Java等语言中内置了数据库操作,而对于C/C++来说,它并没有提供这方方面的标准。不同的平台有自己独特的一套机制,但是从总体来说,思想是共通的,只是语法上的不同,这里主要是说明OLEDB中的使用方式。使用“?”符将SQL语句中的条件值常量进行替换,组成一个新的SQL语句,比如上面登录的查询语句可以写成select count(*) from user where username = ? and password = ?调用ICommandText的SetCommandText设置sql语句。调用ICommandParpare的Prepare方法对含有"?"的语句进行预处理调用ICommandWithParameters方法的GetParameterInfo方法获取参数详细信息的DBPARAMINFO结构(类似于DBCOLUMNINFO)分配对应大小的DBBINDING缓冲用来保存每个参数的绑定信息调用IAccessor的CreateAccessor方法创建对应的访问器为参数分配缓冲,设置合适的参数后准备DBPARAMS结构调用ICommandText的Execute方法并将DBPARAMS结构的指针作为参数传入。操作返回的结果集对象typedef struct tagDBPROPIDSET { DBPROPID * rgPropertyIDs; ULONG cPropertyIDs; GUID guidPropertySet; } DBPROPIDSET;DBPARAMS结构的定义如下:typedef struct tagDBPARAMS { void *pData; DB_UPARAMS cParamSets; HACCESSOR hAccessor; } DBPARAMS;pData是保存参数信息的缓冲;cParamSets: 表示又多少个参数hAccessor: 之前获取到的绑定结构的访问器句柄下面是一个使用的例子:BOOL QueryData(LPOLESTR pQueryStr, IOpenRowset* pIOpenRowset, IRowset* &pIRowset) { IAccessor *pParamAccessor = NULL; //与参数化查询相关的访问器接口 LPOLESTR pSql = _T("Select * From aa26 Where Left(aac031,2) = ?"); //参数化查询语句 BOOL bRet = FALSE; DB_UPARAMS uParams = 0; DBPARAMINFO* rgParamInfo = NULL; LPOLESTR pParamBuffer = NULL; DWORD dwOffset = 0; DBBINDING *rgParamBinding = NULL; HACCESSOR hAccessor = NULL; DBPARAMS dbParams = {0}; DBBINDSTATUS *pdbBindStatus = NULL; //设置SQL hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, pSql); //预处理SQL命令 pICommandPrepare->Prepare(0); hRes = pICommandText->QueryInterface(IID_ICommandPrepare, (void**)&pICommandPrepare); //获取参数信息 hRes = pICommandText->QueryInterface(IID_ICommandWithParameters, (void**)&pICommandWithParameters); COM_SUCCESS(hRes, _T("查询接口ICommandWithParameters失败,错误码为:%08x\n"), hRes); hRes = pICommandWithParameters->GetParameterInfo(&uParams, &rgParamInfo, &pParamBuffer); COM_SUCCESS(hRes, _T("获取参数信息失败,错误码为:%08x\n"), hRes); rgParamBinding = (DBBINDING*)MALLOC(sizeof(DBBINDING) * uParams); ZeroMemory(rgParamBinding, sizeof(DBBINDING) * uParams); //绑定参数信息 for (int i = 0; i < uParams; i++) { rgParamBinding[i].bPrecision = rgParamInfo[i].bPrecision; rgParamBinding[i].bScale = rgParamInfo[i].bScale; rgParamBinding[i].cbMaxLen = 7 * sizeof(WCHAR); //行政区编号最大长度为6 rgParamBinding[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED; rgParamBinding[i].dwPart = DBPART_LENGTH | DBPART_VALUE; rgParamBinding[i].eParamIO = DBPARAMIO_INPUT; rgParamBinding[i].iOrdinal = rgParamInfo[i].iOrdinal; rgParamBinding[i].obLength = dwOffset; rgParamBinding[i].obStatus = 0; rgParamBinding[i].obValue = dwOffset + sizeof(ULONG); rgParamBinding[i].wType = DBTYPE_WSTR; dwOffset = dwOffset + sizeof(ULONG) + rgParamBinding[i].cbMaxLen; dwOffset = UPROUND(dwOffset); } //获取访问器 pdbBindStatus = (DBBINDSTATUS*)MALLOC(uParams * sizeof(DBBINDSTATUS)); ZeroMemory(pdbBindStatus, uParams * sizeof(DBBINDSTATUS)) pParamAccessor->CreateAccessor(DBACCESSOR_PARAMETERDATA, uParams, rgParamBinding, dwOffset, &hAccessor, pdbBindStatus); COM_SUCCESS(hRes, _T("获取参数访问器失败,错误码为:%08x\n"), hRes); //准备参数 dbParams.pData = MALLOC(dwOffset); ZeroMemory(dbParams.pData, dwOffset); dbParams.cParamSets = uParams; dbParams.hAccessor = hAccessor; for (int i = 0; i < uParams; i++) { *(ULONG*)((BYTE*)dbParams.pData + rgParamBinding[i].obLength) = _tcslen(pQueryStr) * sizeof(WCHAR); StringCchCopy((LPTSTR)((BYTE*)dbParams.pData + rgParamBinding[i].obValue), _tcslen(pQueryStr) + 1, pQueryStr); } //执行SQL hRes = pICommandText->Execute(NULL, IID_IRowset, &dbParams, NULL, (IUnknown**)&pIRowset); return bRet; }完整代码
2018年03月25日
6 阅读
0 评论
0 点赞
2018-02-12
数据更新接口与延迟更新
title: 数据更新接口与延迟更新tags: [OLEDB, 数据库编程, VC++, 数据库]date: 2018-02-12 14:29:35categories: windows 数据库编程keywords: OLEDB, 数据库编程, VC++, 数据库,数据库数据更新, 延迟提交在日常使用中,更新数据库数据经常使用delete 、update等SQL语句进行,但是OLEDB接口提供了额外的接口,来直接修改和更新数据库数据。OLEDB数据源更新接口为何不使用SQL语句进行数据更新常规情况下,使用SQL语句比较简单,利用OLEDB中执行SQL语句的方法似乎已经可以进行数据库的任何操作,普通的增删改查操作似乎已经够用了。确实,在某种情况下,这些内容已经够了,能够执行SQL语句并得到结果集已经够了,但是某些情况下并不合适使用SQL语句。SQL语句的执行一般经过这样几个步骤:数据库通过sql语句对SQL语句进行分析,生成一些可以被数据库识别的步骤,在这里我们叫它计划任务数据库根据计划任务中的相关操作,调用对应的核心组件来执行SQL语句中规定的操作。将操作得到的结果集返回到应用程序我们可以简单的将SQL语句理解为一种运行在数据库平台上的一个脚本语言,它与一般的脚本语言一样需要对每句话进行解释执行。根据解释的内容调用对应的功能模块来完成用户的请求。如果我们能够跳过SQL语句的解释,直接调用对应的核心组件,那么就能大幅度的提升程序的性能。OLEDB中的数据更新的相关接口就是完成这个操作的。至此我们可能有点明白为什么不用SQL语句而是用OLEDB的相关接口来实现对应的更新操作。主要是为了提高效率。数据修改的相关接口数据修改的相关接口主要是IRowsetChange,接口中提供的主要方法如下:DeleteRows: 删除某行InsertRows: 插入行SetData: 更新行通过这些接口就可以直接对数据库进行相关的增删改操作,而由于查询的操作条件复杂特别是涉及到多表查询的时候,所以OLEDB没有提供对应的查询接口。更新数据更新数据需要IRowsetChange接口,而打开该接口需要设置结果集的相关属性。一般需要设置下面两个属性:DBPROP_UPDATABILITY:该属性表示是否允许对数据进行更新,它是一个INT型的数值,该属性有3个标志的候选值:DBPROPVAL_UP_CHANGE,允许对数据进行更新,也就是打开SetData方法;DBPROPVAL_UP_DELETE:允许对数据进行删除,打开的是DeleteRows方法。DBPROPVAL_UP_INSERT,允许插入新行,该标志打开InsertRows方法。这3个值一般使用或操作设置对应的标识位。DBPROP_IRowsetUpdate:该属性是一个BOOL值,表示是否打开IRowsetChange接口。如果要使用IRowsetChange接口,需要将该属性设置为TRUE。它们属于属性集DBPROPSET_ROWSET。使用命令对象来设置设置完属性后,调用Execute执行SQL语句并获取到接口IRowsetChange。 PS:使用IRowsetChange接口有一个特殊的要求,必须使用IRowsetChange替代IRowset等接口,作为创建并打开结果集对象的第一个接口。也就是说Execute方法中的最后一个表示结果集对象的参数必须是IRowsetChange。数据更新模式一般来说,使用OLEDB的接口对数据库中的数据进行操作时,操作的结果是实时的反映到数据库中的。对于一般的应用程序来说。采用数据更新的接口虽然在一定程度上解决的效率的问题,但是使用实时更新的模式仍然有一些问题:修改立即反映到数据库中,不利于数据库中数据完整性维护和数据安全如果是网络中的数据库,会形成很多小的网络数据包传输,造成网络传输效率低下。因此OLEDB提供了另外一种更新模式——延迟更新延迟更新延迟更新本质上提供了一种将所有更新都在本地中缓存起来,最后再一口气将所有更新都一次性提交的机制,它与数据库中的事务不同,事务是将一组操作组织起来,当其中某一步操作失败,那么回滚事务中的所有数据,强调的是一个完整性维护,而延迟提交并不会自己校验某一步是否错误,它强调的是将某些更改一次性的提交。延迟提交与实时提交有下面几个优点:当多个客户端都在修改数据库中的数据时,有机会将某些客户端对数据的修改通知到其他客户端。可以合并对一行数据多列的修改并一次提交到数据源上网络数据库中可以将对不同表的不同操作合并成一个大的网络数据包,提高网络的使用效率。当更新不合适的时候有机会进行回滚打开延迟更新的接口要使用延迟更新必须申请打开OLEDB的IRowsetUpdate接口,这个申请主要通过设置结果集的DBPROP_IRowsetUpdate属性来实现,这个属性是一个bool类型的值。同时要打开延迟更新的接口必须先打开IRowsetChange接口,所以它们二者一般都是同时打开的。另外为了方便操作一般也会将结果集的DBPROP_CANHOLDROWS属性打开,该属性允许在上一次对某行数据的更改未提交的情况下插入新行。如果不设置该属性,那么在调用SetData方法进行更新后就必须调用IRowsetUpdate的Update接口进行提交,否则在提交之前数据库不允许进行Insert操作(但是允许进行SetData操作)在使用延迟更新的时候需要注意一个问题。当我们使用了DBPROP_CANHOLDROWS属性后,数据源为了维护方便,会额外返回一个第0行的数据。通常改行数据是一个INT型,由数据提供者进行维护,它一般是只读的,如果尝试对它进行修改可能会返回一个错误造成对数据的其他修改操作也失败。在这种情况下,可以考虑建立2个访问器,一个包含第0行,只用来做显示使用,而另外一个更新的访问器不绑定第0行。一般情况下可以通过检测返回结果集中的列信息中的标志字段来确定哪些列可以进行变更,哪些列是只读列等标志来创建多个不同用途的行访问器下面是延迟更新的例子:BOOL ExecSql(IOpenRowset *pIOpenRowset, IRowsetChange* &pIRowsetChange) { COM_DECLARE_BUFFER(); COM_DECALRE_INTERFACE(IDBCreateCommand); COM_DECALRE_INTERFACE(ICommandText); COM_DECALRE_INTERFACE(ICommandProperties); LPOLESTR lpSql = OLESTR("select * from aa26;"); BOOL bRet = FALSE; //TODO:query出ICommandProperties接口并设置对应属性 //设置属性,允许返回的结果集对数据进行增删改操作 dbProp[0].colid = DB_NULLID; dbProp[0].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[0].dwPropertyID = DBPROP_UPDATABILITY; dbProp[0].vValue.vt = VT_I4; dbProp[0].vValue.intVal = DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT; //设置属性,打开IRowsetUpdate接口实现延迟更新 dbProp[1].colid = DB_NULLID; dbProp[1].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[1].dwPropertyID = DBPROP_IRowsetUpdate; dbProp[1].vValue.vt = VT_BOOL; dbProp[1].vValue.boolVal = VARIANT_TRUE; //设置属性,允许结果集在不提交上次行的更改的情况下插入行 dbProp[2].colid = DB_NULLID; dbProp[2].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[2].dwPropertyID = DBPROP_CANHOLDROWS; dbProp[2].vValue.vt = VT_BOOL; dbProp[2].vValue.boolVal = VARIANT_TRUE; dbPropset[0].cProperties = 3; dbPropset[0].guidPropertySet = DBPROPSET_ROWSET; dbPropset[0].rgProperties = dbProp; hRes = pICommandProperties->SetProperties(1, dbPropset); COM_SUCCESS(hRes, _T("设置属性失败,错误码为:%08x\n"), hRes); hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, lpSql); COM_SUCCESS(hRes, _T("设置SQL失败,错误码为:%08x\n"), hRes); hRes = pICommandText->Execute(NULL, IID_IRowsetChange, NULL, NULL, (IUnknown**)&pIRowsetChange); COM_SUCCESS(hRes, _T("执行SQL语句失败,错误码为:%08x\n"), hRes); bRet = TRUE; __CLEAR_UP: SAFE_RELEASE(pIDBCreateCommand); SAFE_RELEASE(pICommandText); SAFE_RELEASE(pICommandProperties); return bRet; } int _tmain(int argc, TCHAR* argv[]) { CoInitialize(NULL); COM_DECLARE_BUFFER(); COM_DECALRE_INTERFACE(IOpenRowset); COM_DECALRE_INTERFACE(IRowsetChange); COM_DECALRE_INTERFACE(IColumnsInfo); COM_DECALRE_INTERFACE(IAccessor); COM_DECALRE_INTERFACE(IRowset); COM_DECALRE_INTERFACE(IRowsetUpdate); HRESULT hRes = S_FALSE; DBORDINAL cColumns = 0; DBCOLUMNINFO* ColumnsInfo = NULL; OLECHAR *pColumnsName = NULL; DBBINDING *pDBBindinfo = NULL; HACCESSOR hAccessor = NULL; HROW* phRow = NULL; HROW hNewRow = NULL; DBCOUNTITEM cRows = 10; //一次读取10行 DBCOUNTITEM dbObtained = 0; //真实读取的行数 LPVOID pInsertData = NULL; LPVOID pData = NULL; LPVOID pCurrData = NULL; DWORD dwOffset = 0; if(!CreateSession(pIOpenRowset)) { COM_PRINTF(_T("创建回话对象失败\n")); goto __CLEAR_UP; } if (!ExecSql(pIOpenRowset, pIRowsetChange)) { COM_PRINTF(_T("执行SQL语句失败\n")); goto __CLEAR_UP; } hRes = pIRowsetChange->QueryInterface(IID_IRowset, (void**)&pIRowset); COM_SUCCESS(hRes, _T("查询接口IRowsets失败,错误码为:%08x\n"), hRes); hRes = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo); COM_SUCCESS(hRes, _T("查询接口IColumnsInfo失败,错误码为:%08x\n"), hRes); hRes = pIColumnsInfo->GetColumnInfo(&cColumns, &ColumnsInfo, &pColumnsName); COM_SUCCESS(hRes, _T("获取结果集列信息失败,错误码为:%08x\n"), hRes); pDBBindinfo = (DBBINDING*)MALLOC(sizeof(DBBINDING) * (cColumns - 1)); for(int i = 0; i < cColumns; i++) { //取消第0行的绑定,防止修改数据时出错 if (ColumnsInfo[i].iOrdinal == 0) { continue; } pDBBindinfo[i - 1].iOrdinal = ColumnsInfo[i].iOrdinal; pDBBindinfo[i - 1].bPrecision = ColumnsInfo[i].bPrecision; pDBBindinfo[i - 1].bScale = ColumnsInfo[i].bScale; pDBBindinfo[i - 1].cbMaxLen = ColumnsInfo[i].ulColumnSize * sizeof(WCHAR); if (ColumnsInfo[i].wType == DBTYPE_I4) { //整型数据转化为字符串时4个字节不够,需要根据表中的长度来分配对应的空间 //数据库中表示行政单位的数据长度为6个字节 pDBBindinfo[i - 1].cbMaxLen = 7 * sizeof(WCHAR); } pDBBindinfo[i - 1].dwMemOwner = DBMEMOWNER_CLIENTOWNED; pDBBindinfo[i - 1].dwPart = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE; pDBBindinfo[i - 1].eParamIO = DBPARAMIO_NOTPARAM; pDBBindinfo[i - 1].obStatus = dwOffset; pDBBindinfo[i - 1].obLength = dwOffset + sizeof(DBSTATUS); pDBBindinfo[i - 1].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG); pDBBindinfo[i - 1].wType = DBTYPE_WSTR; //为了方便,类型由数据库进行转化 dwOffset = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG) + pDBBindinfo[i - 1].cbMaxLen; dwOffset = COM_UPROUND(dwOffset, 8); //8字节对齐 } //读取数据 hRes = pIRowsetChange->QueryInterface(IID_IAccessor, (void**)&pIAccessor); COM_SUCCESS(hRes, _T("查询IAccessor接口失败,错误码为:%08x\n"), hRes); hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cColumns - 1, pDBBindinfo, 0, &hAccessor, NULL); COM_SUCCESS(hRes, _T("创建访问器失败,错误码为:%08x\n"), hRes); hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &dbObtained, &phRow); COM_SUCCESS(hRes, _T("获取行数据失败,错误码为:%08x\n"), hRes); pData = MALLOC(cRows * dwOffset); for (int i = 0; i < dbObtained; i++) { pCurrData = (BYTE*)pData + dwOffset * i; hRes = pIRowset->GetData(phRow[i], hAccessor, pCurrData); //获取当前行 COM_SUCCESS(hRes, _T("获取数据失败, 错误码为:%08x\n"), hRes); DisplayColumnData(pDBBindinfo, cColumns - 1, pCurrData); // 将前10行第3列的数据修改为中国 LPOLESTR pUpdateData = (LPOLESTR)((BYTE*)pCurrData + pDBBindinfo[1].obValue); ULONG* pLen = (ULONG*)((BYTE*)pCurrData + pDBBindinfo[1].obLength); *pLen = 4; StringCchCopy(pUpdateData, pDBBindinfo[1].cbMaxLen, _T("中国")); hRes = pIRowsetChange->SetData(phRow[i], hAccessor, pCurrData); COM_SUCCESS(hRes, _T("修改数据失败, 错误码为:%08x\n"), hRes); } //插入一行数据 pInsertData = MALLOC(dwOffset); ZeroMemory(pInsertData, dwOffset); //为了方便直接在原来数据的基础上进行修改 CopyMemory(pInsertData, pData, dwOffset); DBSTATUS status[4] = {0}; ULONG uSize[4] = {0}; LPOLESTR lpValues[4] = {0}; for (int i = 0; i < 4; i++) { status[i] = *(DBSTATUS*)((BYTE*)pInsertData + pDBBindinfo[i].obStatus); uSize[i] = *(DBSTATUS*)((BYTE*)pInsertData + pDBBindinfo[i].obLength); lpValues[i] = (LPOLESTR)((BYTE*)pInsertData + pDBBindinfo[i].obValue); } StringCchCopy(lpValues[0], pDBBindinfo[0].cbMaxLen, _T("100001")); StringCchCopy(lpValues[1], pDBBindinfo[1].cbMaxLen, _T("测试")); hRes = pIRowsetChange->InsertRow(NULL, hAccessor, pInsertData, &hNewRow); COM_SUCCESS(hRes, _T("插入数据失败, 错误码为:%08x\n"), hRes); //删除一行数据 hRes = pIRowsetChange->DeleteRows(NULL, 1, &phRow[9], NULL); COM_SUCCESS(hRes, _T("查询IRowsetChange接口失败,错误码为:%08x\n"), hRes); hRes = pIRowsetChange->QueryInterface(IID_IRowsetUpdate, (void**)&pIRowsetUpdate); COM_SUCCESS(hRes, _T("查询IRowsetChange接口失败,错误码为:%08x\n"), hRes); ////提交上述修改 hRes = pIRowsetUpdate->Update(DB_NULL_HCHAPTER, 0, NULL, NULL, NULL, NULL); COM_SUCCESS(hRes, _T("提交数据更新失败, 错误码为:%08x\n"), hRes); //取消修改 //hRes = pIRowsetUpdate->Undo(DB_NULL_HCHAPTER, 0, NULL, NULL, NULL, NULL); COM_SUCCESS(hRes, _T("取消更新失败, 错误码为:%08x\n"), hRes); pIRowset->ReleaseRows(dbObtained, phRow, NULL, NULL, NULL); pIRowset->ReleaseRows(1, &hNewRow, NULL, NULL, NULL); __CLEAR_UP: SAFE_RELEASE(pIOpenRowset); SAFE_RELEASE(pIRowsetChange); SAFE_RELEASE(pIColumnsInfo); SAFE_RELEASE(pIRowsetUpdate); SAFE_RELEASE(pIRowset); SAFE_RELEASE(pIAccessor); CoTaskMemFree(ColumnsInfo); CoTaskMemFree(pColumnsName); FREE(pInsertData); FREE(pData); FREE(pDBBindinfo); CoUninitialize(); return 0; }在例子中仍然是首先进行数据库连接,创建出Session对象,创建Command对象,设置结果集对象的相关属性并执行sql语句。但是与之前不同的是,在执行SQL语句时不再返回IRowset接口而是返回IRowsetChange接口。然后利用IRowsetChange接口Query出其他需要的接口。接着仍然是绑定,与之前不同的是,在绑定中加了一个判断。跳过了第0行的绑定,以免它影响到后面的更新操作,然后打印输出对应的查询结果。并且在显示每行数据之后,调用SetData对数据进行更改。接着准备一个对应的缓冲,放入插入新行的数据。在这为了方便我们直接先拷贝了之前的返回的结果集中的一行的数据,然后再在里面进行修改,修改后调用InsertRows,插入一行数据。插入操作完成后,调用DeleteRows函数删除一行数据最后调用IRowsetUpdate接口的Update方法提交之前的更新,或者调用Undo取消之前的更改源代码查看
2018年02月12日
3 阅读
0 评论
0 点赞
2018-01-28
SQL语句执行与结果集的获取
上次说到命令对象是用来执行SQL语句的。数据源在执行完SQL语句后会返回一个结果集对象,将SQL执行的结果返回到结果集对象中,应用程序在执行完SQL语句后,解析结果集对象中的结果,得到具体的结果,这次的主要内容是如何解析结果集对象并获取其中的值。如何执行SQL语句执行SQL语句一般的步骤如下:创建ICommandText接口.使用ICommandText接口的SetCommandText方法设置SQL命令使用ICommandText接口的Excute方法执行SQL语句并接受返回的结果集对象,这个结果集对象一般是IRowset.其实OLEDB并不一定非要传入SQL语句,他可以传入简单的命令,只要数据源能够识别,也就是说我们可以根据数据源的不同传入那些只有特定数据源才会支持的命令,已达到简化操作或者实现某些特定功能的目的.针对有的SQL语句,我们并不是那么关心它返回了那些数据,比如说Delete语句,insert语句,针对这种情况我们可以将对应返回结果集的参数设置为NULL,比如像下面这样pICommandText->Execute(NULL, IID_NULL, NULL, NULL, NULL)明确告诉数据源程序不需要返回结果集,这样数据源不会准备结果集,减少了数据源的相关操作,从某种程度上减轻了数据源的负担。设置command对象的属性与之前数据源对象和会话对象的属性不同,command对象的属性是作用在返回的数据源对象上的,比如我们没有设置对应的更新属性,那么数据源就不允许我们使用结果集进行更新数据的操作。这些属性必须在执行SQL语句得到结果集的操作之前定义好。因为在获得数据源返回的结果集的时候数据源已经设置了对应的属性。command对象的属性集ID是PROPSET_ROWSET.该属性集中有很多能够影响结果集对象的属性。下面是一个执行SQL语句的例子: LPOLESTR lpSql = OLESTR("select * from aa26"); CreateDBSession(pIOpenRowset); HRESULT hRes = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand); COM_SUCCESS(hRes, _T("查询接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown**)&pICommandText); COM_SUCCESS(hRes, _T("创建接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, lpSql); COM_SUCCESS(hRes, _T("设置sql语句失败,错误码:%08x\n"), hRes); DBPROP dbProp[16] = {0}; DBPROPSET dbPropset[1] = {0}; //设置结果集可以进行增删改操作 dbProp[0].colid = DB_NULLID; dbProp[0].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[0].dwPropertyID = DBPROP_UPDATABILITY; dbProp[0].vValue.vt = VT_I4; dbProp[0].vValue.lVal = DBPROPVAL_UP_DELETE | DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT; //申请打开书签功能 dbProp[1].colid = DB_NULLID; dbProp[1].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[1].dwPropertyID = DBPROP_BOOKMARKS; dbProp[1].vValue.vt = VT_BOOL; dbProp[1].vValue.boolVal = VARIANT_TRUE; //申请打开行查找功能 dbProp[2].colid = DB_NULLID; dbProp[2].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[2].dwPropertyID = DBPROP_IRowsetFind; dbProp[2].vValue.vt = VT_BOOL; dbProp[2].vValue.boolVal = VARIANT_TRUE; //申请打开行索引 dbProp[3].colid = DB_NULLID; dbProp[3].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[3].dwPropertyID = DBPROP_IRowsetIndex; dbProp[3].vValue.vt = VT_BOOL; dbProp[3].vValue.boolVal = VARIANT_TRUE; //申请打开行定位功能 dbProp[4].colid = DB_NULLID; dbProp[4].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[4].dwPropertyID = DBPROP_IRowsetLocate; dbProp[4].vValue.vt = VT_BOOL; dbProp[4].vValue.boolVal = VARIANT_TRUE; //申请打开行滚动功能 dbProp[5].colid = DB_NULLID; dbProp[5].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[5].dwPropertyID = DBPROP_IRowsetScroll; dbProp[5].vValue.vt = VT_BOOL; dbProp[5].vValue.boolVal = VARIANT_TRUE; //申请打开行集视图功能 dbProp[6].colid = DB_NULLID; dbProp[6].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[6].dwPropertyID = DBPROP_IRowsetView; dbProp[6].vValue.vt = VT_BOOL; dbProp[6].vValue.boolVal = VARIANT_TRUE; //申请打开行集刷新功能 dbProp[7].colid = DB_NULLID; dbProp[7].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[7].dwPropertyID = DBPROP_IRowsetRefresh; dbProp[7].vValue.vt = VT_BOOL; dbProp[7].vValue.boolVal = VARIANT_TRUE; //申请打开列信息扩展接口 dbProp[8].colid = DB_NULLID; dbProp[8].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[8].dwPropertyID = DBPROP_IColumnsInfo2; dbProp[8].vValue.vt = VT_BOOL; dbProp[8].vValue.boolVal = VARIANT_TRUE; //申请打开数据库同步状态接口 dbProp[9].colid = DB_NULLID; dbProp[9].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[9].dwPropertyID = DBPROP_IDBAsynchStatus; dbProp[9].vValue.vt = VT_BOOL; dbProp[9].vValue.boolVal = VARIANT_TRUE; //申请打开行集分章功能 dbProp[10].colid = DB_NULLID; dbProp[10].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[10].dwPropertyID = DBPROP_IChapteredRowset; dbProp[10].vValue.vt = VT_BOOL; dbProp[10].vValue.boolVal = VARIANT_TRUE; dbProp[11].colid = DB_NULLID; dbProp[11].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[11].dwPropertyID = DBPROP_IRowsetCurrentIndex; dbProp[11].vValue.vt = VT_BOOL; dbProp[11].vValue.boolVal = VARIANT_TRUE; dbProp[12].colid = DB_NULLID; dbProp[12].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[12].dwPropertyID = DBPROP_IGetRow; dbProp[12].vValue.vt = VT_BOOL; dbProp[12].vValue.boolVal = VARIANT_TRUE; //申请打开行集更新功能 dbProp[13].colid = DB_NULLID; dbProp[13].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[13].dwPropertyID = DBPROP_IRowsetUpdate; dbProp[13].vValue.vt = VT_BOOL; dbProp[13].vValue.boolVal = VARIANT_TRUE; dbProp[14].colid = DB_NULLID; dbProp[14].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[14].dwPropertyID = DBPROP_IConnectionPointContainer; dbProp[14].vValue.vt = VT_BOOL; dbProp[14].vValue.boolVal = VARIANT_TRUE; dbPropset[0].cProperties = 15; dbPropset[0].guidPropertySet = DBPROPSET_ROWSET; dbPropset[0].rgProperties = dbProp; hRes = pICommandText->QueryInterface(IID_ICommandProperties, (void**)&pICommandProperties); COM_SUCCESS(hRes, _T("查询接口ICommandProperties失败,错误码:%08x\n"), hRes); hRes = pICommandProperties->SetProperties(1, dbPropset); COM_SUCCESS(hRes, _T("设置属性失败,错误码:%08x\n"), hRes); hRes = pICommandText->Execute(NULL, IID_IRowset, NULL, NULL, (IUnknown**)&pIRowset); COM_SUCCESS(hRes, _T("执行sql语句失败,错误码:%08x\n"), hRes);这段代码详细的展示了如何执行SQL语句获取结果集并设置COMMANDUI对象的属性。结果集对象结果集一般是执行完SQL语句后返回的一个代表二维结构化数组的对象。这个结构化对象可以理解为一个与数据表定义相同的一个结构体。而结果集中保存了这个结构体的指针下面是结果集对象的详细定义CoType TRowset { [mandatory] interface IAccessor; [mandatory] interface IColumnsInfo; [mandatory] interface IConvertType; [mandatory] interface IRowset; [mandatory] interface IRowsetInfo; [optional] interface IChapteredRowset; [optional] interface IColumnsInfo2; [optional] interface IColumnsRowset; [optional] interface IConnectionPointContainer; [optional] interface IDBAsynchStatus; [optional] interface IGetRow; [optional] interface IRowsetChange; [optional] interface IRowsetChapterMember; [optional] interface IRowsetCurrentIndex; [optional] interface IRowsetFind; [optional] interface IRowsetIdentity; [optional] interface IRowsetIndex; [optional] interface IRowsetLocate; [optional] interface IRowsetRefresh; [optional] interface IRowsetScroll; [optional] interface IRowsetUpdate; [optional] interface IRowsetView; [optional] interface ISupportErrorInfo; [optional] interface IRowsetBookmark; }结果集对象的一般用法得到结果集后,它的使用步骤一般如下:首先Query出IColumnsInfo接口通过调用IColumnsInfo::GetColumnInfo方法得到关于结果集的列的详细信息DBCOLUMNINFO结构的数组,包括:列序号,列名,类型,字节长度,精度,比例等3.通过该结构数组,准备一个对应的DBBINDING结构数组,并计算每行数据实际需要的缓冲大小,并填充结构DBBINDING。这个过程一般叫做绑定4.利用DBBINDING数组和IAccessor::CreateAccessor方法创建一个数据访问器并得到句柄HACCESSOR调用IRowset::GetNextRow遍历行指针到下一行,第一次调用就是指向第一行,并得到行句柄HROW,这个行句柄表示我们访问的当前是结果中的第几行,一般它的值是一个依次递增的整数调用IRowset::GetData传入准备好的行缓冲内存指针,以及之前创建的访问器HACCESSOR句柄和HROW句柄。最终行数据就被放置到了指定的缓冲中。循环调用GetNextRow和GetData即可遍历整个二维结果集。列信息的获取取得结果集对象后,紧接着的操作一般就是获取结果集的结构信息,也就是获取结果集的列信息(有些材料中称为字段信息)要获取列信息,就需要QueryInterface出结果集对象的IColumnsInfo接口,并调用IColumnsInfo::GetColumnInfo方法获得一个称为DBCOLUMNINFO结构体的数组该结构体中反映了列的逻辑结构信息(抽象数据类型)和物理结构信息(内存需求大小等信息)函数GetColumnInfo定义如下:HRESULT GetColumnInfo ( DBORDINAL *pcColumns, DBCOLUMNINFO **prgInfo, OLECHAR **ppStringsBuffer);第一个参数表示总共有多少列,第二个参数是一个DBCOLUMNINFO,返回一个列信息的数组指针,第三个参数返回一个字符串指针,这个字符串中保存的是个列的名称,每个名称间以\0\0分割。但是我们一般不使用它来获取列名,我们一般使用DBCOLUMNINFO结构的pwszName成员。DBCOLUMNINFO定义如下:typedef struct tagDBCOLUMNINFO { LPOLESTR pwszName; //列名 ITypeInfo *pTypeInfo; //列的类型信息 DBORDINAL iOrdinal; //列序号 DBCOLUMNFLAGS dwFlags; //列的相关标识 DBLENGTH ulColumnSize; //列最大可能的大小,对于字符串来说,它表示的是字符个数 DBTYPE wType; //列类型 BYTE bPrecision; //精度(它表示小数点后面的位数) BYTE bScale; //表示该列的比例,目前没有用处,一般给的是0 DBID columnid; //列信息在数据字典表中存储的ID } DBCOLUMNINFO;对于columnid成员,DBMS系统一般会有多个系统表来表示众多的信息,比如用户信息,数据库信息,数据表信息等等,其中针对每个表中的列的相关信息DBMS系统使用特定的系统表来存储,而查询这个系统表来获取列信息时使用的就是这个columnid值。数据绑定一般绑定需要两步,1是获取列信息的DBCOLUMNINFO结构,接着就是根据列信息来填充DBBINDING数据结构。有的时候可能会觉得绑定好麻烦啊,还不如直接返回一个缓冲,将所有结果放入里面,应用程序根据需求自己去解析它,这样岂不是更方便。之所以需要绑定,有下面一个理由:并不是所有的数据类型都能被应用程序支持,比如说数据库中的NUMBER类型在VC++中找不到对应的数据结构来支持。有时一行数据并不能完全读取到内存中,比如说我们给的缓冲不够或者是数据库中的数据本身比较大,比如存储了一个视频文件等等。在程序中并不是所有的访问器都是为了读取数据,而且使用返回所有结果的方式太简单粗暴了,比如我只想要一列的数据那个数据可能占用内存不足1K,但是数据库表中某一列数据特别大,可能占用内存会超过一个G,如果全都返回的话太浪费内存了。所以在绑定时候可以灵活的指定返回那些数据,返回数据长度是多少,针对特别大的数据,我们可以指定它只返回部分,比如只返回前面的1K使用绑定可以灵活的安排返回数据在内存中的摆放形式。绑定结构的定义如下:typedef struct tagDBBINDING { DBORDINAL iOrdinal; //列号 DBBYTEOFFSET obValue; DBBYTEOFFSET obLength; DBBYTEOFFSET obStatus; ITypeInfo *pTypeInfo; DBOBJECT *pObject; DBBINDEXT *pBindExt; DBPART dwPart; DBMEMOWNER dwMemOwner; DBPARAMIO eParamIO; DBLENGTH cbMaxLen; //数据的最大长度,一般给我们为这个列的数据准备的缓冲的长度 DWORD dwFlags; DBTYPE wType; //将该列转化为何种数据类型展示 BYTE bPrecision; BYTE bScale; }DBBINDING;参数的详细说明:obValue、obLength、obStatus:数据源在返回结果的时候一般会返回该列信息的三中数据,数据长度、数据状态、数据值。数据状态表示数据源在提供数据的一个状态信息,比如该列信息为空时它会返回一个DBSTATUS_S_ISNULL,列数据比较长,而提供的数据可能不够,这个时候会返回一个状态表示发生了截断。而数据长度表示返回结果的长度。这个值是返回的数据对应的字节长度,注意这里需要与前面ulColumnSize区分开来。三个数据在内存中摆放的顺序如下:其中每列数据都是按照status length value结构排布,而不同的列的数据按照顺序依次向后排放,这个内存的布局有点像结构体数组在内存中的的布局方式。而绑定结构中的obValue、obLength、obStatus规定了它们三者在一块内存缓冲中的偏移,要注意后面一列的开始位置是在前面一列的结束位置而不是所有数据都是从0开始。dwPart:前面说数据源返回结果中有3个部分,但是我们可以指定数据源返回这3个部分的哪些部分,它的值是一些标志位,根据这些标志来决定需要返回哪些数据,不需要返回哪些数据.它的值主要有:DBPART_LENGTH、 DBPART_STATUS、DBPART_VALUE;dwMemOwner,这个值表示将使用何种内存缓冲来保存数据源返回的结果,我们一般使用DBMEMOWNER_CLIENTOWNED,表示使用用户自定义内存的方式,即这个缓冲需要自行准备。eParamIO:我们将返回的值做何种用途,DBPARAMIO_NOTPARAM表示不做特殊用途,DBPARAMIO_INPUT,作为输入值,一般在需要更新列数据的时候使用这个标志,DBPARAMIO_OUTPUT,作为输出值,这个输出时相对于数据源来说的,表示输出到应用程序程序缓冲,作为展示用。wType:将数据源中的原始数据做何种类型的转化,比如原来数据库中存储的是整数的123456,而这个值是DBTYPE_WSTR的话,数据源中的结果会被转化为字符串的"123456",放入到缓冲中。DBBINDING 与DBCOLUMNSINFO结构的比较它们二者中有许多数据成员是相同的,表示的含义也基本相同,但是二者也有显著的区别:DBCOLUMNINFO是数据提供者给使用者的信息,它是固定的,对相同的查询来说,列总是相同的,因此数据提供者返回的 DBCOLUMNINFO数组也是固定的.而DBBINDING是作为数据消费者创建之后给数据提供者的一个结构数组,它的内容则由调用者来完全控制,通过这个结构可以指定数据提供者最终将数据摆放成调用者指定的格式,并进行指定的数据类型转换.针对相同的查询我们可以指定不同的DBBINDINGS结构。DBCOLUMNINFO反映的是二维结果集的原始列结构信息而DBBINDING则反映的是二维结果集数据最终按要求摆放在内存中的样式下面是一个针对绑定的列子:ExecSql(pIOpenRowset, pIRowset); //创建IColumnsInfo接口 HRESULT hRes = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo); COM_SUCCESS(hRes, _T("查询接口IComclumnsInfo,错误码:%08x\n"), hRes); //获取结果集的详细信息 hRes = pIColumnsInfo->GetColumnInfo(&cClumns, &rgColumnInfo, &lpClumnsName); COM_SUCCESS(hRes, _T("获取列信息失败,错误码:%08x\n"), hRes); //绑定 pDBBindings = (DBBINDING*)MALLOC(sizeof(DBBINDING) * cClumns); for (int iRow = 0; iRow < cClumns; iRow++) { pDBBindings[iRow].bPrecision = rgColumnInfo[iRow].bPrecision; pDBBindings[iRow].bScale = rgColumnInfo[iRow].bScale; pDBBindings[iRow].cbMaxLen = rgColumnInfo[iRow].ulColumnSize * sizeof(WCHAR); if (rgColumnInfo[iRow].wType == DBTYPE_I4) { //数据库中行政单位的长度最大为6位 pDBBindings[iRow].cbMaxLen = 7 * sizeof(WCHAR); } pDBBindings[iRow].dwMemOwner = DBMEMOWNER_CLIENTOWNED; pDBBindings[iRow].dwPart = DBPART_LENGTH | DBPART_STATUS | DBPART_VALUE; pDBBindings[iRow].eParamIO = DBPARAMIO_NOTPARAM; pDBBindings[iRow].iOrdinal = rgColumnInfo[iRow].iOrdinal; pDBBindings[iRow].obStatus = dwOffset; pDBBindings[iRow].obLength = dwOffset + sizeof(DBSTATUS); pDBBindings[iRow].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG); pDBBindings[iRow].wType = DBTYPE_WSTR; dwOffset = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG) + pDBBindings[iRow].cbMaxLen * sizeof(WCHAR); dwOffset = COM_ROUNDUP(dwOffset); //进行内存对齐 } //创建访问器 hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor); COM_SUCCESS(hRes, _T("查询IAccessor接口失败错误码:%08x\n"), hRes); hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cClumns, pDBBindings, 0, &hAccessor, NULL); COM_SUCCESS(hRes, _T("创建访问器失败错误码:%08x\n"), hRes); //输出列名信息 DisplayColumnName(rgColumnInfo, cClumns); //分配对应的内存 pData = MALLOC(dwOffset * cRows); while (TRUE) { hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &uRowsObtained, &phRows); if (hRes != S_OK && uRowsObtained != 0) { break; } ZeroMemory(pData, dwOffset * cRows); //显示数据 for (int i = 0; i < uRowsObtained; i++) { pCurrData = (BYTE*)pData + dwOffset * i; pIRowset->GetData(phRows[i], hAccessor, pCurrData); DisplayData(pDBBindings, cClumns, pCurrData); } //清理hRows pIRowset->ReleaseRows(uRowsObtained, phRows, NULL, NULL, NULL); CoTaskMemFree(phRows); phRows = NULL; } //显示列名称 void DisplayColumnName(DBCOLUMNINFO *pdbColumnInfo, DBCOUNTITEM iDbCount) { COM_DECLARE_BUFFER(); for (int iColumn = 0; iColumn < iDbCount; iColumn++) { COM_CLEAR_BUFFER(); TCHAR wszColumnName[MAX_DISPLAY_SIZE + 1] =_T(""); size_t dwSize = 0; StringCchLength(pdbColumnInfo[iColumn].pwszName, MAX_DISPLAY_SIZE, &dwSize); dwSize = min(dwSize, MAX_DISPLAY_SIZE); StringCchCopy(wszColumnName, MAX_DISPLAY_SIZE, pdbColumnInfo[iColumn].pwszName); COM_PRINTF(wszColumnName); COM_PRINTF(_T("\t")) } COM_PRINTF(_T("\n")); } //显示数据 void DisplayData(DBBINDING *pdbBindings, DBCOUNTITEM iDbColCnt, void *pData) { COM_DECLARE_BUFFER(); for (int i = 0; i < iDbColCnt; i++) { COM_CLEAR_BUFFER(); DBSTATUS status = *(DBSTATUS*)((PBYTE)pData + pdbBindings[i].obStatus); ULONG uSize = (*(ULONG*)((PBYTE)pData + pdbBindings[i].obLength)) / sizeof(WCHAR); PWSTR pCurr = (PWSTR)((PBYTE)pData + pdbBindings[i].obValue); switch (status) { case DBSTATUS_S_OK: case DBSTATUS_S_TRUNCATED: COM_PRINTF(_T("%s\t"), pCurr); break; case DBSTATUS_S_ISNULL: COM_PRINTF(_T("%s\t"), _T("(null)")); break; default: break; } } COM_PRINTF(_T("\n")); }在使用前一个列子中的方法设置对应的属性并执行SQL语句后,得到一个结果集,然后调用对应的Query方法,得到一个pIColumnsInfo接口,接着调用接口的GetColumnsInfo方法,获取结构的具体信息。最需要注意的是绑定部分的代码,根据返回的具体列数,我们定义了一个对应的绑定结构的数组,将每个赋值,赋值的时候定义了一个dwOffset结构来记录当前使用内存的情况,这样每次在循环执行一次后,它的位置永远在上一个列信息缓冲的尾部,这样我们可以很方便的进行偏移的计算。绑定完成后这个dwOffset的值就是所有列使用的内存的总大小,因此在后面利用这个值分配一个对应长度的内存。然后循环调用GetNextRows、GetData方法依次获取每行、每列的数据。最后调用相应的函数来进行显示,至此就完成了数据的读取操作。最后,我发现码云上的代码片段简直就是为保存平时例子代码而生的,所以后面的代码将不再在GitHub上更新了,而换到码云上面。源代码查看
2018年01月28日
29 阅读
5 评论
0 点赞
2018-01-21
事务对象和命令对象
上次说到数据源对象,这次接着说事务对象和命令对象。事务是一种对数据源的一系列更新进行分组或批处理以便当所有更新都成功时同时提交这些更新,或者如果任何一个更新失败则不提交任何更新并且回滚整个事务的方法.命令对象一般是用来执行sql语句并生成结果集的对象会话对象在OLEDB中通过以下3中方式支持事务:ITransactionLocal::StartTransactionITransaction::commitITransaction::AbortOLEDB中定义事务和回话对象的接口如下:CoType TSession { [mandatory] interface IGetDataSource; [mandatory] interface IOpenRowset; [mandatory] interface ISessionProperties; [optional] interface IAlterIndex; [optional] interface IAlterTable; [optional] interface IBindResource; [optional] interface ICreateRow; [optional] interface IDBCreateCommand; [optional] interface IDBSchemaRowset; [optional] interface IIndexDefinition; [optional] interface ISupportErrorInfo; [optional] interface ITableCreation; [optional] interface ITableDefinition; [optional] interface ITableDefinitionWithConstraints; [optional] interface ITransaction; [optional] interface ITransactionJoin; [optional] interface ITransactionLocal; [optional] interface ITransactionObject; }在创建了数据库连接之后使用QueryInterface 查询出IDBCreateSeesion对象,然后调用IDBCreateSession的CreateSession方法创建一个回话对象。需要注意的是,一个数据源连接可以创建多个回话对象,这里只能通过这种方式创建回话对象,而不能直接通过CoCreateInstance 来创建。一般来说应用中至少创建一个会话对象Command对象Command对象的定义如下:CoType TCommand { [mandatory] interface IAccessor; [mandatory] interface IColumnsInfo; [mandatory] interface ICommand; [mandatory] interface ICommandProperties; [mandatory] interface ICommandText; [mandatory] interface IConvertType; [optional] interface IColumnsRowset; [optional] interface ICommandPersist; [optional] interface ICommandPrepare; [optional] interface ICommandWithParameters; [optional] interface ISupportErrorInfo; [optional] interface ICommandStream; }一般创建Command对象是通过Session对象Query出来一个IDBCreateCommand接口让后调用CreateCommand方法创建。与会话对象相似,一个会话对象可以创建多个命令对象,但是从上面会话对象的定义可以看出IDBCreateCommand接口是一个可选接口,并不是所有的数据库都支持,因此在创建命令对象的时候一定要注意判断是否支持。对于不支持的可以采用其他办法(一般采用直接打开数据库表的方式)。下面是一个演示例子:#define CHECK_OLEDB_INTERFACE(I, iid)\ hRes = (I)->QueryInterface(IID_##iid, (void**)&(p##iid));\ if(FAILED(hRes))\ {\ OLEDB_PRINTF(_T("不支持接口:%s\n"), _T(#iid));\ }\ else\ {\ OLEDB_PRINTF(_T("支持接口:%s\n"), _T(#iid));\ } BOOL CreateDBSession(IOpenRowset* &pIOpenRowset) { DECLARE_BUFFER(); DECLARE_OLEDB_INTERFACE(IDBPromptInitialize); DECLARE_OLEDB_INTERFACE(IDBInitialize); DECLARE_OLEDB_INTERFACE(IDBCreateSession); BOOL bRet = FALSE; HWND hDesktop = GetDesktopWindow(); HRESULT hRes = CoCreateInstance(CLSID_DataLinks, NULL, CLSCTX_INPROC_SERVER, IID_IDBPromptInitialize, (void**)&pIDBPromptInitialize); OLEDB_SUCCESS(hRes, _T("创建接口IDBPromptInitialize失败,错误码:%08x\n"), hRes); hRes = pIDBPromptInitialize->PromptDataSource(NULL, hDesktop, DBPROMPTOPTIONS_WIZARDSHEET, 0, NULL, NULL, IID_IDBInitialize, (IUnknown**)&pIDBInitialize); OLEDB_SUCCESS(hRes, _T("弹出接口数据源配置对话框失败,错误码:%08x\n"), hRes); hRes = pIDBInitialize->Initialize(); OLEDB_SUCCESS(hRes, _T("链接数据库失败,错误码:%08x\n"), hRes); hRes = pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession); OLEDB_SUCCESS(hRes, _T("创建接口IDBCreateSession失败,错误码:%08x\n"), hRes); hRes = pIDBCreateSession->CreateSession(NULL, IID_IOpenRowset, (IUnknown**)&pIOpenRowset); OLEDB_SUCCESS(hRes, _T("创建接口IOpenRowset失败,错误码:%08x\n"), hRes); bRet = TRUE; __CLEAR_UP: OLEDB_SAFE_RELEASE(pIDBPromptInitialize); OLEDB_SAFE_RELEASE(pIDBInitialize); OLEDB_SAFE_RELEASE(pIDBCreateSession); return bRet; } int _tmain(int argc, TCHAR *argv[]) { DECLARE_BUFFER(); DECLARE_OLEDB_INTERFACE(IOpenRowset); DECLARE_OLEDB_INTERFACE(IDBInitialize); DECLARE_OLEDB_INTERFACE(IDBCreateCommand); DECLARE_OLEDB_INTERFACE(IAccessor); DECLARE_OLEDB_INTERFACE(IColumnsInfo); DECLARE_OLEDB_INTERFACE(ICommand); DECLARE_OLEDB_INTERFACE(ICommandProperties); DECLARE_OLEDB_INTERFACE(ICommandText); DECLARE_OLEDB_INTERFACE(IConvertType); DECLARE_OLEDB_INTERFACE(IColumnsRowset); DECLARE_OLEDB_INTERFACE(ICommandPersist); DECLARE_OLEDB_INTERFACE(ICommandPrepare); DECLARE_OLEDB_INTERFACE(ICommandWithParameters); DECLARE_OLEDB_INTERFACE(ISupportErrorInfo); DECLARE_OLEDB_INTERFACE(ICommandStream); CoInitialize(NULL); if (!CreateDBSession(pIOpenRowset)) { OLEDB_PRINTF(_T("调用函数CreateDBSession失败,程序即将推出\n")); return 0; } HRESULT hRes = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand); OLEDB_SUCCESS(hRes, _T("创建接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pIDBCreateCommand->CreateCommand(NULL, IID_IAccessor, (IUnknown**)&pIAccessor); OLEDB_SUCCESS(hRes, _T("创建接口IAccessor失败,错误码:%08x\n"), hRes); CHECK_OLEDB_INTERFACE(pIAccessor, IColumnsInfo); CHECK_OLEDB_INTERFACE(pIAccessor, ICommand); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandProperties); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandText); CHECK_OLEDB_INTERFACE(pIAccessor, IConvertType); CHECK_OLEDB_INTERFACE(pIAccessor, IColumnsRowset); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandPersist); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandPrepare); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandWithParameters); CHECK_OLEDB_INTERFACE(pIAccessor, ISupportErrorInfo); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandStream); __CLEAR_UP: OLEDB_SAFE_RELEASE(pIOpenRowset); OLEDB_SAFE_RELEASE(pIDBInitialize); OLEDB_SAFE_RELEASE(pIDBCreateCommand); OLEDB_SAFE_RELEASE(pIAccessor); OLEDB_SAFE_RELEASE(pIColumnsInfo); OLEDB_SAFE_RELEASE(pICommand); OLEDB_SAFE_RELEASE(pICommandProperties); OLEDB_SAFE_RELEASE(pICommandText); OLEDB_SAFE_RELEASE(pIConvertType); OLEDB_SAFE_RELEASE(pIColumnsRowset); OLEDB_SAFE_RELEASE(pICommandPersist); OLEDB_SAFE_RELEASE(pICommandPrepare); OLEDB_SAFE_RELEASE(pICommandWithParameters); OLEDB_SAFE_RELEASE(pISupportErrorInfo); OLEDB_SAFE_RELEASE(pICommandStream); CoUninitialize(); return 0; }在上述的例子中,首先定义了一个宏,用来判断是否支持对应的接口。同时定义了一个CreateDBSession方法来创建一个会话对象。在该函数中首先利用上一节的方法创建一个数据库连接,然后在数据源对象上调用QueryInterface来获取接口IDBCreateSeesion,接着利用IDBCreateSeesion接口的CreateSeesion方法创建一个会话对象,由于IDBCreateCommand接口并不是所有的数据源都支持,所以为了保证一定能创建会话对象,我们选择会话对象必须支持的接口IOpenRowset。在得到会话对象后,尝试创建IDBCreateSession对象,如果它不支持,那么程序直接退出。接着调用IDBCreateCommand接口来创建一个命令对象并尝试query命令对象的其他接口,得出数据源支持哪些接口。这个例子非常简单,只是为了演示如何创建会话对象和数据源对象罢了。本节例子代码
2018年01月21日
2 阅读
0 评论
0 点赞
2018-01-12
OLEDB数据源
数据源在oledb中指数据提供者,这里可以简单的理解为数据库程序。数据源对象代表数据库的一个连接,是需要创建的第一个对象。而数据源对象主要用于配置数据库连接的相关属性如连接数据库的用户名密码等等
2018年01月12日
5 阅读
0 评论
0 点赞
2018-01-06
Windows数据库编程接口简介
数据库是计算机中一种专门管理数据资源的系统,目前几乎所有软件都需要与数据库打交道(包括操作系统,比如Windows上的注册表其实也是一种数据库),有些软件更是以数据库为核心因此掌握数据库系统的使用方法以及数据库系统编程接口的使用方法是程序员非常重要的基本技能之一。所以我花了一定的时间学习了在Windows平台上使用COM接口的方式操作数据库。这段时间我会将自己学习过程中掌握的知识和其中的一些坑都发布出来,供个人参考,也方便他人学习
2018年01月06日
3 阅读
0 评论
0 点赞
1
2