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






4.63/5 (7投票s)
2003年5月8日
6分钟阅读

86098

2023
使用 ADOX 将字段插入 MS Access 表。
引言
无法使用普通的 ADOX 追加列方法插入字段(就像 MS Access 一样)。原因如下:
Append
当然只会将列添加到集合的末尾;- 字段/列的集合按字母顺序排列。
本文介绍了一种使用 ADOX 以编程方式将字段插入 MS Access 数据库表的方法。还有其他方法,例如使用 ALTER TABLE
SQL 命令。但是,使用 ADOX 非常有吸引力,因为它允许访问提供程序特定的字段属性,如描述、默认值、自动增量、可为空、允许零长度等。
方法论
该方法非常简单:**重建表!**
- 创建一个空的临时表
- 按正确的序数顺序从原始表中复制每个字段。在达到插入点时,将要插入的字段追加进去。
- 将记录从原始表复制到临时表中
- 将索引复制到临时表中
- *复制/恢复其他表属性或对象(如果有)
- *暂时从关系(
MSysRelationships
)中移除原始表 - 删除原始表
- 将临时表重命名为原始表的名称
- *恢复原始表的关系
*本文或代码示例中未涵盖。
重建表实际上并非那么简单。首先要克服的问题是字段集合将字段按字母顺序排序。解决方案已在另一篇文章中提供:获取 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); } }
限制,下一步该怎么办
InsertField
不适用于包含BLOB
或Long Binary
的表。这是因为临时表是通过标准 SQL 语句填充的。您可能需要使用 ADO/OLEDB 方法在表之间传输BLOB
。- 仅支持以下字段属性集合项:
- 默认值
- 描述
- 可空
- Jet OLEDB: 允许零长度
要改进代码,您可以在
CopyFieldProperties
函数中添加对以下项的支持:- 自动增量
- 固定长度
- 种子
- 增量
- Jet OLEDB: 列验证文本
- Jet OLEDB: 列验证规则
- Jet OLEDB: IISAM 不是最后一列
- Jet OLEDB: 自动生成
- Jet OLEDB: 每页一个 BLOB
- Jet OLEDB: 压缩的 UNICODE 字符串
- Jet OLEDB: 超链接
- 在用作关系中的表上不起作用。这可以通过扫描
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
Catalog
、Table
和Column
对象 - 扫描 OLEDB 提供程序错误
- 基本方法是子类化列表视图控件
- WTL 中的反射、通知处理
- 在 ATL/WTL 中使用 DDX
- 当然,还使用了 ADOXColumnOrdinal.h 和 ADOXInsertField.h
注释
- 基础 ATL 应用程序是使用 ATL 7 的 AppWizard 生成的。
- 在编译 release 模式时,我不得不删除项目设置中定义的
_ATL_MIN_CRT
来解决link error : LNK2001 symbol '_main' not found
。STL 需要main()
入口点。 - 打开 /GX 选项,以支持异常处理
- 如果您想使用 ATL 版本 3 编译它,请注释掉 ADOXInsertField.cpp 文件中带有
AtlInitCommonControls()
的那一行。
最后
最后,我想说——这里的代码是在我做晚饭的时候写的 :-) 。它可能不完美,但我希望它能帮助说明我的观点。
历史
- 2003 年 6 月 2 日 - 包含对 ADOXColumnOrdinal.h 的更改,以动态检测书签支持
- 2003 年 5 月 8 日 - 初始版本