65.9K
CodeProject 正在变化。 阅读更多。
Home

ATL OLE DB 消费者模板架构

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (6投票s)

2005年2月6日

CPOL

10分钟阅读

viewsIcon

55109

downloadIcon

292

使用 ATL OLE DB 消费者模板探索 SQL CE 2.0 架构信息。

引言

这是第三篇关于在 Pocket PC 上使用 ATL OLE DB 消费者模板的文章。在第一篇文章中,我描述了如何将 ATL OLE DB 消费者模板改编并用于 Pocket PC 平台。第二篇文章第二篇文章展示了如何管理大数据类型,也称为 BLOB。这两篇文章提供的都是管理 SQL Server CE 2.0 数据库中数据的示例,是本文材料的基础。

在这里,我将向您展示如何探索数据库架构信息,特别是针对 SQL CE 2.0 数据库。就像您在第一篇文章中所见,我将不得不改编 ATL 分发文件之一,以便它能在 Windows CE 编译器上正常工作。

启用

如果您还记得我的第一篇文章,我不得不创建一个自定义版本的 atldbcli.h 头文件,并重新排列 stdafx.h 的布局,以便我们可以将 ATL OLE DB 消费者模板与 MFC 一起编译。现在,还有更多相同的工作要做,但现在我们将不得不专注于 atldbsch.h 头文件。

  • atldbsch.h 头文件

    此文件包含许多类声明,可帮助我们枚举数据库架构信息。大部分信息以行集的形式提供,可以像表一样访问。这些信息包括表、列、索引、约束等的列表。有趣的是,正如您将在本文的讨论中看到的,我们将使用混合技术来访问 SQL CE 2.0 架构信息。这些行集起着非常重要的作用,但正如我们将在本文中看到的,它们还不够。

    atldbsch.h 中有许多有趣的类,其中最值得注意的是 CRestrictions 类模板。该模板用于使用辅助访问器类来实例化架构行集类。这些类中的每一个都定义了底层数据结构的结构。例如,要枚举数据库中的所有表,您可以使用 CTables 类,它不过是一个 typedef(您可以在 atldbsch.h 文件的末尾找到所有可用的 typedef)。

    typedef CRestrictions<CAccessor<CTableInfo>,4,&DBSCHEMA_TABLES> CTables;

    要理解打开和浏览此类行集时可用的信息,您必须查看 CTableInfo 类的定义。为了方便我们的讨论,我在此处重述它

    class CTableInfo
    {
    public:
    // Constructors
        CTableInfo()
        {
            memset(this, 0, sizeof(*this));
        }
    
    // Attributes
        TCHAR   m_szCatalog[129];
        TCHAR   m_szSchema[129];
        TCHAR   m_szName[129];
        TCHAR   m_szType[129];
        GUID    m_guidTable;
        TCHAR   m_szDescription[129];
    
    // Binding Map
    BEGIN_COLUMN_MAP(CTableInfo)
        COLUMN_ENTRY(1, m_szCatalog)
        COLUMN_ENTRY(2, m_szSchema)
        COLUMN_ENTRY(3, m_szName)
        COLUMN_ENTRY(4, m_szType)
        COLUMN_ENTRY(5, m_guidTable)
        COLUMN_ENTRY(6, m_szDescription)
    END_COLUMN_MAP()
    };

    正如您所见,这很简单:记录描述和访问器列映射。这些类中的大多数将按预期运行,有些会失败,而有些则完全不完整。因此,就像我们之前所做的一样,我们必须更改此文件以使其正常工作。让我们看看如何。

  • 修改 atldbsch.h 头文件

    开始之前,请将 atldbsch.h(位于 ATL include 文件夹中)复制到 atldbsch_ce.h。这将允许您在不修改 SDK 分发版的情况下进行必要的更改。此外,我建议您将此新文件放在不同的目录中,以便在需要重新安装开发工具时不会被删除。

    我们需要进行的更改是纠正布尔值的访问方式。布尔值使用 VARIANT_BOOL 类型声明,它不过是一个具有特定语义的无符号短整型:false 表示为 0,true 表示为 ~0。这与布尔常数的习俗定义略有不同,当数据由访问器绑定时会出现问题。解决方案实际上非常简单,它涉及替换访问器列绑定宏。让我们看一个例子。

    首先找到 CColumnsInfo 类。正如您所见,有两个布尔值:m_bColumnHasDefaultm_bIsNullable。如果您往下看,您将看到这些值的绑定条目

    COLUMN_ENTRY(8, m_bColumnHasDefault)

    三行之后,您将找到另一个条目

    COLUMN_ENTRY(11, m_bIsNullable)

    这就是我们需要更改的内容,以使所有布尔值绑定都能正常工作,而且这是一个简单的更改。例如,我们将第一个绑定更改为

    COLUMN_ENTRY_TYPE_SIZE(8, DBTYPE_BOOL, 2, m_bColumnHasDefault)

    此列绑定宏现在明确表示我们正在绑定一个长度为两个字节的布尔值。现在,您的任务是在文件中替换所有此类出现的实例。

    最后,您应该修改 stdafx.h 文件,并将 atldbsch_ce.h 包含在 atldbcli_ce.h 之后。

    ...
    #include "atldbcli_ce.h"
    #include "atldbsch_ce.h"
    ...

