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

使用 ADOX 插入字段( 非追加)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (7投票s)

2003年5月8日

6分钟阅读

viewsIcon

86098

downloadIcon

2023

使用 ADOX 将字段插入 MS Access 表。

Sample Image - ADOXInsertField.gif

引言

无法使用普通的 ADOX 追加列方法插入字段(就像 MS Access 一样)。原因如下:

  1. Append 当然只会将列添加到集合的末尾;
  2. 字段/列的集合按字母顺序排列。

本文介绍了一种使用 ADOX 以编程方式将字段插入 MS Access 数据库表的方法。还有其他方法,例如使用 ALTER TABLE SQL 命令。但是,使用 ADOX 非常有吸引力,因为它允许访问提供程序特定的字段属性,如描述、默认值、自动增量、可为空、允许零长度等。

方法论

该方法非常简单:**重建表!**

  1. 创建一个空的临时表
  2. 按正确的序数顺序从原始表中复制每个字段。在达到插入点时,将要插入的字段追加进去。
  3. 将记录从原始表复制到临时表中
  4. 将索引复制到临时表中
  5. *复制/恢复其他表属性或对象(如果有)
  6. *暂时从关系(MSysRelationships)中移除原始表
  7. 删除原始表
  8. 将临时表重命名为原始表的名称
  9. *恢复原始表的关系

*本文或代码示例中未涵盖。

重建表实际上并非那么简单。首先要克服的问题是字段集合将字段按字母顺序排序。解决方案已在另一篇文章中提供:获取 ADOX 表对象的正确列序数

此外,提供程序有自己特殊的表和字段属性。重建表时,请确保应用程序使用的属性已正确恢复。这可能是个案处理,因此本文不再深入探讨具体细节。(有关改进和专业化可能发生的地方,请参阅《限制》部分和代码中的 TODO 项。)

<SandBox>嗯,对于纯粹主义者来说,这种方法很糟糕。我也不喜欢它,但它有效并且在我的项目中运行得很好。我只希望这篇文章能成为更好解决方案的起点。</SandBox>

使用代码

这是函数原型

static BOOL InsertField(_TablePtr p_table, 
   _ColumnPtr p_field, long l_beforeIndex, _bstr_t & rstr_error)

在特定序数位置之前插入新字段到表中。

参数

  • OField & p_newField - 对新 OField 对象的引用
  • long l_beforeIndex - 在此位置插入新字段
  • _bstr_t & rstr_error - 对将在发生任何错误时接收错误消息的字符串的引用

Returns

  • BOOL - 如果成功插入字段,则为 TRUE

注释

新字段应具有默认值,或者“Nullable”属性应设置为 TRUE,否则插入操作将失败。发生的情况是,在临时表填充了原始表的记录后,新字段的值为 NULL(如果没有默认值)。

您需要将 ADOXInsertField.h 文件包含到您的项目中;或者将其复制粘贴到您的类中。需要 ADOXColumnOrdinal.h 文件,该文件也包含在此包中。

注意:如果尚未设置,则需要将 /GX 编译器选项设置为支持异常处理。

请确保您的 stdafx.h 或其他可访问的位置包含以下内容。

// I assume you have this somewhere
#import "C:\Program Files\Common Files\System\ado\msado15.dll" \
    rename( "EOF", "adoEOF" ) rename("DataTypeEnum", "adoDataTypeEnum")
#import "C:\Program Files\Common Files\System\ado\msadox.dll" \
    rename( "EOF", "adoEOF" ) no_namespace rename("DataTypeEnum", 
                                                    "adoDataTypeEnum")
// For _bstr_t and _variant_t<
#include <comdef.h>
// OLEDB<
#include "oledb.h"
// And of course, our code<
#include "ADOXInsertField.h"

示例

以下代码将一个名为 Quality 的新字段插入到当前由字段 Value 占据的序数位置。

_TablePtr p_table =  mp_catalog->Tables->GetItem(_T("MyTable"));

// Get a conversion map from ADOX collection index into actual field ordinal
ColumnNameToOrdinal columnMap;
if (GetColumnNameToOrdinalMap(p_table, columnMap))
{
    // Here is the field we want to insert
    _ColumnPtr fieldToAdd;
    fieldToAdd.CreateInstance(__uuidof (Column));

    // Set the reference catalog so that we can access Jet OLEDB properties
    fieldToAdd->PutRefParentCatalog(mp_catalog);
    // Field Name
    fieldToAdd->PutName(_T("Quality"));
    // Description of the field
    fieldToAdd->Properties->GetItem
         (_T("Description"))->PutValue(_T("Quality assessment"));
    // Field Type
    fieldToAdd->PutType(adVarWChar);
    // Field Size
    fieldToAdd->PutDefinedSize(64);
    // Nullable
    fieldToAdd->Properties->GetItem
         (_T("Nullable"))->PutValue
         (_variant_t(VARIANT_TRUE,VT_BOOL));
    // Allow Zero Length 
    fieldToAdd->Properties->GetItem
         (_T("Jet OLEDB:Allow Zero Length"))->PutValue
         (_variant_t(VARIANT_TRUE,VT_BOOL));
    // Default Value 
    fieldToAdd->Properties->GetItem
         (_T("Default"))->PutValue(_bstr_t("Not assessed"));

    // Now do the insert thing
    _bstr_t str_error;
    if (InsertField(p_table, fieldToAdd, 
            columnMap[_T("Value")], str_error))
    {
        // Note! p_table is no longer valid
        // because it is pointing to a table that has 
        // already been replaced by a new one containing the inserted field

        // So get it again
        p_table = mp_catalog->Tables->GetItem(_T("MyTable"));
    }
    else
    {
        ::MessageBox(NULL, str_error,_T("Insert"), 
                            MB_ICONINFORMATION|MB_OK);
    }

}

