上次将OLEDB的所有内容基本上都说完了,从之前的示例上来看OLEDB中有许多变量的定义,什么结果集对象、session对象、命令对象,还有各种缓冲等等,总体上来说直接使用OLEDB写程序很麻烦,用很大的代码量带来的仅仅只是简单的功能。还要考虑各种缓冲的释放,各种对象的关闭,程序员的大量精力都浪费在无用的事情上,针对这些情况微软在OLEDB上提供了两种封装方式,一种是将其封装在ATL模板库中,一种是使用ActiveX控件来进行封装称之为ADO,这次主要写的是这两种方式

ATL 模板中的OLEDB

由于ATL模板是开源的,这种方式封装简洁,调试简易(毕竟源代码都给你了),各个模块相对独立,但是它的缺点很明显就是使用门槛相对较高,只有对C++中的模板十分熟悉的开发人员才能使用的得心应手。

ATL中的OLEDB主要有两大模块,提供者模块和消费者模块,顾名思义,提供者模块是数据库的开发人员使用的,它主要使用这个模块实现OLEDB中的接口,对外提供相应的数据库服务;消费者模块就是使用OLEDB在程序中操作数据库。这里主要说的是消费者模块

ATL主要封装的类

ATL针对OLEDB封装的主要有这么几个重要的类:

数据库对象

  • CDataConnection 数据源连接类主要实现的是数据库的连接相关的功能,根据这个可以猜测出来它实际上封装的是OLEDB中的数据源对象和会话对象
  • CDataSource:数据源对象
  • CEnumerator: 架构结果集对象,主要用来查询数据库的相关信息,比如数据库中的表结构等信息
  • CSession: 会话对象

访问器对象:

  • CAccessor: 常规的访问器对象
  • CAccessorBase: 访问器对象的基类
  • CDynamicAccessor:动态绑定的访问器
  • CDynamicParamterAccessor:参数绑定的访问器,从之前博文的内容来看它应该是进行参数化查询等操作时使用的对象
  • CDynamicStringAccessor:这个一般是要将查询结果显示为字符串时使用,它负责将数据库中的数据转化为字符串

ALT中针对OLEDB的封装在头文件atldbcli.h中,在项目中只要包含它就行了

模板的使用

静态绑定

针对静态绑定,VS提供了很好的向导程序帮助我们生成对应的类,方便了开发,使用的基本步骤如下:

  1. 在项目上右键,选择添加类
  2. 在类选择框中点击ATL并选择其中的ATL OLEDB使用者

  3. 选择对应的数据源、数据库表和需要对数据库进行的操作

注意如果要对数据库表进行增删改查等操作,一定要选这里的表选项

4. 点击数据源配置数据源连接的相关属性,最后点击完成。

最终会在项目中生成对应的头文件

这是最终生成的完整代码

