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)- URLDBSOURCETYPE_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上供大家参考。 项目地址


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏python学习路

三、模型(一)

当我们的程序涉及到数据库相关操作时,我们一般都会这么做: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接数据库,并编写数据访问层代码 业务逻辑层去调...

41790
来自专栏农夫安全

注入学习之sqli-labs-6(第五次)

前言 上一次课讲解的是sql基于布尔型盲注,紧接着这节讲基于时间的盲注 布尔型盲注,是在我们判断网站是否存在注入的时候,网页不会暴漏错误信息,但会返回正确的页面...

38060
来自专栏Java3y

阅读SSM项目之scm

导入项目 项目是由eclipse来编写的,我使用的开发环境是Idea,那么就需要将eclipse项目导入进去Idea中。要想项目能够启动起来。是这样干的: 导入...

355110
来自专栏用户2442861的专栏

初学Redis(2)——用Redis作为Mysql数据库的缓存

http://blog.csdn.net/qtyl1988/article/details/39519951

19820
来自专栏nice_每一天

Elasticsearch JavaApi

 官网JavaApi地址:https://www.elastic.co/guide/en/elasticsearch/client/java-api/curre...

74140
来自专栏zhisheng

《从0到1学习Flink》—— 如何自定义 Data Source ?

在 《从0到1学习Flink》—— Data Source 介绍 文章中,我给大家介绍了 Flink Data Source 以及简短的介绍了一下自定义 Dat...

15540
来自专栏Jerry的SAP技术分享

ABAP和Java单例模式的攻防

然而我只需要将这个单例类JerrySingleton的构造函数通过反射设置成可以访问Accessible,然后就能通过反射调用该构造函数,进而生成新的对象实例。...

18240
来自专栏跟着阿笨一起玩NET

【C#】Entity Framework 增删改查和事务操作

  方法二:方法一中每次都需要对所有字段进行修改,效率低,而且麻烦,下面介绍修改部分字段

14510
来自专栏机器人网

中英文对照,瞬间理解西门子PLC指令

指令( 英文全称意思 ) :指令含义 1、LD ( Load 装载 ) :动合触点 2、LDN ( Load Not 不装载 ) : 动断触点 3、A ( A...

36270
来自专栏一个会写诗的程序员的博客

spring boot 集成mybatis 注解版查询

spring boot 和 mybatis已经正常集成,在使用查询时使用的是注解,(项目没有任何XML文件)

10810

扫码关注云+社区

领取腾讯云代金券