限制,下一步该怎么办

  1. InsertField 不适用于包含 BLOBLong Binary 的表。这是因为临时表是通过标准 SQL 语句填充的。您可能需要使用 ADO/OLEDB 方法在表之间传输 BLOB
  2. 仅支持以下字段属性集合项:
    • 默认值
    • 描述
    • 可空
    • Jet OLEDB: 允许零长度

    要改进代码,您可以在 CopyFieldProperties 函数中添加对以下项的支持:

    • 自动增量
    • 固定长度
    • 种子
    • 增量
    • Jet OLEDB: 列验证文本
    • Jet OLEDB: 列验证规则
    • Jet OLEDB: IISAM 不是最后一列
    • Jet OLEDB: 自动生成
    • Jet OLEDB: 每页一个 BLOB
    • Jet OLEDB: 压缩的 UNICODE 字符串
    • Jet OLEDB: 超链接
  3. 在用作关系中的表上不起作用。这可以通过扫描 MSysRelationShips 表的 szObjects 字段来检查。如果您的表在那里,则上述方法的步骤 7 将失败。要使其正常工作,您需要弄清楚如何移除和恢复这些条目(分别为步骤 6 和 9)。

观察

在将属性从原始表复制到临时表时,遇到了大多数问题。以下是最棘手的问题:

可为空字段属性异常

在某些 Jet OLE DB 配置(2.5 或 2.6 结合 MS Access 安装?)下,“Nullable”属性从列属性集合中反转。例如:将“nullable”设置为 TRUE,在添加列并检索“nullable”属性后,您会得到 FALSE!尽管当您从 MS Access 查看时,它实际上设置为 TRUE

为了解决这个问题,在读取“nullable”属性时,使用 GetAttributes 方法

BOOL b_nullable = p_sourceField->GetAttributes()&adColNullable;

但在设置“nullable”属性时,使用字段属性集合

p_targetField->Properties->GetItem(_T("Nullable"))->PutValue
                                        (_variant_t(VARIANT_TRUE,VT_BOOL));

那么为什么不全部使用 Get/PutAttributes 呢?答案是:因为使用 p_field->PutAttributes() 方法设置“nullable”属性会在将字段追加到集合时导致异常。自己琢磨吧 :-)

不要碰我的属性!

在传输属性集合时,自然的想法是遍历集合并将其设置为另一个字段。这样,我就不会在上面的《限制》部分的第 2 项下列出内容了。例如:

for (long i = 0; i < p_source->Properties->GetCount(); i++)
{
    _variant_t var_prop;
    _bstr_t name = p_source->Properties->GetItem(i)->GetName();
    ATLTRACE(_T("Field Property %d: %s\n"), i+1, (LPCTSTR)name);
    try
    {
        var_prop = p_source->Properties->GetItem(i)->GetValue();
        if (var_prop.vt != VT_EMPTY && var_prop.vt != VT_NULL)
        {
            p_destination->Properties->GetItem(i)->PutValue(var_prop);
        }
    }
    catch(...)
    {
    }
}

上面的代码会正常运行,但当我们最终将字段(p_destination)添加到表中时,会发生错误!OLEDB 0x80040e21 - multistep error. 属性集合是否如此敏感,它不允许您修改与字段无关的属性?我没有更多时间去处理这个问题了,所以我把它留给你们去告诉我发生了什么。

为了解决这个问题,CopyFieldProperties() 只会复制选定的属性,并且在复制之前,会测试字段类型的相关性。请参阅《限制》部分,了解支持哪些属性。

演示应用程序

示例项目是一个简单的 ATL 应用程序。它最初只是为了展示 InsertField 的工作原理,但后来发展到支持浏览字段属性和删除字段。无论如何,在演示项目中,您可以看到一些关于:

  • 结合使用 ADO 和 ADOX
  • 使用 ADOX CatalogTableColumn 对象
  • 扫描 OLEDB 提供程序错误
  • 基本方法是子类化列表视图控件
  • WTL 中的反射、通知处理
  • 在 ATL/WTL 中使用 DDX
  • 当然,还使用了 ADOXColumnOrdinal.hADOXInsertField.h

注释

  1. 基础 ATL 应用程序是使用 ATL 7 的 AppWizard 生成的。
  2. 在编译 release 模式时,我不得不删除项目设置中定义的 _ATL_MIN_CRT 来解决 link error : LNK2001 symbol '_main' not found。STL 需要 main() 入口点。
  3. 打开 /GX 选项,以支持异常处理
  4. 如果您想使用 ATL 版本 3 编译它,请注释掉 ADOXInsertField.cpp 文件中带有 AtlInitCommonControls() 的那一行。

最后

最后,我想说——这里的代码是在我做晚饭的时候写的 :-) 。它可能不完美,但我希望它能帮助说明我的观点。

历史

  • 2003 年 6 月 2 日 - 包含对 ADOXColumnOrdinal.h 的更改,以动态检测书签支持
  • 2003 年 5 月 8 日 - 初始版本
© . All rights reserved.