使 ATL OLE DB 提供程序模板支持数据更新 - 第 1 部分





5.00/5 (3投票s)
ATL OLE DB 提供程序模板似乎仅支持只读行集,并且使其支持数据更新不像您期望的那样容易!
通过 ADO 记录集更新数据
首先,值得澄清一些关于客户端和服务器端游标以及我们的行集的混淆。通常,选择客户端或服务器端游标是网络流量和本地存储之间的简单选择。服务器端游标物理上位于数据旁边,在大多数 OLE DB 提供程序的情况下,这很可能位于到数据库服务器的网络连接的远端。对于用于访问我们数据对象的行集,我们的服务器端位于我们的数据对象内部...如果我们能让所有数据绑定的控件与此服务器端游标实现一起工作,那么我们就不需要关心客户端游标了,但由于某种原因,大多数 Microsoft 编写的数据绑定控件在将游标设置设置为服务器端时,将无法从这些行集中获取任何数据。选择客户端游标会导致 OLE DB 在您的行集和使用者之间插入客户端游标引擎,这是一个 OLE DB 服务组件,它添加了您自己不提供的功能(客户端数据缓存)。使用 CCE 与我们的行集的问题在于,数据已经全部在客户端,因此缓存只是复制数据并占用我们通常需要的两倍存储空间...然而,目前,如果您需要允许使用者通过 Microsoft 编写的数据绑定控件更新您的数据,那么您必须使用 CCE...
如果使用服务器端游标,则支持从我们的行集更新相对容易,但如果选择客户端游标,事情会变得更加复杂。我们将在本文中讨论服务器端更新问题,并在下一篇文章中介绍使用客户端游标所需的更改。
实现 IRowsetChange
OLE DB 提供程序文档似乎暗示,如果您希望您的提供程序可更新,那么您需要实现 IRowsetChange 或 IRowsetUpdate。IRowsetChange 包含了添加新行、删除行和更改数据所需的一切。IRowsetUpdate 增加了将一系列更改批量处理并一次性应用到数据源的功能。有趣的是,Janus Grid 会对所有更新使用 IRowsetChange(如果 IRowsetUpdate 不可用),但如果后者可用,则会使用后者。由于 IRowsetUpdate 的实现更复杂,并且对我们的示例没有增加任何价值,因此我们不会麻烦它。在尝试让客户端游标引擎更新正常工作时,我添加了对 IRowsetUpdate 的支持,但它在让 CCE 更新正常工作方面既无帮助也无阻碍...
IRowsetChange 是一个相对简单的接口,由三个相当直接的方法组成
HRESULT DeleteRows( HCHAPTER hChapter, ULONG cRows, const HROW rghRows[], DBROWSTATUS rgRowStatus[]); HRESULT InsertRow( HCHAPTER hChapter, HACCESSOR hAccessor, void *pData, HROW *phRow); HRESULT SetData( HROW hRow, HACCESSOR hAccessor, void *pSrcData);
提供程序可以选择实现以上三个方法中的任何一个或全部,并为它不支持的任何功能返回 DB_E_NOTSUPPORTED。它通过 DBPROP_UPDATABILITY 属性报告其支持级别。有关更多详细信息,请参阅接口文档。这使得实现方法稍微复杂一些,因为它们必须首先检查它们正在使用的行集是否支持所需的操作。IRowsetChange 方法的另一个主要复杂之处在于正确处理使用者通知阶段。每个方法都可以通过 IRowsetNotify 连接点接口触发多个通知到使用者。更重要的是,使用者可以通过适当地响应通知调用来否决某些操作。
使用我们对 IRowsetChange 的实现允许我们在行集的属性映射中设置以下属性。
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_TRUE) PROPERTY_INFO_ENTRY_EX( UPDATABILITY, VT_I4, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT, 0) PROPERTY_INFO_ENTRY_EX( OWNINSERT, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX( OWNUPDATEDELETE, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX( REMOVEDELETED, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX( IConnectionPointContainer, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, VARIANT_TRUE, 0)
支持通知
要通过 IRowsetNotify 支持使用者回调,我们的行集需要成为一个连接点容器,并且我们需要支持 IRowsetNotify 连接点的连接。这使用 ATL 连接点支持相对容易实现。
实际的通知触发代码比 ATL 可以为您生成的事件触发代码稍微复杂一些,因为允许使用者响应某些通知并否决行集中发生的更改。这意味着我们需要特别注意通知调用的返回值,并对否决更改的请求做出适当的反应。
IRowsetNotify 事件源实际上非常简单,但由于可能发生的事件类型和事件阶段的性质,使用事件很复杂。例如,在更新之前,我们可能会发送一个“列集”“好的”事件,然后是一个“列集”“即将进行”事件,如果使用者否决了其中任何一个事件,那么所有使用者都会收到一个“列集”“未完成”事件。由于这种复杂性,我们将事件组封装在助手方法中,可以从我们的 IRowsetChange 方法内部调用这些助手方法。
OLE DB 规范所需的通知事件的顺序和详细信息很难从文档中确定。此代码发送的事件似乎足够了,但您的结果可能因具体情况而异……一种研究这些事件的方法是观察客户端游标引擎在将客户端游标更新应用于记录集时触发的事件序列。
一个可更新的代理行集
现在我们有了 IRowsetChange 和 IRowsetNotify 事件源接口,我们可以实现一个代理行集,该行集使用这些接口为我们数据对象的行集提供更新功能。CUpdatableProxyRowsetImpl 从我们之前文章中开发的代理行集以及 IRowsetChange 和 IConnectionPointContainer 的实现派生而来。
template < class DataClass, class T, class CreatorClass, class Storage = CRowsetStorageProxy<T>, class ArrayType = CRowsetArrayTypeProxy<T, Storage>, class RowClass = CSimpleRow, class RowsetInterface = IRowsetImpl < T, IRowset, RowClass> > class CUpdatableProxyRowsetImpl: public CProxyRowsetImpl< DataClass, T, CreatorClass, Storage, ArrayType, RowClass, RowsetInterface >, public IRowsetChangeImpl<T, Storage>, public IConnectionPointContainerImpl<CUpdatableProxyRowsetImpl>
在我们的行集类中,我们处理我们的行集通知所需的连接点容器。我们还提供调用我们数据对象的行集执行更新的代码。插入和删除使用我们行集的代理存储对象上已有的操作来完成。
请注意,CUpdatableProxyRowsetImpl 中的 SetDataHelper() 方法有一个糟糕的硬编码限制,即每个列的数据限制为 256 字节。这可以很容易地删除,但留给读者作为练习;)
我们数据对象行集的更改
我们需要更改我们的数据对象行集对象,以利用我们提供的更新功能。它现在从我们新的可更新代理行集继承,并且需要实现 UpdateColumn() 方法。完成此操作后,只要我们选择了服务器端游标,我们的对象就可以通过 ADO 更新。如果我们运行 VB 测试工具程序并创建一个表,然后从具有服务器端游标和批量乐观锁定的数据对象中获取行集,我们可以单击按钮将行集显示在 Janus Grid 中,并在数据对象行集的 UpdateColumn() 方法内部设置一个断点。当我们更改网格中的数据时,我们会进入行集的 UpdateColumn() 方法,并且数据会被更新。
有趣的是,要使上面的示例正常工作,我们不需要将数据源属性 DBPROP_DATASOURCEREADONLY 设置为 VARIANT_TRUE,但我们可能应该这样做……同样,在我们自己的代理行集中,我们默认不通过添加 DBCOLUMNFLAGS_WRITE 到列标志来指示列是可写的,但我们应该这样做。未能通过添加此标志将列数据标记为可写会完全阻止客户端游标更新工作……(是的,我花了很长时间才找到为什么在以前可以正常工作的代码中它们会失败)
基础表信息不足...
我们的行集现在支持更新,但它仍然无法与许多常见的 Microsoft 数据绑定控件配合使用。如果我们使用服务器端游标,Data Grid 不会显示数据(我不知道为什么,而且我有一个与 Microsoft 相关的开放式支持电话),因此要从 Microsoft 数据绑定控件更新数据,我们需要支持通过客户端游标引擎进行更新。然而,如果我们选择客户端游标和乐观锁定在我们的测试程序中,并在 Data Grid 中尝试更改数据,我们会收到一条错误消息“更新或刷新所需的基础表信息不足”。切换到批量乐观锁定只会导致在发出记录集的 UpdateBatch 命令时出现错误,而不是在数据更改时立即出现错误……我们将在下一篇文章中解决此错误的原因及其解决方案。
源文件使用 Visual Studio 6.0 SP3 构建。使用 7 月版的 Platform SDK。如果您没有安装 Platform SDK,您可能会发现编译失败并查找“msado15.h”。您可以创建一个具有该名称并包含“adoint.h”的文件来解决此问题。