class Caa26Accessor
{
public:
//value
LONG m_aac031;
TCHAR m_aaa146[51];
LONG m_aaa147;
LONG m_aaa148;
//status
DBSTATUS m_dwaac031Status;
DBSTATUS m_dwaaa146Status;
DBSTATUS m_dwaaa147Status;
DBSTATUS m_dwaaa148Status;
//lenth
DBLENGTH m_dwaac031Length;
DBLENGTH m_dwaaa146Length;
DBLENGTH m_dwaaa147Length;
DBLENGTH m_dwaaa148Length; void GetRowsetProperties(CDBPropSet* pPropSet)
{
pPropSet->AddProperty(DBPROP_CANFETCHBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
pPropSet->AddProperty(DBPROP_CANSCROLLBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
pPropSet->AddProperty(DBPROP_IRowsetChange, true, DBPROPOPTIONS_OPTIONAL);
pPropSet->AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);
} HRESULT OpenDataSource()
{
CDataSource _db;
HRESULT hr;
hr = _db.OpenFromInitializationString(L"Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa; Password=XXXXXX;Initial Catalog=study;Data Source=XXXX;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LIU-PC;Use Encryption for Data=False;Tag with column collation when possible=False");
if (FAILED(hr))
{
#ifdef _DEBUG
AtlTraceErrorRecords(hr);
#endif
return hr;
}
return m_session.Open(_db);
} void CloseDataSource()
{
m_session.Close();
} operator const CSession&()
{
return m_session;
} CSession m_session; DEFINE_COMMAND_EX(Caa26Accessor, L" \
SELECT \
aac031, \
aaa146, \
aaa147, \
aaa148 \
FROM dbo.aa26") BEGIN_COLUMN_MAP(Caa26Accessor)
COLUMN_ENTRY_LENGTH_STATUS(1, m_aac031, m_dwaac031Length, m_dwaac031Status)
COLUMN_ENTRY_LENGTH_STATUS(2, m_aaa146, m_dwaaa146Length, m_dwaaa146Status)
COLUMN_ENTRY_LENGTH_STATUS(3, m_aaa147, m_dwaaa147Length, m_dwaaa147Status)
COLUMN_ENTRY_LENGTH_STATUS(4, m_aaa148, m_dwaaa148Length, m_dwaaa148Status)
END_COLUMN_MAP()
}; class Caa26 : public CCommand<CAccessor<Caa26Accessor> >
{
public:
HRESULT OpenAll()
{
HRESULT hr;
hr = OpenDataSource();
if (FAILED(hr))
return hr;
__if_exists(GetRowsetProperties)
{
CDBPropSet propset(DBPROPSET_ROWSET);
__if_exists(HasBookmark)
{
if( HasBookmark() )
propset.AddProperty(DBPROP_IRowsetLocate, true);
}
GetRowsetProperties(&propset);
return OpenRowset(&propset);
}
__if_not_exists(GetRowsetProperties)
{
__if_exists(HasBookmark)
{
if( HasBookmark() )
{
CDBPropSet propset(DBPROPSET_ROWSET);
propset.AddProperty(DBPROP_IRowsetLocate, true);
return OpenRowset(&propset);
}
}
}
return OpenRowset();
} HRESULT OpenRowset(DBPROPSET *pPropSet = NULL)
{
HRESULT hr = Open(m_session, NULL, pPropSet);
#ifdef _DEBUG
if(FAILED(hr))
AtlTraceErrorRecords(hr);
#endif
return hr;
} void CloseAll()
{
Close();
ReleaseCommand();
CloseDataSource();
}
};

从名字上来看Caa26Accessor主要是作为一个访问器,其实它的功能也是与访问器相关的,比如创建访问器和数据绑定都在最后这个映射中。而后面的Caa26类主要是用来执行sql语句并根据上面的访问器类来解析数据,其实我们使用上主要使用后面这个类,这些代码都很简单,有之前的OLEDB基础很容易就能理解它们,这里就不再在这块进行展开了

int _tmain(int argc, TCHAR *argv)
{
CoInitialize(NULL);
Caa26 aa26;
HRESULT hRes = aa26.OpenDataSource();
if (FAILED(hRes))
{
aa26.CloseAll();
CoUninitialize();
return -1;
} hRes = aa26.OpenRowset();
if (FAILED(hRes))
{
aa26.CloseAll();
CoUninitialize();
return -1;
} hRes = aa26.MoveNext();
COM_USEPRINTF();
do
{
COM_PRINTF(_T("|%-30u|%-30s|%-30u|%-30u|\n"), aa26.m_aac031, aa26.m_aaa146, aa26.m_aaa147, aa26.m_aaa148);
hRes = aa26.MoveNext();
} while (S_OK == hRes); aa26.CloseAll();
CoUninitialize();
return 0;
}

动态绑定

动态绑定主要是使用模板对相关的类进行拼装,所以这里需要关于模板的相关知识,只有掌握了这些才能正确的拼接出合适的类。

一般需要拼接的是这样几个类

