1. 为什么表单里改完数据总想“反悔”
做 VFP 表单数据编辑的朋友大概率遇到过这种场景:用户在 Grid 里改了几行、删了一条、又新增了一条,点“取消”的时候你只能重新USE一遍表或者重新SQLEXEC拉一次数据。表一关,Grid 立刻白屏,焦点丢失,用户体验直接崩。更麻烦的是多人共享同一个 DBF 目录时,你还没点保存,别人已经把同一条记录改掉了,冲突检测全靠自己写代码比对。
缓冲表就是为这个场景设计的。它把当前记录复制到 VFP 管理的内存或磁盘区域,原表数据仍然可以被其他人访问;你改的是副本,只有调用TABLEUPDATE()时才尝试锁定并写回原表,写回前 VFP 会检查这条记录有没有被别人动过。想放弃就TABLEREVERT(),副本丢掉,原表纹丝不动。
适合谁用:正在用 VFP 做单表或一对多表单编辑的开发者;用共享文件夹做网络版、想减少锁冲突的维护人员;以及准备把本地 DBF 逻辑迁移到远程视图、CursorAdapter 的进阶用户。核心就三个函数:CURSORSETPROP()开缓冲、TABLEUPDATE()提交、TABLEREVERT()回滚。下面按“开缓冲 → 增删改 → 提交/回滚 → 排错”的顺序,把每一步都落到可复制的代码上。
2. 用 TaoToken 准备一个可对话的验证环境
缓冲逻辑本身不依赖网络,但你在排错时经常需要对照文档、或者让模型帮你解释某段冲突处理代码。我习惯开一个模型对话窗口放在旁边,遇到TABLEUPDATE()返回 .F. 的时候直接把错误号和表结构贴进去问。
访问 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册后,在控制台生成 API Key,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。拿到 Key 之后,模型对话入口在 https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,把报错信息、CURSORSETPROP参数、AERROR()返回的数组内容贴进去,让它帮你判断是锁模式选错还是SET MULTILOCKS没开。
如果你打算长期在 VFP 项目里做代码辅助,比如批量生成表单的保存/回滚逻辑,可以看 Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。接口层面统一走 https://taotoken.net/api ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。这一步只是把“查文档、问报错”的通道准备好,真正跑缓冲还是在你本机的 VFP 里。
3. CURSORSETPROP 参数配置与缓冲模式选择
3.1 Buffering 属性取值对照
CURSORSETPROP("Buffering", eExpression, cAlias)里最关键的就是第二个参数。它决定了缓冲类型和锁定模式,取值含义如下:
| 取值 | 缓冲类型 | 锁定模式 | 行为说明 |
|---|---|---|---|
| 1 | 无缓冲 | — | 默认值,改即写原表 |
| 2 | 行缓冲 | 保守 | 一修改就锁定该记录 |
| 3 | 行缓冲 | 开放 | 修改不锁,指针移动或 TABLEUPDATE 时锁 |
| 4 | 表缓冲 | 保守 | 一修改就锁定该记录,TABLEUPDATE 后释放 |
| 5 | 表缓冲 | 开放 | 修改不锁,TABLEUPDATE 时才锁并更新 |
保守和开放的区别一句话:保守是“我一改就锁住,别人别碰”;开放是“我先改着,提交那一刻才锁”。共享文件夹环境下保守更安全但并发差,开放并发好但冲突要在提交时处理。视图上使用缓冲时 VFP 强制开放锁定。
3.2 开启缓冲的完整片段
*-- 缓冲表初始化:必须在 USE 之后、任何修改之前执行 SET MULTILOCKS ON && 除 Buffering=1 外,这一句必须为 ON SET EXCLUSIVE OFF && 共享方式打开,模拟多用户 USE 教师 IN 0 ALIAS 教师 SHARED *-- 选择表缓冲 + 开放锁定(值 5),适合表单批量编辑 IF CURSORSETPROP("Buffering", 5, "教师") WAIT WINDOW "表缓冲已开启" TIMEOUT 1 ELSE = AERROR(laErr) MESSAGEBOX("开启失败:" + laErr[2]) ENDIF如果你做的是“移动到下一条就自动保存当前行”的录入表单,用行缓冲开放模式(值 3)更合适:
CURSORSETPROP("Buffering", 3, "教师")注意SET MULTILOCKS ON是全局设置,漏掉它CURSORSETPROP会直接返回 .F.。另外nWorkArea传 0 表示对之后打开的所有表生效,一般不建议这么用,容易把只读的临时表也带上缓冲。
4. 增删改验证:一段可本地跑通的完整动作
下面这段代码把新增、修改、删除都做一遍,然后先回滚、再提交,你可以直接粘到命令窗口或 PRG 里跑。前提是有一个教师.DBF,字段随意,比如姓名 C(10)、工资 N(8,2)。
*-- 0. 环境准备 CLOSE DATABASES ALL SET MULTILOCKS ON SET EXCLUSIVE OFF USE 教师 IN 0 ALIAS 教师 SHARED CURSORSETPROP("Buffering", 5, "教师") *-- 记录初始状态,便于对比 LOCAL nOldCount nOldCount = RECCOUNT("教师") ? "缓冲前记录数:", nOldCount *-- 1. 新增一条 APPEND BLANK IN 教师 REPLACE 教师.姓名 WITH "测试新增", 教师.工资 WITH 5000 IN 教师 ? "新增后(缓冲内)记录数:", RECCOUNT("教师") *-- 2. 修改第一条 GO TOP IN 教师 REPLACE 教师.工资 WITH 教师.工资 + 100 IN 教师 ? "修改后(缓冲内)首条工资:", 教师.工资 *-- 3. 删除最后一条 GO BOTTOM IN 教师 DELETE IN 教师 ? "删除标记:", DELETED("教师") *-- 4. 先回滚,验证原表未变 = TABLEREVERT(.T., "教师") ? "回滚后记录数:", RECCOUNT("教师") ? "回滚后首条工资:", 教师.工资 *-- 5. 重新做一遍增删改,这次提交 APPEND BLANK IN 教师 REPLACE 教师.姓名 WITH "测试提交", 教师.工资 WITH 6000 IN 教师 GO TOP IN 教师 REPLACE 教师.工资 WITH 教师.工资 + 200 IN 教师 GO BOTTOM IN 教师 DELETE IN 教师 IF TABLEUPDATE(.T., .F., "教师") ? "提交成功,当前记录数:", RECCOUNT("教师") ELSE = AERROR(laErr) ? "提交失败,错误码:", laErr[1], " 信息:", laErr[2] ENDIFTABLEUPDATE()第一个参数.T.表示强制提交所有记录,第二个参数.F.表示不强制覆盖冲突。如果返回 .F.,用AERROR()拿到错误数组,常见的是记录被他人修改导致冲突。TABLEREVERT(.T., "教师")的.T.表示放弃所有修改,.F.则只放弃当前记录。
跑完这段你会看到:回滚之后记录数和工资都回到初始值;提交之后新增和修改生效,删除的记录带上了删除标记。整个过程没有关闭表,Grid 不会白屏。
5. 本篇常见错排查
5.1 CURSORSETPROP 返回 .F.
九成是SET MULTILOCKS没开。先执行SET MULTILOCKS ON再调用。另一个可能是别名写错,表没打开或别名大小写不匹配。用USED("教师")确认表已打开。
5.2 TABLEUPDATE 返回 .F. 但 AERROR 为空
检查是不是在缓冲开启前就修改了数据。缓冲只对开启之后的修改生效,之前的修改已经写进原表了。另外TABLEUPDATE()不传别名时默认当前工作区,如果你在别的区操作,要显式传别名。
5.3 提交后别人看不到更新
共享文件夹环境下,其他用户的 VFP 有本地缓存。让他们执行=REFRESH()或重新GO TOP触发刷新。如果是远程视图,检查CursorAdapter的UpdateType和KeyFieldList是否配置正确。
5.4 删除后 RECCOUNT 没变
DELETE只是打删除标记,RECCOUNT()统计的是物理记录数,不会减少。要看有效记录数用RECCOUNT()配合SET DELETED ON,或者用COUNT FOR NOT DELETED()。提交后删除标记才真正写入原表。
5.5 行缓冲下移动指针自动提交
这是行缓冲开放模式(值 3)的正常行为:指针离开当前记录时 VFP 尝试锁定并写入。如果你不想让它自动提交,改用表缓冲(值 4 或 5),所有修改攒到TABLEUPDATE()时一起提交。
6. 把缓冲逻辑接进你的表单保存按钮
表单上的“保存”按钮对应TABLEUPDATE(.T., .F., "教师"),“取消”按钮对应TABLEREVERT(.T., "教师")。如果你用的是 CursorAdapter,CA的TableUpdate方法内部已经封装了这些调用,配合缓冲可以实现一键保存,不用手写 SQL。远程数据场景下,Buffering 用 3 或 5,提交时 VFP 会走 CA 的更新逻辑。
需要对照接口文档或者让模型帮你生成表单的保存/回滚代码模板,可以从 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 拿 Key,接入说明在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。长期做 VFP 表单批量改造的话,Coding Plan 入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,把缓冲模板、冲突处理、CA 配置这些重复代码交给它生成,你专注在业务字段和界面逻辑上。
最后留一个我踩过的坑:TABLEUPDATE()提交成功后,缓冲区的修改标记会清掉,但记录指针位置不变;如果你紧接着又改了一条,记得确认缓冲仍然处于开启状态,别在提交后误调了CURSORSETPROP("Buffering", 1)把缓冲关掉。