Using

OLE DB 允许您枚举数据库中的所有内容,从可用的服务器到表列的非常具体的属性。在本文中,我们仅考虑枚举 SQL CE 2.0 架构,因此有一些限制。

要查看支持哪些架构行集,请参阅 SQL CE 2.0 在线文档。正如您所见,我们缺少一些相关的行集。让我们看看如何在没有它们的情况下进行。

  • 枚举服务器

    枚举服务器不是一个选项,因为每个设备只能有一个服务器,所以服务器要么已安装,要么未安装。要确定服务器是否已安装,您可以使用以下代码

    #include <ca_mergex20.h>
    
    bool IsSqlCeInstalled()
    {
        ISSCEEngine* pEngine;
        HRESULT      hr;
    
        ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    
        hr = CoCreateInstance(CLSID_Engine, 
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              IID_ISSCEEngine, 
                              (LPVOID *) &pEngine);
    
        if(SUCCEEDED(hr))
            pEngine->Release();
    
        ::CoUninitialize();
    
        return SUCCEEDED(hr);
    }

    不要忘记在项目的库列表中(项目设置/链接选项卡)包含 ca_mergex20.lib

  • 枚举数据库

    SQL CE 2.0 数据库存储在单个文件中,并且不像 SQL Server 2000 那样有中央数据库引用,但像 Access 一样。因此,枚举可用数据库只是使用 FindFirstFileFindNextFile 函数的一个简单方法,使用“*.sdf”作为搜索通配符。

  • 枚举表

    数据库表使用 CTables 类进行枚举。以下代码假定您已使用第一篇文章中描述的技术打开了数据库。

    HRESULT hr;
    CTables table;
    
    hr = table.Open(session, NULL, NULL, NULL, _T("TABLE"));

    变量 session 持有一个打开的 CSession 对象。最后一个参数指示 OLE DB 提供程序枚举用户表,跳过系统表。要查看此参数的所有支持值,请参阅 OLE DB 程序员参考中的“TABLES Rowset”主题。

    枚举表就像在表或查询中一样简单

    if(SUCCEEDED(hr))
    {
        for(hr = table.MoveFirst(); hr == S_OK; hr = table.MoveNext())
        {
        // The table name is table.m_szName
        }
    }

    在表枚举结束时,不要忘记关闭 CTables 对象

    table.Close();

    很简单,不是吗?现在,让我们看看表中列的枚举。

  • 枚举表列

    枚举表中的列也很简单。SQL CE 2.0 OLE DB 提供程序支持 COLUMNS 行集,该行集由 ATL 通过 CColumns 类实现。逻辑相同

    HRESULT  hr;
    CColumns column;
    
    hr = column.Open(session, NULL, NULL, _T("TableName"));

    此代码打开指定表的列行集进行枚举。枚举的执行方式与表一样:使用底层游标。

    不幸的是,此架构行集不会暴露您可能需要的所有列信息(请参阅 atldbsch_ce.h 中的 CColumnsInfo 类)。正如您所看到的,一些最有趣的信息缺失了,比如列是否为 IDENTITY 列,其种子值和增量值。不幸的是,无法使用架构行集获取此信息。我们必须另辟蹊径。

ITableCreation 接口

要访问扩展的列架构信息,我们必须使用一个特定的 OLE DB 接口:ITableCreation。如果您查看接口文档,您会发现它只支持一个方法:GetTableDefinition。这是一个多么强大的方法!只需一次调用,您就可以获得所有列的名称、属性和约束。

为了说明如何使用此方法,请参考示例应用程序中的 CDbTable 类和 LoadSchema 方法。此类封装了整个表的架构信息,包括列、约束和索引。

