OLEDB 枚举数据源
在之前的程序中,可以看到有这样一个功能,弹出一个对话框让用户选择需要连接的数据源,并输入用户名和密码,最后连接;而且在一些数据库管理软件中也提供这种功能——能够自己枚举出系统中存在的数据源,同时还可以枚举出能够连接的SQL Server数据库的实例。其实这个功能是OLEDB提供的高级功能之一。
枚举对象用于搜寻可用的数据源和其它的枚举对象(层次式),枚举出来的对象是一个树形结构。在程序中提供一个枚举对象就可以枚举里面的所有数据源,如果没有指定所使用的的上层枚举对象,则可以使用顶层枚举对象来枚举可用的OLEDB提供程序,其实我们使用枚举对象枚举数据源时它也是在注册表的对应位置进行搜索,所以我们可以直接利用操作注册表的方式来获取数据源对象,但是注册表中的信息过于复杂,而且系统对注册表的依赖比较严重,所以并不推荐使用这种方式。
枚举对象的原型如下:
CoType TEnumerator {
[mandatory] IParseDisplayName;
[mandatory] ISourcesRowset;
[optional] IDBInitialize;
[optional] IDBProperties;
[optional] ISupportErrorInfo;
}
顶层枚举对象的获取和遍历
要利用数据源枚举功能,第一个要获取的枚举对象就是顶层枚举对象。或者称之为根枚举器,根枚举器对象的CLSID是CLSID_OLEDB_ENUMNRATOR,顶层枚举对象可以使用标准的COM对象创建方式来创建,之后可以使用ISourceRowset对象的GetSourcesRowset,得到数据源组合成的结果集。接着可以根据行集中的行类型来判断是否是一个子枚举对象或者数据源对象。如果是子枚举对象,可以利用名字对象的方法创建一个新的子枚举对象,然后根据这个枚举对象来枚举其中的数据源对象。
一般来说这颗数结构只有两层。
OLEDB提供者结果集
在上面我们说可以根据结果集中的行类型来判断是否是一个子枚举对象或者数据源对象,那么怎么获取这个行类型呢?这里需要了解返回的行集的结构。
| 字段名称 | 类型 | 最大长度 | 含义描述 |
|---|---|---|---|
| SOURCES_NAME | DBTYPE_WSTR | 128 | 枚举对象或数据源名称 |
| SOURCES_PARSENAME | DBTYPE_WSTR | 128 | 可以传递给IParseDisplayName接口并得到一个moniker对象的字符串(枚举对象或数据源的moniker) |
| SOURCES_DESCRIPTION | DBTYPE_WSTR | 128 | 枚举对象或数据源的描述 |
| SOURCES_TYPE | DBTYPE_UI2 | 2(单位字节) | 枚举对象或实例的类型,有下列值: DBSOURCETYPE_BINDER (=4)- URL DBSOURCETYPE_DATASOURCE_MDP (=3) - OLAP提供者 DBSOURCETYPE_DATASOURCE_TDP (=1) - 关系型或表格型数据源 DBSOURCETYPE_ENUMERATOR (=2) - 子枚举对象 |
| SOURCES_ISPARENT | DBTYPE_BOOL | 2(单位字节) | 是否是父枚举器 |
在枚举时根据SOURCES_TYPE字段来判断是否是子枚举对象,如果是则使用第二列的数据获取子枚举器的对象。
如果根据名称创建子枚举器
这里需要使用IMoniker接口。
名字对象(moniker)的创建方法,是一种标准的以名字解析方法创建一个COM对象及接口的方法。相比于直接使用CoCreateInstance来说是一种更加高级的方法。
这是标准的COM 对象的创建方式,其原理就是通过一个全局唯一的名称在注册表中搜索得到对应的CLSID,然后根据ID调用CoCreateInstance来创建对象。具体搜索过程可以参考COM基础系列
在数据源枚举的应用中,先从ISourcesRowset对象中Query出IParseDisplayName接口,再调用该接口的ParseDisplayName方法传入上述表格中SOURCES_PARSENAME的值,得到IMoniker接口,最后调用全局函数BindMinker传递IMoniker接口指针并指定需要创建的接口ID。
具体例子
最后是一个具体的例子
这个例子中创建了一个MFC应用程序,最后效果类似于前面几个例子中的OLEDB的数据源选择对话框。
在例子中最主要的代码有两段:IDBSourceDlg对话框的EnumDataSource方法,和IDBConnectDlg方法Initialize。这两个分别用来枚举系统中存在的数据源对象和数据源对象中对应的数据库实例。当用户根据界面的提示选择了对应的选项后点击测试连接按钮来尝试连接。
这里展示的代码主要是3段,枚举数据源,枚举数据源中对应的数据库实例,以及根据选择的实例生成对应的数据源对象接口并测试连接。
void IDBSourceDlg::EnumDataSource(ISourcesRowset *pISourceRowset)
{
COM_DECLARE_INTERFACE(IRowset);
COM_DECLARE_INTERFACE(IAccessor);
COM_DECLARE_INTERFACE(IMoniker);
COM_DECLARE_INTERFACE(IParseDisplayName);
HROW *rgRows = NULL;
HACCESSOR hAccessor = NULL;
ULONG cRows = 10;
DWORD dwOffset = 0;
PVOID pData = NULL;
PVOID pCurrentData = NULL;
DBCOUNTITEM cRowsObtained = 0;
LPOLESTR lpParamName = OLESTR("");
//利用顶层枚举对象来枚举系统中存在的数据源
HRESULT hRes = pISourceRowset->GetSourcesRowset(NULL, IID_IRowset, 0, NULL, (IUnknown**)&pIRowset);
ISourcesRowset *pSubSourceRowset = NULL;
ULONG ulEaten = 0;
if (FAILED(hRes))
{
ComMessageBox(NULL, _T("OLEDB 错误"), MB_OK, __T("创建接口ISourcesRowset失败,错误码:%08x\n"), hRes);
goto __CLEAR_UP;
}
DBBINDING rgBinding[3] = {0}; //这里只关心我们需要的列,不需要获取所有的列
for (int i = 0; i < 3; i++)
{
rgBinding[i].bPrecision = 0;
rgBinding[i].bScale = 0;
rgBinding[i].cbMaxLen = 128 * sizeof(WCHAR);
rgBinding[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBinding[i].dwPart = DBPART_VALUE;
rgBinding[i].eParamIO = DBPARAMIO_NOTPARAM;
rgBinding[i].wType = DBTYPE_WSTR;
rgBinding[i].obLength = 0;
rgBinding[i].obStatus = 0;
rgBinding[i].obValue = dwOffset;
dwOffset += rgBinding[0].cbMaxLen;
}
rgBinding[0].iOrdinal = 3; //第三项是数据源或者枚举器的描述信息,用于显示
rgBinding[1].wType = DBTYPE_UI2;
rgBinding[1].iOrdinal = 4; //第四列是枚举出来的类型信息,用于判断是否需要递归
rgBinding[2].iOrdinal = 1; //第一列是枚举出来的类型信息,用于获取子枚举器
hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor);
if (FAILED(hRes))
{
ComMessageBox(NULL, _T("OLEDB 错误"), MB_OK, __T("查询接口pIAccessor失败,错误码:%08x\n"), hRes);
goto __CLEAR_UP;
}
hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 3, rgBinding, 0, &hAccessor, NULL);
if (FAILED(hRes))
{
ComMessageBox(NULL, _T("OLEDB 错误"), MB_OK, __T("创建访问器失败,错误码:%08x\n"), hRes);
goto __CLEAR_UP;
}
pData = MALLOC(dwOffset * cRows);
while (TRUE)
{
hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &cRowsObtained, &rgRows);
if(S_OK != hRes && cRowsObtained == 0)
{
break;
}
ZeroMemory(pData, dwOffset * cRows);
for (int i = 0; i < cRowsObtained; i++)
{
pCurrentData = (BYTE*)pData + dwOffset * i;
pIRowset->GetData(rgRows[i], hAccessor, pCurrentData);
DATASOURCE_ENUM_INFO dbei = {0}; //将枚举到的相关信息存储到对应的结构中
dbei.csSourceName = (LPCTSTR)((BYTE*)pCurrentData + rgBinding[2].obValue);
dbei.csDisplayName = (LPCTSTR)((BYTE*)pCurrentData + rgBinding[0].obValue);
dbei.dbTypeEnum = *(DBTYPEENUM*)((BYTE*)pCurrentData + rgBinding[1].obValue);
m_DataSourceList.AddString(dbei.csDisplayName); //显示数据源信息
g_DataSources.push_back(dbei);
}
pIRowset->ReleaseRows(cRowsObtained, rgRows, NULL, NULL, NULL);
}
pIAccessor->ReleaseAccessor(hAccessor, NULL);
__CLEAR_UP:
FREE(pData);
CoTaskMemFree(rgRows);
COM_SAFE_RELEASE(pIRowset);
COM_SAFE_RELEASE(pIAccessor);
}
void IDBConnectDlg::Initialize(const CStringW& csSelected)
{
BSTR lpOleName = NULL;
ULONG uEaten = 0;
for (vector<DATASOURCE_ENUM_INFO>::iterator it = g_DataSources.begin(); it != g_DataSources.end(); it++)
{
if (it->csDisplayName == csSelected)
{
lpOleName = it->csSourceName.AllocSysString();
}
}
COM_DECLARE_INTERFACE(ISourcesRowset);
COM_DECLARE_INTERFACE(IParseDisplayName);
COM_DECLARE_INTERFACE(IMoniker);
CoCreateInstance(CLSID_OLEDB_ENUMERATOR, NULL, CLSCTX_INPROC_SERVER, IID_ISourcesRowset, (void**)&pISourcesRowset);
pISourcesRowset->QueryInterface(IID_IParseDisplayName, (void**)&pIParseDisplayName);
pIParseDisplayName->ParseDisplayName(NULL, lpOleName, &uEaten, &pIMoniker);
if (lpOleName != NULL)
{
SysFreeString(lpOleName);
}
HRESULT hRes = BindMoniker(pIMoniker, 0, IID_ISourcesRowset, (void**)&m_pConnSourceRowset);
COM_SAFE_RELEASE(pIMoniker);
COM_SAFE_RELEASE(pIParseDisplayName);
if (FAILED(hRes))
{
COM_SAFE_RELEASE(m_pConnSourceRowset);
return;
}
COM_DECLARE_INTERFACE(IRowset)
hRes = m_pConnSourceRowset->GetSourcesRowset(NULL, IID_IRowset, 0, NULL, (IUnknown**)&pIRowset);
if (FAILED(hRes))
{
return;
}
DBBINDING rgBind[1] = {0};
rgBind[0].bPrecision = 0;
rgBind[0].bScale = 0;
rgBind[0].cbMaxLen = 128 * sizeof(WCHAR);
rgBind[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBind[0].dwPart = DBPART_VALUE;
rgBind[0].eParamIO = DBPARAMIO_NOTPARAM;
rgBind[0].iOrdinal = 2; //绑定第二项,用于展示数据源
rgBind[0].obLength = 0;
rgBind[0].obStatus = 0;
rgBind[0].obValue = 0;
rgBind[0].wType = DBTYPE_WSTR;
HACCESSOR hAccessor = NULL;
HROW *rghRows = NULL;
PVOID pData = NULL;
PVOID pCurrData = NULL;
ULONG cRows = 10;
COM_DECLARE_INTERFACE(IAccessor);
hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor);
if (FAILED(hRes))
{
COM_SAFE_RELEASE(pIRowset);
return;
}
hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, rgBind, 0, &hAccessor, NULL);
DBCOUNTITEM cRowsObtained;
if (FAILED(hRes))
{
COM_SAFE_RELEASE(pIRowset);
COM_SAFE_RELEASE(pIAccessor);
return;
}
pData = MALLOC(rgBind[0].cbMaxLen * cRows);
while (TRUE)
{
hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &cRowsObtained, &rghRows);
if (S_OK != hRes && cRowsObtained == 0)
{
break;
}
for (int i = 0; i < cRowsObtained; i++)
{
pCurrData = (BYTE*)pData + rgBind[0].cbMaxLen * i;
pIRowset->GetData(rghRows[i], hAccessor, pCurrData);
m_ComboDataSource.AddString((LPOLESTR)pCurrData);
}
pIRowset->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL);
}
FREE(pData);
pIAccessor->ReleaseAccessor(hAccessor, NULL);
}
void IDBConnectDlg::OnBnClickedBtnConnectTest()
{
// TODO: 在此添加控件通知处理程序代码
CStringW csSelected = _T("");
ULONG chEaten = 0;
m_ComboDataSource.GetWindowText(csSelected);
COM_DECLARE_INTERFACE(IParseDisplayName);
COM_DECLARE_INTERFACE(IMoniker);
if (m_pConnSourceRowset == NULL)
{
MessageBox(_T("连接失败"));
return;
}
HRESULT hRes = m_pConnSourceRowset->QueryInterface(IID_IParseDisplayName, (void**)&pIParseDisplayName);
if (FAILED(hRes))
{
return;
}
hRes = pIParseDisplayName->ParseDisplayName(NULL, csSelected.AllocSysString(), &chEaten, &pIMoniker);
COM_SAFE_RELEASE(pIParseDisplayName);
if (FAILED(hRes))
{
MessageBox(_T("连接失败"));
return;
}
COM_DECLARE_INTERFACE(IDBProperties);
hRes = BindMoniker(pIMoniker, 0, IID_IDBProperties, (void**)&pIDBProperties);
COM_SAFE_RELEASE(pIMoniker);
if (FAILED(hRes))
{
MessageBox(_T("连接失败"));
return;
}
//获取用户输入
CStringW csDB = _T("");
CStringW csUser = _T("");
CStringW csPasswd = _T("");
GetDlgItemText(IDC_EDIT_USERNAME, csUser);
GetDlgItemText(IDC_EDIT_PASSWORD, csPasswd);
GetDlgItemText(IDC_EDIT_DATABASE, csDB);
//设置链接属性
DBPROP connProp[5] = {0};
DBPROPSET connPropset[1] = {0};
connProp[0].colid = DB_NULLID;
connProp[0].dwOptions = DBPROPOPTIONS_REQUIRED;
connProp[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
connProp[0].vValue.vt = VT_BSTR;
connProp[0].vValue.bstrVal = csSelected.AllocSysString();
connProp[1].colid = DB_NULLID;
connProp[1].dwOptions = DBPROPOPTIONS_REQUIRED;
connProp[1].dwPropertyID = DBPROP_INIT_CATALOG;
connProp[1].vValue.vt = VT_BSTR;
connProp[1].vValue.bstrVal = csDB.AllocSysString();
connProp[2].colid = DB_NULLID;
connProp[2].dwOptions = DBPROPOPTIONS_REQUIRED;
connProp[2].dwPropertyID = DBPROP_AUTH_USERID;
connProp[2].vValue.vt = VT_BSTR;
connProp[2].vValue.bstrVal = csUser.AllocSysString();
connProp[3].colid = DB_NULLID;
connProp[3].dwOptions = DBPROPOPTIONS_REQUIRED;
connProp[3].dwPropertyID = DBPROP_AUTH_PASSWORD;
connProp[3].vValue.vt = VT_BSTR;
connProp[3].vValue.bstrVal = csPasswd.AllocSysString();
connPropset[0].cProperties = 4;
connPropset[0].guidPropertySet = DBPROPSET_DBINIT;
connPropset[0].rgProperties = connProp;
hRes = pIDBProperties->SetProperties(1, connPropset);
if (FAILED(hRes))
{
COM_SAFE_RELEASE(pIDBProperties);
return;
}
COM_DECLARE_INTERFACE(IDBInitialize);
hRes = pIDBProperties ->QueryInterface(IID_IDBInitialize, (void**)&pIDBInitialize);
COM_SAFE_RELEASE(pIDBProperties);
if (FAILED(hRes))
{
return;
}
hRes = pIDBInitialize->Initialize();
if (FAILED(hRes))
{
MessageBox(_T("连接失败"));
}else
{
MessageBox(_T("连接成功"));
}
pIDBInitialize->Uninitialize();
COM_SAFE_RELEASE(pIDBInitialize);
}
最后,这次由于是一个MFC的程序,涉及到的代码文件比较多,因此就不像之前那样以代码片段的方式方上来了,这次我将其以项目的方式放到GitHub上供大家参考。
项目地址
OLEDB 枚举数据源的更多相关文章
- OLEDB 数据变更通知
除了之前介绍的接口,OLEDB还定义了其他一些支持回调的接口,可以异步操作OLEDB对象或者得到一些重要的事件通知,从而使应用程序有机会进行一些必要的处理.其中较有用的就是结果集对象的变更通知接口.通 ...
- [WinForm] DataGridView 绑定 DT && ComboBox 列绑定 Dict
一 需求介绍 一般像枚举类型的数据,我们在数据库里存储着诸如(1.2.3.4-)或者("001"."002"."003"-)此类,但是界面 ...
- ADO.net操作数据库
今天整理硬盘,发现2年前开始着手开始学习C#的学习日记.陆续整理,一是自己的知识梳理梳理,二是希望与大家多多交流,能给初学者带来一定帮助,当然是更高兴的啦. 断线对象 另一类是与数据源无关的断线对象, ...
- 【转】ODBC、OLE DB、 ADO的区别
一.ODBC ODBC的由来 1992年Microsoft和Sybase.Digital共同制定了ODBC标准接口,以单一的ODBC API来存取各种不同的数据库.随后ODBC便获得了许多数据库厂商和 ...
- mbos之动态图表设计
前言 所谓,一图胜千言.人脑有80%的部分专门用于视觉处理.而随着数据时代的全面来临,我们自然有必要将数据转化为图形与图表. Mbos是一个快速,稳定的云端轻应用开发平台.帮助企业快速开发移动应用,加 ...
- BI之SSIS入门最新版Visual Studio调试技巧
简介 最近公司业务需要用到BI SSIS,SSIS是什么?"SSIS是Microsoft SQL Server Integration Services的简称,是生成高性能数据集成解决方案( ...
- SNF软件开发机器人平台2018-发展升级履历-零编程时代
一.SNF软件开发机器人产品白皮书 二.SNF开发机器人教程:链接:https://pan.baidu.com/s/1Qpomg11c_1b1NKY5P7e4Bw 密码:jwc3 三.SNF软件开发机 ...
- SNF软件开发机器人2018最新更新内容
SNF软件开发机器人从10月份到现在的更新升级情况如下: 1 表单 表单控件占多列时,宽度默认0,自适应宽度2 excel导出 部分excel导出方法移动到框架中,可通用获取3 生成代码 生成的代码, ...
- C# 通用树形数据结构
前言 树在图论中是一种重要的图,由于其自身的许多特殊性质,也是一种重要的计算机数据结构,在很多地方都有用.但是这些树大多都是作为其他应用的内部数据结构来使用.我们无法了解这些树的详细信息,而 .Net ...
随机推荐
- LAMP课程
LAMP课程 上次课回顾: ls -a:查看全部目录内容 若文件名以“.”开头,则认为是隐藏的文件. ls-l:可以直接用命令 ll命令:ls -l 的别名. ls -m:横向显示文件和目录 ls - ...
- 一个简单的Samba服务
上次给大家认识了下,搭建一个服务大概的一个认识. 这次给大家搭建一个Samba服务认识下. 项目准备: 虚拟机一个(Centos6.5版本) 项目目标: 进行samba最简单的配置 项目难度: ❤❤ ...
- 训练DCGAN(pytorch官网版本)
将pytorch官网的python代码当下来,然后下载好celeba数据集(百度网盘),在代码旁新建celeba文件夹,将解压后的img_align_celeba文件夹放进去,就可以运行代码了. 输出 ...
- vue框架组件之父子组件之间的通信
1.如图看解说: 你子标签要给我父标签传递信息,你总得有个触发机制告诉我这是怎么回事对吧 要不我怎么知道你要传数据给我呢!
- 003 Android常见错误汇总
1.installation failed with message invalid file 解决办法: <1>.点击工具栏上的Build中的Clean Project <2> ...
- Apache 去掉 www
1 用phpstudy的网友打开“其他选项菜单”- “配置文件”-httpd-conf.找到 #LoadModule rewrite_module modules/mod_rewrite.so 把这一 ...
- 【KMP】洛谷P2375 [NOI2014]动物园 题解
一开始的方向应该对了,但是没有想到合理的优化还是没写出来…… 题目描述 近日,园长发现动物园中好吃懒做的动物越来越多了.例如企鹅,只会卖萌向游客要吃的.为了整治动物园的不良风气,让动物们凭自己 ...
- 栈 - 20 Valid Parentheses, 150 Evaluate Reverse Polish Notation
class Solution { public: bool isValid(string s) { stack<char> st; ; i<s.size(); i++){ if(s[ ...
- vm 中安装 CentOS7
第三步:安装ISO文件 1.在vm下,文件,新建虚拟机 在我的机算机中,选中刚命名的CentOS7,右键,属性 2.开启虚拟机 PS: 打开虚拟机之后,提示了一个小错误,LZ根据错误提示,到BIOS里 ...
- rabbitmq 事务消息
事务消息主要用在发送方 在connection上加上事务属性, 发送方感知到本地事务执行失败, 需要通知broker将先前已经接收到的消息rollback,不要发给后面的消费者, 满足强一致性的要求 ...