  • 结果集类,在结果集类的模板中填入对应的访问器类,表示该结果集将使用对应的访问器进行解析。访问器类可以系统预定义的,也向静态绑定那样自定义。
  • Command类,在命令对象类的模板位置填入与命令相关的类,也就是执行命令生成的结果集、以及解析结果集所用的访问器,之后就主要使用Command类来进行数据库的相关操作了

    下面是一个使用的示例
typedef CCommand<CDynamicAccessor, CRowset, CMultipleResults> CComRowset;
typedef CTable<CDynamicAccessor, CRowset> CComTable;
//将所有绑定的数据类型转化为字符串
typedef CTable<CDynamicStringAccessor, CRowset> CComTableString; int _tmain(int argc, TCHAR *argv[])
{
CoInitialize(NULL);
COM_USEPRINTF();
//连接数据库,创建session对象
CDataSource db;
db.Open();
CSession session;
session.Open(db); //打开数据库表
CComTableString table;
table.Open(session, OLESTR("T_DecimalDemo"));
HRESULT hRes = table.MoveFirst();
if (FAILED(hRes))
{
COM_PRINTF(_T("表中没有数据,退出程序\n"));
goto __CLEAN_UP;
}
do
{
//这里传入的参数是列的序号,注意一下,由于有第0列的行需要存在,所以真正的数据是从第一列开始的
COM_PRINTF(_T("|%-10s|%-20s|%-20s|%-20s|\n"), table.GetString(1), table.GetString(2), table.GetString(3), table.GetString(4));
hRes = table.MoveNext();
} while (S_OK == hRes); __CLEAN_UP:
CoUninitialize();
return 0;
}

在上面的代码中我们定义了两个模板类,Ctable和CCommand类,没有发现需要的访问器类,查看CTable类可以发现它是继承于CAccessorRowset,而CAccessorRowset继承于TAccessor和 TRowset,也就是说它提供了访问器的相关功能

而且它还可以使用OpenRowset方法不执行SQL直接打开数据表,因此在这里我们选择使用它

在CTable的模板中填入CDynamicStringAccessor表示将会把得到的结果集中的数据转化为字符串。

在使用上先使用CDataSource类的Open方法打开数据库连接,然后调用CTable的Open打开数据表,接着调用CTable的MoveFirst的方法将行句柄移动到首行。

接着在循环中调用table的GetString方法得到各个字段的字符串值,并调用MoveNext方法移动到下一行

其实在代码中并没有使用CCommand类,这是由于这里只是简单的使用直接打开数据表的方式,而并没有执行SQL语句,因此不需要它,在这里定义它只是简单的展示一下

ADO

ATL针对OLEDB封装的确是方便了不少,但是对于像我这种将C++简单的作为带对象的C来看的人来说,它使用模板实在是太不友好了,说实话现在我现在对模板的认识实在太少,在代码中我也尽量避免使用模板。所以在我看来使用ATL还不如自己根据项目封装一套。

好在微软实在太为开发者着想了,又提供了ADO这种针对ActiveX的封装方式。

要使用ADO组件需要先导入,导入的语句如下:

#import "C:\\Program Files\\Common Files\\System\\ado\\msado15.dll" no_namespace rename("EOF", "EndOfFile")

这个路径一般是不会变化的,而EOF在C++中一般是用在文件中的,所以这里将它rename一下

ADO中的主要对象和接口有:

  • Connect :数据库的连接对象,类似于OLEDB中的数据源对象和session对象
  • Command:命令对象,用来执行sql语句,类似于OLEDB中的Command对象
  • Recordset: 记录集对象,执行SQL语句返回的结果,类似于OLEDB中的结果集对象
  • Record: 数据记录对象,一般都是从Recordset中取得,就好像OLEDB中从结果集对象通过访问器获取到具体的数据一样
  • Field:记录中的一个字段,可以简单的看做就是一个表字段的值,一般一个记录集中有多条记录,而一条记录中有个Field对象
  • Parameter:参数对象,一般用于参数化查询或者调用存储过程
  • Property:属性,与之前OLEDB中的属性对应

在ADO中大量使用智能指针,所谓的智能指针是它的生命周期结束后会自动析构它所指向的对象,同时也封装了一些常见指针操作,虽然它是这个对象但是它的使用上与普通的指针基本上相同。ADO中的智能指针对象一般是在类名后加上Ptr。比如Connect对象的智能指针对象是_ConnectPtr

智能指针有利也有弊,有利的地方在于它能够自动管理内存,不需要程序员进行额外的释放操作,而且它在使用上就像普通的指针,相比于使用类的普通指针更为方便,不利的地方在于为了方便它的使用一般都经过了大量的重载,因此很多地方表面上看是一个普通的寻址操作,而实际上却是一个函数调用,这样就降低了性能。所以在特别强调性能的场合要避免使用智能指针。

在使用上,一般经过这样几个步骤:

  1. 定义数据库连接的Connect对象
  2. 调用Connect对象的Open方法连接数据库,这里使用的连接字串的方式
  3. 创建Command对象并调用对象Execute方法执行SQL,并获取对应的记录集。这里执行SQL语句也可以使用Recordset对象的Open方法。
  4. 循环调用Recordse对象的MoveNext不断取出对应行的行记录

下面是一个使用的简单例子

#import "C:\\Program Files\\Common Files\\System\\ado\\msado15.dll" no_namespace rename("EOF", "EndOfFile")
int _tmain(int argc, TCHAR *argv[])
{
CoInitialize(NULL);
_ConnectionPtr conn;
_RecordsetPtr rowset;
_bstr_t bstrConn = _T("Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Password = 123456;Initial Catalog=Study;Data Source=LIU-PC\\SQLEXPRESS;");
conn.CreateInstance(_T("ADODB.Connection")); conn->Open(bstrConn, _T("sa"), _T("123456"), adModeUnknown);
if (conn->State)
{
COM_PRINTF(_T("连接到数据源成功\n"));
}else
{
COM_PRINTF(_T("连接到数据源失败\n"));
return 0;
} rowset.CreateInstance(__uuidof(Recordset));
rowset->Open(_T("select * from aa26;"), conn.GetInterfacePtr(), adOpenStatic, adLockOptimistic, adCmdText);
while (!rowset->EndOfFile)
{
COM_PRINTF(_T("|%-30u|%-30s|%-30u|%-30u|\n"),
rowset->Fields->GetItem(_T("aac031"))->Value.intVal,
rowset->Fields->GetItem(_T("aaa146"))->Value.bstrVal,
rowset->Fields->GetItem(_T("aaa147"))->Value.llVal,
rowset->Fields->GetItem(_T("aaa148"))->Value.llVal
);
rowset->MoveNext();
}
CoUninitialize();
return 0;
}

ADO与OLEDB混合编程

ADO相比较OLEDB来说确实方便了不少,但是它也有它的问题,比如它是封装的ActiveX控件,从效率上肯定比不上OLEDB,而且ADO中记录集是一次性将结果中的所有数据加载到内存中,如果数据表比教大时这种方式很吃内存。而OLEDB是每次调用GetNextRow时加载一条记录到内存(其实根据之前的代码可以知道它加载的时机,加载的大小是可以控制的),它相对来说比教灵活。

其实上述问题使用二者的混合编程就可以很好的解决,在处理结果集时使用OLEDB,而在其他操作时使用ADO这样既保留了ADO的简洁性也使用了OLEDB灵活管理结果集内存的能力。

在ADO中,可以通过_Recordset查询出ADORecordsetConstruction接口,这个接口提供了将记录集转化为OLEDB中结果集,以及将结果集转化为Recordset对象的能力

下面是一个简单的例子

CoInitialize(NULL);
try
{
_bstr_t bsCnct(_T("Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Password = 123456;Initial Catalog=Study;Data Source=LIU-PC\\SQLEXPRESS;"));
_RecordsetPtr Rowset(__uuidof(Recordset)); Rowset->Open(_T("select * from aa26;")
,bsCnct,adOpenStatic,adLockOptimistic,adCmdText); //获取IRowset接口指针
ADORecordsetConstruction* padoRecordsetConstruct = NULL;
Rowset->QueryInterface(__uuidof(ADORecordsetConstruction),
(void **) &padoRecordsetConstruct);
IRowset * pIRowset = NULL;
padoRecordsetConstruct->get_Rowset((IUnknown **)&pIRowset);
padoRecordsetConstruct->Release(); DisplayRowSet(pIRowset); pIRowset->Release(); GRS_PRINTF(_T("\n\n显示第二个结果集:\n")); //使用OLEDB方法打开一个结果集
IOpenRowset* pIOpenRowset = NULL;
TCHAR* pszTableName = _T("T_State");
DBID TableID = {}; CreateDBSession(pIOpenRowset); TableID.eKind = DBKIND_NAME;
TableID.uName.pwszName = (LPOLESTR)pszTableName; HRESULT hr = pIOpenRowset->OpenRowset(NULL,&TableID,NULL,IID_IRowset,0,NULL,(IUnknown**)&pIRowset);
if(FAILED(hr))
{
_com_raise_error(hr);
}
//创建一个新的ADO记录集对象
_RecordsetPtr Rowset2(__uuidof(Recordset));
Rowset2->QueryInterface(__uuidof(ADORecordsetConstruction),
(void **) &padoRecordsetConstruct);
//将OLEDB的结果集放置到ADO记录集对象中
padoRecordsetConstruct->put_Rowset(pIRowset);
ULONG ulRow = 0; while(!Rowset2->EndOfFile)
{
COM_PRINTF(_T("|%10u|%10s|%-40s|\n")
,++ulRow
,Rowset2->Fields->GetItem("K_StateCode")->Value.bstrVal
,Rowset2->Fields->GetItem("F_StateName")->Value.bstrVal); Rowset2->MoveNext();
}
}
catch(_com_error & e)
{
COM_PRINTF(_T("发生错误:\n Source : %s \n Description : %s \n")
,(LPCTSTR)e.Source(),(LPCTSTR)e.Description());
} _tsystem(_T("PAUSE"));
CoUninitialize();
return 0;

这次就不再放上整体例子的链接了,有之前的基础应该很容易看懂这些,而且这次代码比较短,基本上将所有代码全粘贴了过来。


ATL模板库中的OLEDB与ADO的更多相关文章