首先,我们开始声明所需的变量

HRESULT                 hr;
CComPtr<ITableCreation> spTableCreation;

现在,我们实例化一个指向 ITableCreation 接口的指针

hr = session.m_spOpenRowset->QueryInterface(IID_ITableCreation,
                                            (void**)&spTableCreation);

现在,我们可以访问列架构

if(SUCCEEDED(hr))
{
    DBID              idTable;
    ULONG             nColumns,
                      nProperties,
                      nConstraints;
    DBCOLUMNDESC*     pColDesc;
    DBPROPSET*        pPropertySet;
    DBCONSTRAINTDESC* pConstraints;
    OLECHAR*          pStrings;
    TCHAR             szName[129];

    hr = spTableCreation->GetTableDefinition(&idTable,
                                             &nColumns,
                                             &pColDesc,
                                             &nProperties,
                                             &pPropertySet,
                                             &nConstraints,
                                             &pConstraints,
                                             &pStrings);

    if(SUCCEEDED(hr))
    {
        AddColumns(pColDesc, nColumns);
        AddConstraints(pConstraints, nConstraints);
        AddIndexes(session);
        CoTaskMemFree(pStrings);
    }

    spTableCreation.Release();
}

代码实际上并不复杂。该方法填充了许多由提供程序分配的缓冲区(不包括 pStrings),这些缓冲区将包含我们需要的所有信息。如您所见,除了 pStrings 之外,我们不关心释放这些缓冲区。访问如此丰富的信息需要一些工作。在这里,我将所有这些工作封装在专门的方法下:AddColumns 用于添加所有列定义,AddConstraints 用于添加主键和外键约束,最后 AddIndexes 用于添加与表相关的所有索引信息。

