| 离线视图的实现
作者:Doug Hennig 译者:RMH 出处:网络
在Visual FoxPro 3中引入的可更新视图为FoxPro应用程序提供了一种新的能力. VFP 5 的一种新功能进一步扩展了可更新视图的功能:
你现在可以设计任何离线视图. 离线视图是与数据源“分离的”数据集,因此当视图的基表不可用时也可以用离线视图来进行数据输入. 然后在稍后的时间里,
你可以用“重联接”来用视图中的数据更新源表. 当你在VFP 3中做这样的工作时, 需要许多代码和技巧. VFP 5 使它变得非常简单了.
本文将着眼于离线视图的技巧: 如何获得离线视图, 考虑关于用离线视图进行工作, 更新源表, 以及联线(Back online). 我们也考虑离线视图的一些用途:诸如允许在旅途或分支部门查询和数据输入的远程应用程序,
以及进行表维护时的一些操作 (索引, 更新结构, 压缩, 备份等).
离线视图的基础
当你生成一个离线视图时, VFP 建立一个包含了所有视图中的记录的表. 该表可以复制到另一个系统中或工作站中, 并可用于查询和数据输入. 当你打开视图时, VFP 实际上打开该表而不是执行一条一般的SQL Select 命令. 任何对离线视图的修改 (添加, 修改或删除) 都在该表中进行而没有回写到该视图所基于的源表.
对于这个DBF 文件中 (如果视图中包含了备注字段或通用字段,还有FPT 文件), VFP 建立TBF 和TDX 文件(如果视图中包含了备注字段或通用字段可能还有TPT 文件) ,这些文件是用于DBF 文件的稳定的表缓存文件. 虽然你可能猜想这些文件是真正的表,索引和备注文件只不过是冠以不同的扩展名而已, 你不能在VFP中打开TBF 表; 如果试图打开该表会得到一条“不能直接打开持久表缓存” 错误信息. 经探索, 我发现TBF 文件包含了对视图中每一个记录的修改,添加或删除的记录. 然而, 该文件的结构与一般的DBF文件的结构还是稍有不同, 因此你确实不能直接对它进行操作.
当你完成一个离线视图时,你可以对它做两件事:
更新该视图所基于的源表.
放弃所有对视图的修改, 保持源表不变.
取得离线视图
获取离线视图是简单的: 确信包含了视图定义的数据库是当前数据库并使用新的createoffline() 函数. 语法如下:
Createoffline( [, ])
是离线视图的名字. 任何视图都可以离线; 你不用特别定义一个视图作为“离线表”. 选项 是要建立的DBF的目录和文件名. 如果你未指定, VFP 将在当前目录中建立一个与视图同名的表(例如, 如果视图名字是LV_CUSTOMER, 表名将是LV_CUSTOMER.Dbf). 注意VFP 文档中关于该参数的描述是错误的; 它规定你只能为该表指定一个目录, 但实际上你也可以指定名字 (尽管扩展名不是必须的).
如果视图成功的离线,createoffline() 返回.T.。如果视图已经离线,如果你使用了第二个参数而又未指定文件名, 如果视图表或支持性表缓存文件已经存在 (该行为有望在将来的版本中得到改变; 依我看来, 它不会妨害set Safety), 或源表不能打开,则返回 .F. 。
以下是该函数的一个例子:
llSuccess = Createoffline('LV_CUSTOMER', 'OFFLINE\LV_CUSTOMER')
该例试图让LV_CUSTOMER 视图离线并在当前目录下的OFFLINE子目录中建立一个叫LV_CUSTOMER.Dbf 的表.
这里是关于生成离线视图的其它要注意的事:
Createoffline() 打开视图所基于的表而不是视图本身.
如果视图离线,dbgetprop(, 'View', 'Offline') 返回.T..
VFP 5.0 (包括5.0a) 有一个bug: 如果 Createoffline() 返回.F., 指明视图因某种原因而未能离线,DBGetProp(, 'View', 'Offline') 出乎意料的返回.T.. Dropoffline()也不会正常工作; 它返回.F. 指明视图不能离线, 而且即使它确实已经不是离线视图, DBGetProp(, 'View', 'Offline') 继续返回.T., 然而; 在视图联线模式或管理模式下打开视图将会得到一条 “对象不是一个离线视图” 错误. 幸运的是, 如果你解决该问题而致使createoffline() 失败, 你可以再次使用它来让视图离线.
用离线视图工作
你可以用USE命令打开离线视图就象打开一个联线视图一样. 然而, 就象我在前面的说明一样, VFP 事实上打开离线视图的持久表而不是执行一条一般的SQL Select 命令.
使用离线视图工作时有一些要注意的事:
改变模式到离线视图,添加, 修改或删除操作都是对视图表而没有写回到视图所基于的源表. 这意味着其它人打开源表时,看不到数据在离线视图中的改变.
在另一方面: 你也不能从源表中刷新离线视图的内容.
视图表的行动象一个普通表的缓存. Getfldstate() 和getnextmodified() 反映了视图的缓存状态. Tableupdate() 将视图缓冲中的修改写回到视图表中, Tablerevert() 则取消修改. 没有这些函数将不能对源表进行任何处理.
你不能修改视图表的结构,也不能使用对它使用create Trigger, Insert, Pack, 或 Zap 命令 (但可以使用delete All).
与联线视图一样, 离线视图自动使用行缓存和表缓存, 因此你应该将multilocks 设置为 On. 如果你试图关闭缓存将会得到一条错误信息.
与联线视图不同的是, 打开一个离线视图不会自动打开视图所基于的源表 ,这是由于离线视图是与源表“脱离的”. 这意味着当你把离线视图交给别人时,不需要提供源表的拷贝 (它可能会非常大或是一个非VFP 数据表). 同时也意味着你可以在源表上进行某些类型的(诸如重建索引之类)维护, 甚至在多个用户打开离线视图时. 稍后我们将看到这种例子.
在离线视图上输入数据时,源表的规则和触发器不会激活, 仅当你将数据更新到源表时, 源表的规则和触发器才会激活. 这就是说除非你为使用 DBSetProp()为离线视图定义了规则, 有可能有不正确的数据进入离线视图并不能在较早的时候被发现.
如果你使用“修补关键字”, 你需要为添加到视图中的记录指定关键字值. 最常用的指定关键字段值的方法是调用一个NEXTID 程序来为该表的取得下一个可用的基本关键字段值. 如果你使用的是这种方案, 你需要使用dbsetprop() 来设置离线视图中的字段的DefaultValue 属性,这样添加记录到离线视图中时可取得指定的关键字段值.
使用候选关键字段的一个不利因素是: 如果离线视图用于允许远程数据输入 (如在笔记本电脑中), 你需要为每一个笔记本电脑指定一个关键字段值块,这样主关键字段的值才不会重复. 作为一种选择, 你可以将用户名或笔记本电脑名组合进关键字段值中, 这样就不用担心会出现相同的主关键字段值了.
如果一个离线视图是用于一个远程计算机上进行数据输入, 你需要传送离线视图表(包括FPT), 持久表缓存文件和数据库容器(Dbc, DCX, 和DCT) 到那个远程计算机中.
如果你需要确定自视图离线后那些记录被修改了, 使用use 命令的admin 子句. 这需要以独占方式访问视图表否则你会得到一条错误信息. 下面是使用该命令的例子:
Use LV_CUSTOMER admin Exclusive
当用 admin 模式打开视图时,它自动使用表缓存而不是行缓存. 试图改变表的缓存模式会造成错误. 一但视图以 admin 模式打开, 你可以使用 getnextmodified() 和 Getfldstate() 来确定哪些记录和这些记录中的哪些字段被修改了. 有趣的是, 在 admin 模式下打开一个离线视图时并没有自动打开它的源表, 因此 VFP 必须使用 TBF 文件来检查哪些记录被修改了. 当视图在管理模式下打开时,你不能更新源表; 更详细的信息参见下一章中的更新源表. 可是, 你可以使用 tablerevert() 来撤消对离线视图的修改.
更新源表
当你准备用离线视图中的变化更新源表时, 在 Use 命令中使用 online 子句来打开视图使其与源表“关联”, 然后使用 tableupdate().
以 online 模式打开视图需要对视图表以独占的方式访问否则会发生错误,并表能使视图与源表关联; 它简单的打开视图并准备更新源表. 以下是示例代码:
Use LV_CUSTOMER online Exclusive
与 admin 模式一样, 视图在 online 模式下打开时会自动使用表缓存而不是行缓存. 在 online 模式下打开一个离线视图不会自动打开源表,除非你发布一条tableupdate() 命令.
在以 online 模式打开视图后, 使用 Tableupdate() 命令来把离线视图中的修改更新到源表. 这与从表缓存中或联线视图中更新数据到表中完全一样: 如果发现冲突或规则(字段或表表验证, 主关键字或候选关键字, 或触发器) 违反, Tableupdate() 会失败而你可以用getnextmodified() 和 Getfldstate() 来确定哪些记录中的哪些字段被修改并处理这些冲突和规则违反.
从离线视图中更新源表不会让视图与源数据相联, 也不会以源表中的数据刷新视图 (例如, 被其它离线视图修改或对源表的直接修改).
让视图与数据源相连
Dropoffline() 函数让视图与源表相联. 语法如下:
Dropoffline()
这会删除视图离线时建立的支持性缓存文件(TBF, TDX, 和 TPT). 在离线视图中的任何还没有写回源表的数据修改都将被放弃.若视图成功的与源表相联dropoffline() 返回.T. 如果视图正被其它工作站打开或已经与源表相联则返回.F. 示例如下:
llSuccess = Dropoffline('LV_CUSTOMER')
刷新(Refreshing) 离线视图
虽然createoffline() 的帮助主题似乎说明,如果以online模式打开视图时,可以刷新离线视图, 但这并非事实. 在那种情况下,不能从源表刷新离线视图; Requery() 或 Refresh() 命令不支持离线视图, 即使它是在admin 或 online 模式下被打开, 将会得到一条 “对离线视图非法操作” 错误. 刷新视图的唯一方法是让它与源表相联 (在更新或放弃修改后,使用dropoffline() 命令) 然后再让其离线.
使用离线视图
离线视图的最大用处是允许远程查询和数据输入. 但是, 另一个重要理由是:当用户在持续地进行查询和数据输入时,允许以独占方式打开源表对源表进行维护. 我们将探讨一下关于使用离线视图使这种任务变得更容易.
机动查询和数据输入
这种情形的典型情况是一个销售人员正在访问一个客户的旅途中. 在以前的时候, 如果销售人员想为一个客房输入一个新的订货, 有以下两种选择:
他不得不通过 modem 拨号进入办公系统,并以 modem 的速度运行订单输入应用程序.
在他的笔记本电脑中有一个数据输入程序. 同时还需要在办公室中有一个高级的程序来合并销售人员上传的订单数据.
使用离线视图, 第二种选择非常容易实现. 销售人员可以使用与办公室使用的程序相同的应用程序(或者该程序的一个子集) ,但使用一个客户, 订单, 订单细节和产品表的离线视图拷贝. 当销售人员回到办公室后, 把离线视图文件从笔记本电脑中拷贝到服务器并更新到源表就可以了.
假如每一位销售人员在他们的视图中只需要他们自己的客户或产品; 换句话说, 你如何处理参数化的视图? 问题是当视图离线时, 你必须指定什么样的参数, 因此每一位销售人员将得到相同的数据. 方案是游离视图后, 为每一位销售员指定不同的参数, 然后复制视图文件到销售员的笔记本电脑中. 让视图离线, 然后再次联线处理下一位销售人员的数据, 指定参数. 在更新各位销售人员的数据前,视图必须保持离线或让其离线.
一个次要因素是:如果客户的余额需要更新, 需要使用一种不同的方法处理冲突而不是采用复盖或放弃更新. 例如, 在销售员的视图中, 客户可能签了$100.00 产品的订单, 但同时, 在办公室的视图中, 客户支付了$50.00 发票款. 更新子程序需要以检查在离线视图中的以前的值和新值的方法来处理这种类型的更新,并提供差值至源表中. 产品的当前数处理也与此相似.
多个本地数据
几年前, 我为一个拥有12个办公室的客户写了一个应用程序. 每一个办公室有它们自己的数据表拷贝, 但总办公室的文件必须包含各办公室的完整的数据以便进行数据维护. 每月一次, 各办公室将他们的文件送到总办公室并使用一个复杂的合并程序将各办公室的数据合并在一起. 要实现该方法的程序是很难编写的; 它必须查看各办公室数据的添加,删除和修改并将其反映到主办公室的文件中去.
这种情形在 VFP 5 中可以通过离线视图方便地实现. 如果我今天来写该程序,可能我会采用以下方案:
各办公室都拥有一个数据库和离线视图表的拷贝, 并可以在离线视图中输入数据.离线视图存放在与数据库相同的目录中以保存数据的一致性.
在需要时, 各办公室将它们的离线视图文件送至主办公室. 这些文件放在不同的子目录中, 每一办公室一个目录.
一次一个目录地, 各办公室的离线视图文件拷贝到主办公室的并复盖主办公室的视图文件. 然后在online 模式下打开视图文件并用 Tableupdate() 将视图中数据的修改提交到源表. 进行该处理后各办公室子目录下的离线视图文件将被删除.
刷新子办公室的视图, 让视图离线, 然后再联线, 并将离线视图文件送回各办公室. 当然, 这意味着在办公室的数据输入在他们的文件送回主办公室前不能更新. 然而, 由于该处理可以在夜间自动运行, 这不会是大问题.
当你解压演示该技术的源代码压缩文件时,会建立一个 MULTIPLE 目录. 如果VFP 没有安装在与这些文件相同驱动器中的VFP5中, 修改 Setup.PRG 文件并按实际修改lcDataDir 中的路径定义. 运行 Setup.PRG 来在MULTIPLE目录中建立一个VFP 的样板数据库文件TESTDATA 的一个副本,并将它们复制到以下两个目录中: OFFICE1 和 OFFICE2. 并建立一个叫做LV_CUSTOMER的离线视图.
在运行SETUP后, 运行 NEWRECS.PRG. 这会添加新记录到主办公室(CUST_ID 000) 、办公室1 (CUST_ID 111), 和办公室2 (CUST_ID 222)的离线视图中. 在添加了各记录后, 会显示一个浏览窗口,这样 你可以看见新记录. 在运行完NEWRECS 后, 运行 CONSOLID.PRG. 该程序合并各视中的数据变化到主办公室的CUSTOMER 表,并在一个流览窗口中显示该表的数据以便你查看所有的修改.
维护源表
如果你有一个应用程序必须不停止的工作或表太大以致于应用程序不能在为它建立索引是停止下来, 你将面临这样的问题: 如果工作站或服务器崩溃, 索引被破坏并要求重建时,应该怎么办?
有一个好的理由让你的表单和报表是基于视图的而不是基于表的. 你可以让一个视图离线, 并在用户继续查询或数据输入时重建索引. 一但索引完成, 从离线视图把数据更新到源表. 仅需要短暂的中断来让视图离线后更新源表并重新让视图联线. 事实上, 如果你需要进行常规的重建索引, 你可以考虑使用离线视图, 并周期性的短时联线时间进行重建索引工作.
一个小的复杂的地方是, 如何关闭各工作站上的视图足够长的时间来使视图离线(以及连线) 而不需要打电话告诉各个用户告诉他们关闭数据输入表单一会? 我使用的方法是一种“旗语”方案: 各数据输入表单监视一个需要维护的信号. 当接收到该信号时, 表单关闭,然后应用程序等待第二个视图状态已改变的信号(离线或联线), 然后数据输入可以继续下去. 在收到第二个信息前, 用户不能访问数据输入表单或任何使用视图的报表.
监视信号的基础是计时器对象. 在该方案中居然使用了三个有关的计时器:
重索引表单中的计时器用于执行重建索引时的等待,直到所有用户关闭了视图. 然后让视图离线或联线(这要看是否重建索引是开始了或已经完成) 并传送信号到应用程序级的计时器.
在数据输入表单中的计时器等待一个信号时,视图必须改变状态. 当接收到该信号时, 表单关闭且一个应用程序级的计时器开始工作.
应用程序级的计时器, 它仅在等待视图的修改状态时被启用, 监视来自重索引表单中的视图状态改变信号,这样数据输入和查询可以继续.
当你解压本文所附的源代码时,会在REINDEX 目录中生成一些文件. 以下是这些文件中的一部分:
SYSINFO.Dbf 是一个包含一个逻辑字段CHNGSTATE 的表(ChangeState的缩写). 该字段在通常情况下的值为.F.,当它的值被设置为.T.时, 它反映了视图中所有的该状态的块必须修改. 该表在应用程序运行过程中始终是打开的.
应用程序菜单中的所有功能都需要一个Skip For SYSINFO.CHNGSTATE 。因此当视图的该状态被修改时,用户不能访问该功能.
任何使用视图的数据输入表单都有一个计时器对象,在其Timer() 事件中的代码关闭表单并启用一个应用程序级的计时器,如果 SYSINFO.CHNGSTATE 变为.T. 以下是CUSTOMER 表单中的计时器对象中的代码 (在该代码中, oTimer 是应用程序级的计时器):
If SYSINFO.CHNGSTATE
Messagebox('Closing form to perform maintenance. ' + ;
'The form will automatically reopen in just a ' + ;
'moment.', 48)
oTimer.Enabled = .T.
Thisform.Release()
Endif SYSINFO.CHNGSTATE
在应用程序启动时, 一个计时器对象被实例化但是被废止的(Disabled). 该计时器 仅在表单因视图处于修改状态而被强制关闭时启用,在它的Timer() 事件event 代码中搜索SYSINFO.CHNGSTATE 是否为.F. 因此它可以重新打开任何因视图处于修改状态而被强制关闭的表单. 以下是其代码在(在类库MYAPP.VCX 中的MyApp计时器类)中:
If Not SYSINFO.CHNGSTATE
This.Enabled = .F.
Do Form CUSTOMER
Endif Not SYSINFO.CHNGSTATE
当然, 这些代码是硬编码来打开CUSTOMER 表单,因为它是本例中指定的表单. 在一个真正的应用程序中, 你可以用一个管理对象在关闭表单将它注册到应用程序的INI文件中, 管理器对象负责再次启动各个表单的备份.
一个用于重建索引的表单, 当用户开始重索引时或已完成时设置SYSINFO.CHNGSTATE 为 .T. 并启动计时器. 正象我们已看到的那样, 设置SYSINFO.CHNGSTATE 通知所有工作站上的所有打开的表单,它需要启动应用程序范围的计时器然后关闭它自己. 重索引表单计时器使用试着以独占方式打开视图的方法来定期检查所有用户已经关闭视图. 一但可以以独占方式打开视图, 视图离线或联线 (首先更新源表),并且 SYSINFO.CHNGSTATE 设置回.F. (通知应用程序级计时器数据输入可以继续). 以下是REINDEX 表单的计时器对象的Timer() 事件的代码:
With Thisform
* Disable 计时器(因此当我们执行这此代码时它不会激活)
* 并看我们是否可以以独占方式打开该视图。
This.Enabled = .F.
.CloseTables()
Select 0
Use LV_CUSTOMER Exclusive
Do Case
* 我们可以打开该视图, 因此再关闭它, 我们自己disable它
* 并转换视图的状态.
Case Used('LV_CUSTOMER')
.CloseTables()
.SwitchState()
* 如果我们还再尝试中, 看我们是否超过了最大时间数。
* 如果是, 取消处理.
Case Seconds() - .nStartTime > .nMaxTime
.cmdOffline.Click()
* 再次 Enable 计时器.
Otherwise
This.Enabled = .T.
Endcase
Endwith
如果 VFP 没有安装在这些文件所在驱动器上的 VFP5 目录中, 修改 Setup.PRG 并按需要修改 lcDataDir 中的目录定义. 运行 SETUP.PRG 来在 DATA 目录中建立VFP 样板数据库文件 TESTDATA. 一个新的叫做 LV_CUSTOMER 的视图和一个叫 SYSINFO.Dbf 的表将被建立.
在运行了 SETUP 后, 运行 MYAPP.PRG. 显示一个 customer 表单的实例 (注意表单标题指明视图是 online 的) 然后从菜单中选择重索引表. 重建索引表表单显示一个亮红色并指明重索引不能处理因为视图是 online (重建索引按钮也将被废止). 单击离线按钮. 注意颜色变为黄色并指明正在等待视图离线. 同时, 联线按导的提示改变为取消; 这允许你在等待时取消处理. 5 秒钟后(客户表单中的计时器时钟), 将会显示一个对话框指明客户表单将被关闭. 然后客户表单关闭, 重索引表单的颜色变为绿色并指明可以进行重建索引处理. 同时, 取消按钮的提示变为联线, 允许你将视图回联, 且重建索引按钮变为可用状态. 客户表单也立即自动打开, 但请注意标题指明视图是 offline. 单击重建索引按钮来重索引 customer 表. 在客户表单中修改一条记录来证明可以进行数据输入. 现在单击联线按钮并注意它自己的重复处理: 客户表单关闭, 重索引表表单指明视图已回联, 然后客户表单在视图联线时重新打开. 注意你对离线视图中的数据的修改现在 reflected 在联线视图中了.
你可能会想当视图离线时是否可以更新源表的结构. 回答是: 可能.VFP 会非常不快.(因此, 你也会非常不快) 如果表的结构修改了,任何基于该表的视图都会受到影响. 例如, 如果 LV_CUSTOMER 是用 SELECT * 命令建立的基于 CUSTOMER 的视图, 在该表中添加或删除一个字段; 你会得到一条错误“基表字段已改变与视图不再匹配” 且视图不能打开. (有趣的是, 如果在修改时视图是离线的, 你可以继续以该视图工作并在 online 模式下打开它并更新到源表. 然而, 一但你将该视图回联并试着打开它时, 你将看到该错误信息).
如果视图的每一个字段是用精确的字段名(例如 SELECT CUST_ID, COMPANY, CONTACT …), 字段可以添加到表中并不影响视图(如果你想通过视图向新字段输入数据,视图当然必须重新定义). 从表中删除一个字段或更名一个字段会致使 “SQL 列<字段名> 未找到” 错误且视图不能打开(如果它是一个联线视图). 如果视图是离线的, 它可以被打开,但 tableupdate() 将失败.
这个故事的教训是: 根据你的视图是如何定义的和对表的结构进行了什么样的修改, 你可能可以在对一个表的离线视图进行数据输入的同时修改该表的结构. 为确保存安全,你应该在表结构改变后重新定义视图.
数据备份是另一个当源表与离线视图未关联时的维护任务.
结论
离线视图扩展了可更新视图的能力, 允许你为你的应用程序添加对于 VFP 3 来说是难于实现的功能. 试运行该文所附的示例程序, 并考虑在你的应用程序中使用离线视图的方法.
[返回] |