  1. 8、泛型程序设计与c++标准模板库2、c++标准模板库中的容器

    顺序容器类以逻辑线性排列方式存储元素,在这些容器类型中的元素在逻辑上被认为是连续的存储空间中存储的.顺序容器可用于存储线性群体. 在关联容器类中,元素的存储和检索基于关键字和元素与其他元素之间的关系, ...

  2. STL(标准模板库) 中栈(stack)的使用方法

    STL 中栈的使用方法(stack) 基本操作: stack.push(x)  将x加入栈stack中,即入栈操作 stack.pop()  出栈操作(删除栈顶),只是出栈,没有返回值 stack.t ...

  3. c++之旅:模板库中的容器

    容器 C++中的容器包括array, vector, list,map,set 数组 array不可变长,创建时其大小就固定了,array中可以存储各种数据类型包括对象,不过array是在栈上分配的, ...

  4. 标准模板库中的优先队列(priority_queue)

    //C++数据结构与算法(第4版) Adam Drozdek 著  徐丹  吴伟敏<<清华大学出版社>> #include<queue> priority_queu ...

  5. 标准模板库中的队列(queue)

    //C++数据结构与算法(第4版) Adam Drozdek 著  徐丹  吴伟敏<<清华大学出版社>> 队列容器默认由deque实现,用户也可以选择list容器来实现.如果用 ...