让我们详细看看每一个。

  • AddColumns

    每个表列由一个 DBCOLUMNDESC 结构描述。如果您在 oledb.h 文件中查看它,您会看到

    typedef struct  tagDBCOLUMNDESC
        {
        LPOLESTR pwszTypeName;
        ITypeInfo __RPC_FAR *pTypeInfo;
        /* [size_is] */ DBPROPSET __RPC_FAR *rgPropertySets;
        CLSID __RPC_FAR *pclsid;
        ULONG cPropertySets;
        ULONG ulColumnSize;
        DBID dbcid;
        DBTYPE wType;
        BYTE bPrecision;
        BYTE bScale;
        }   DBCOLUMNDESC;

    此结构包含一个属性集数组,该数组描述了列的所有特征。每个属性集都由其自己的 GUID 标识,我们正在查找的列属性可以在 DBPROPSET_COLUMN 属性集中找到。属于此属性集的最相关的属性是

    • DBPROP_COL_AUTOINCREMENT:告诉我们这是否是 IDENTITY 列。SQL CE 将这些列限制为 32 位整数类型(DBTYPE_I4)。
    • DBPROP_COL_SEED:此属性包含 IDENTITY 列的种子(初始值)值。
    • DBPROP_COL_INCREMENT:数据库引擎用于递增 IDENTITY 列的值存储在此处。
    • DBPROP_COL_NULLABLE:如果列接受 null 值,则此属性设置为 true
    • DBPROP_COL_ISLONG:存储二进制大对象(BLOB)的列将此属性设置为 true
    • DBPROP_COL_FIXEDLENGTH:对于固定长度的列,此属性为 true

    要读取所有这些属性值,我们必须循环遍历属性集数组。我的方法是使用一个中间结构来存储列属性

    typedef struct tagCOLUMN
    {
        TCHAR           szName[129];
        DBTYPE          wType;
        DBCOLUMNFLAGS   dwFlags;
        ULONG           nSize,
                        nOrdinal;
        USHORT          nPrecision;
        SHORT           nScale;
        VARIANT_BOOL    bIdentity;
        int             nSeed,
                        nIncr;
    } COLUMN;

    某些属性将存储在结构变量中,而其他属性将转换为列标志。请注意,在此示例中,我没有检索列的默认值,但这也可以通过使用 DBPROP_COL_DEFAULT 属性来完成。

    填充此结构后,将创建一个类型为 CDbColumn 的对象并将其添加到表的列列表中(m_vecCol 成员变量)。

    这是完整的代码

    void CDbTable::AddColumns(DBCOLUMNDESC* pColDesc, ULONG nColumns)
    {
        ULONG        i;
        COLUMN       col;
    
        m_vecCol.reserve(nColumns);
    
        //
        // Loop through all columns
        //
        for(i = 0; i < nColumns; ++i)
        {
            ULONG       cs,
                        cp;
            DBPROPSET*  pPropSet;
            CDbColumn*  pColumn;
    
            StrCopyN(col.szName, pColDesc[i].dbcid.uName.pwszName, 128);
    
            col.dwFlags     = 0;
            col.wType       = pColDesc[i].wType;
            col.nPrecision  = pColDesc[i].bPrecision;
            col.nOrdinal    = i + 1;
            col.nScale      = pColDesc[i].bScale;
            col.nSize       = pColDesc[i].ulColumnSize;
            col.bIdentity   = VARIANT_FALSE;
            col.nSeed       = 0;
            col.nIncr       = 0;
    
            pPropSet = pColDesc[i].rgPropertySets;
            for(cs = 0; cs < pColDesc[i].cPropertySets; ++cs, ++pPropSet)
            {
                if(pPropSet->guidPropertySet == DBPROPSET_COLUMN)
                {
                    DBPROP* pProp = pPropSet->rgProperties;
                    for(cp = 0; cp < pPropSet->cProperties; ++cp, ++pProp)
                    {
                        switch(pProp->dwPropertyID)
                        {
                        case DBPROP_COL_AUTOINCREMENT:
                            col.bIdentity = pProp->vValue.boolVal;
                            break;
    
                        case DBPROP_COL_SEED:
                            col.nSeed = pProp->vValue.intVal;
                            break;
    
                        case DBPROP_COL_INCREMENT:
                            col.nIncr = pProp->vValue.intVal;
                            break;
    
                        case DBPROP_COL_NULLABLE:
                            if(pProp->vValue.boolVal == VARIANT_TRUE)
                                col.dwFlags |= DBCOLUMNFLAGS_ISNULLABLE;
                            break;
    
                        case DBPROP_COL_ISLONG:
                            if(pProp->vValue.boolVal == VARIANT_TRUE)
                                col.dwFlags |= DBCOLUMNFLAGS_ISLONG;
                            break;
    
                        case DBPROP_COL_FIXEDLENGTH:
                            if(pProp->vValue.boolVal == VARIANT_TRUE)
                                col.dwFlags |= DBCOLUMNFLAGS_ISFIXEDLENGTH;
                            break;
                        }
                    }
                }
            }
    
            //
            // Add the column
            //
            pColumn = new CDbColumn(&col);
            if(pColumn)
                m_vecCol.push_back(pColumn);
        }
    }
  • AddConstraints

    约束有四种不同类型

    • 主键
    • 外键
    • 检查约束
    • 唯一约束

    如果 SQL CE 2.0 不支持检查约束,那么我们可以跳过它们。本文不会涵盖唯一约束,尽管它们可以轻松添加。

    所有约束类型都由 DBCONSTRAINTDESC 结构描述,该结构定义如下

    typedef struct  tagDBCONSTRAINTDESC
        {
        DBID __RPC_FAR *pConstraintID;
        DBCONSTRAINTTYPE ConstraintType;
        ULONG cColumns;
        /* [size_is] */ DBID __RPC_FAR *rgColumnList;
        DBID __RPC_FAR *pReferencedTableID;
        ULONG cForeignKeyColumns;
        /* [size_is] */ DBID __RPC_FAR *rgForeignKeyColumnList;
        OLECHAR __RPC_FAR *pwszConstraintText;
        DBUPDELRULE UpdateRule;
        DBUPDELRULE DeleteRule;
        DBMATCHTYPE MatchType;
        DBDEFERRABILITY Deferrability;
        ULONG cReserved;
        /* [size_is] */ DBPROPSET __RPC_FAR *rgReserved;
        }   DBCONSTRAINTDESC;

    约束类型存储在 ConstraintType 成员中,可以是以下值之一

    • DBCONSTRAINTTYPE_UNIQUE
    • DBCONSTRAINTTYPE_FOREIGNKEY
    • DBCONSTRAINTTYPE_PRIMARYKEY
    • DBCONSTRAINTTYPE_CHECK

    为了处理主键和外键,我创建了类来存储它们,即 CDbForeignKeyCDbIndex(主键实际上是一种特殊的索引)。

    这些类只是列或列对的列表(有关详细信息,请参阅示例应用程序)。

    这是 AddConstraints 的完整代码

    void CDbTable::AddConstraints(DBCONSTRAINTDESC *pConstraints, 
                                              ULONG nConstraints)
    {
        ULONG    iCon;
    
        for(iCon = 0; iCon < nConstraints; ++iCon)
        {
            DBCONSTRAINTDESC* pCon = pConstraints + iCon;
    
            switch(pCon->ConstraintType)
            {
            case DBCONSTRAINTTYPE_UNIQUE: // Not handled
                break;
    
            case DBCONSTRAINTTYPE_FOREIGNKEY:
                {
                    CDbForeignKey* pFky = new CDbForeignKey(
                        pCon->pConstraintID->uName.pwszName,
                        pCon->pReferencedTableID->uName.pwszName,
                        pCon->UpdateRule,
                        pCon->DeleteRule);
    
                    if(pFky)
                    {
                        ULONG iCol;
    
                        m_vecFky.push_back(pFky);
    
                        for(iCol = 0; iCol < pCon->cColumns; ++iCol)
                        {
                          CDbForeignKeyCol* pCol;
                            
                          pCol = new CDbForeignKeyCol(
                            pCon->rgColumnList[iCol].uName.pwszName,
                            pCon->rgForeignKeyColumnList[iCol].uName.pwszName);
    
                          if(pCol)
                              pFky->AddColumn(pCol);
                        }
                    }
                }
                break;
    
            case DBCONSTRAINTTYPE_PRIMARYKEY:
                {
                    ULONG     iCol;
                    CDbIndex* pIndex = new CDbIndex(
                        pCon->pConstraintID->uName.pwszName, true);
    
                    if(pIndex)
                        m_vecIdx.push_back(pIndex);
    
                    for(iCol = 0; iCol < pCon->cColumns; ++iCol)
                    {
                        CDbColumn* pColumn = FindColumn(
                             pCon->rgColumnList[iCol].uName.pwszName);
                        CDbIndexCol* pIdxCol = new CDbIndexCol(
                             pCon->rgColumnList[iCol].uName.pwszName, 1);
    
                        if(pColumn)
                            pColumn->SetPrimaryKey(true);
    
                        if(pIdxCol && pIndex)
                            pIndex->AddColumn(pIdxCol);
                    }
                }
                break;
    
            case DBCONSTRAINTTYPE_CHECK: // Not supported
                break;
            }
        }
    }
  • AddIndexes

    最后,我们可以将索引添加到我们的表定义中。不幸的是,这个过程并不像您预期的那样直接。索引使用 CIndexes 类进行枚举,但此列表将包括真正的索引、主键(一种特殊的索引)和所有外键。因此,我们需要在弄清楚我们被报告的对象是真正的索引还是其他东西之前告知它们。

    CIndexes 类通过在 m_bPrimaryKey 成员变量中存储索引是否为主键来帮助我们。不幸的是,外键没有这样的运气——我们必须比较约束名称。

    此过程在源代码中进行了说明,并在此完全重现

    void CDbTable::AddIndexes(CSession &session)
    {
        CIndexes idx;
        HRESULT  hr;
    
        hr = idx.Open(session, NULL, NULL, NULL, NULL, m_strName);
        if(hr == S_OK)
        {
            while(idx.MoveNext() == S_OK)
            {
                CDbIndex* pIdx = NULL;
    
                // Skip primary keys.
                if(idx.m_bPrimaryKey == VARIANT_TRUE)
                    continue;
    
                // Skip foreign key indexes.
                if(FindForeignKey(idx.m_szIndexName))
                    continue;
    
                pIdx = FindIndex(idx.m_szIndexName);
    
                if(!pIdx)
                {
                    pIdx = new CDbIndex(idx.m_szIndexName, 
                                        false, 
                                        idx.m_bUnique == VARIANT_TRUE);
                    if(pIdx)
                        m_vecIdx.push_back(pIdx);
                }
    
                if(pIdx)
                {
                    CDbIndexCol* pCol = new CDbIndexCol(idx.m_szColumnName, 
                                                        idx.m_nCollation);
    
                    if(pCol)
                        pIdx->AddColumn(pCol);
                }
            }
            idx.Close();
        }
    }

    请注意,每个记录都包含索引名称和列名称。要构建多个列索引,我们必须从 CIndexes 读取一个以上的行。这就是为什么我必须使用 FindIndex 方法。

示例应用

本文的示例应用程序是 **SqlCeSpy**,一个小型免费工具。在这里,您可以访问完整的源代码,其中包含一些有趣的功能,例如带有分割器的窗口和 CListView 控件的数据缓存。但这些将在其他文章中介绍。

注意:示例仅包含 eVC3 项目文件。

© . All rights reserved.