  6. 标准模板库中的栈(stack)

    ////C++数据结构与算法(第4版) Adam Drozdek 著  徐丹  吴伟敏<<清华大学出版社>> STL中的通用栈类实现为容器适配器:使用以指定方式运行的容器.栈容 ...

  7. 标准模板库中的链表(list)

    //C++数据结构与算法(第4版) Adam Drozdek 著  徐丹  吴伟敏<<清华大学出版社>> 头文件:include<list> list() 创建一个 ...

  8. 标准模板库中的向量(vector)

    //C++数据结构与算法(第4版) Adam Drozdek 著  徐丹  吴伟敏<<清华大学出版社>> 头文件:#include<vector> 向量是最简单的S ...

  9. 8、泛型程序设计与c++标准模板库4.标准c++库中的算法

    标准c++算法是通过迭代器和模板来实现的,其实算法本身就是一种函数模板. 算法从迭代器那里获得一个元素,而迭代器则知道一个元素在容器中的什么位置.迭代器查找元素的位置并将这些信息提供给算法以便算法能够 ...

随机推荐

  1. HDU6336-2018ACM暑假多校联合训练4-1005-Problem E. Matrix from Arrays-前缀和

    题意是给了一种矩阵的生成方式 让你求两个左边之间的矩阵里面的数加起来的和(不是求矩阵的值) 没看标程之前硬撸写了160行 用了前缀和以后代码量缩短到原来的1/3 根据规律可以推导出这个矩阵是在不断重复 ...

  2. VS2017+DLib_19.17详细配置教程

      最近学校布置了一个关于图像融合的作业,于是想利用Learn OpenCV 网站上的Face Morph 教程来设计一个人脸融合的Gif图,但是程序中需要用到DLib库,光是配置这个库就花费了我半天 ...

  3. NetworkX初相识

    听说NetworkX是一个很牛的复杂网络研究的工具,就来试一下吧. import networkx as nx G= nx.Graph()#建立一个空白的图 G.add_node("node ...

  4. ThinkPHP U方法

    方法1: {:U('User/Booking/bookingdetails')} 方法2: {:U('User/Booking/bookingdetails')}"+"&a ...

  5. 利用EFCore 封装Repository(可扩展不同数据的sql操作)

    本篇是对EFCore 进行下封装并实现基本的增删改查的同步异步方法及针对不同数据库的批量插入.sql语句直接操作数据库: 一. 先定义基础仓储接口IRepository public interfac ...

  6. springMVC下载功能

    前台页面 <a href="download">下载</a> 后台代码 /** * 文件下载 * @param request * @return * @t ...

  7. C#异步编程之基于任务的异步模式

    http://www.cnblogs.com/afei-24/p/6757361.html该文讲了基于任务的编程,这里再详细介绍一下.一.延续任务 private async static void ...

  8. Oracle的pipelined函数实现高性能大数据处理

    从Oracle 8开始,我们就可以从一个collection类型的数据集合中查询出数据,这个集合称之为"虚拟表".它的方法是"SELECT FROM TABLE(CAST ...

  9. PIE SDK内存栅格数据的创建

    1. 功能简介 目前在地理信息领域中数据包括矢量和栅格两种数据组织形式.每一种数据有不同的数据格式,目前PIE SDK支持多种数据格式的数据创建,下面对内存栅格数据格式的数据创建功能进行介绍. 2.  ...

  10. Quartz.NET 作业调度使用

    Quartz.NET的使用方法有很多,今天使用Quartz.NET3.0.6的时候发现和2.0版本的语法不太一样,百度上找了一圈也没有找到解决办法 后来在GitHub上下载源代码解决了 实现每隔10s ...