VFP 学习、开发漫谈 (18)
今天与大伙聊一聊多用户编程方式下,如何减少数据共享冲突。首先,用户越少,则冲突越少。从这一点来说,每台终端上可以仅允许运行一个应用程度窗口,防止应用程序重复运行。对于应用程序来说,每打开一个应用程序窗口,则意味着增加了一个“用户”。
实际应用中,可采取以下两种方法避免程序重复运行:
方法一:采用 API 函数 FindWindow()。但该方法要求应用程序的标题(即:_Screen.Caption)固定不变,有一定的局限性。
程序代码:
DECLARE Integer FindWindow IN win32api Integer,String IF FindWindow(0,'物料管理系统') # 0 MESSAGEBOX('程序已经运行!',64,'提示') QUIT ENDIF
方法二:采用表独占法。该方法对应用程序的标题无任何限制。若应用程序的标题包含动态内容,如:“版本号”、“用户名”等,则应该采用此方法。
程序代码:
IF !DIRECTORY('C:\MIS') && 建立临时文件夹 MD C:\MIS ENDIF IF FILE('C:\MIS\FLAG.DBF') * 标记文件存在,则独占打开之。若不能打开,则说明程序已运行 TRY USE C:\MIS\FLAG EXCLUSIVE CATCH MESSAGEBOX('系统已经启动!',48,'提示') QUIT ENDTRY ELSE * 标记文件不存在,建立之 CREATE TABLE C:\MIS\FLAG.DBF (UserName C(10)) ENDIF
上述示例中的 Flag.dbf 表不仅可以防止重复运行程序,还可用于保存程序的运行状态。如:本例保存的是登录用户名,这样可以在下次登录时,自动填入用户名。
减少共享冲突的另一个途径是,每个表单窗口仅允许打开一次。在 DO FORM 之前,先判断窗口是否已打开,若窗口已打开,则激活原窗口,否则再执行 DO FORM。以下代码做了精简,完整的代码请参考“漫谈(八)”中的 DoForm()函数。
程序代码:
cForm = 'employee' W_Name = 'frm' + cForm IF !WEXIST(W_Name) && 窗口不存在时,运行表单 DO FORM (cForm) ELSE && 窗口存在时,激活窗口 ACTIVATE WINDOW (W_Name) TOP && 激活窗口 IF WMINIMUM(W_Name) && 最小化时还原 ZOOM WINDOW (W_Name) NORM ENDIF ENDIF
以上所述只是辅助手段,主要手段还得从表的结构设计入手。在设计表时,添加一个类似“制单人”或“用户”这样的身份识别字段,由系统自动维护,用于识别记录是由“谁”添加的。系统采取“谁添加,谁维护”的策略,这样可以避免多个用户同时对一条记录进行修改。也就是说,张三添加的记录只有张三有权修改,李四可以查询,但不能修改。当然,实际应用中还应该授权“管理员”对所有记录的修改权限,防止因人员变动而无法处理相关记录。
默认情况下,系统采取自动锁定。比如,在执行 Replace 命令时,系统会自动对记录进行加锁和解锁,若加锁不成功,系统可能处于无限等待状态(假死机)。我不喜欢这种处理方式,一般采用人工锁定方式,如下所述。
在表单的 Load 事件或数据环境的 BeforeOpenTables 事件中,添加 Set Reprocess to 0 Seconds,取消锁定失败后的自动重试。
自定义 3 个锁定函数:_FLock()、_RLock()、_HLock(),分别用于锁定表、锁定记录和锁定表头。这三个函数的代码大同小异,仅以 _FLock()为例,其代码如下:
程序代码:
* 功能:锁定表----------------------------------------------------------------------------------------------------- * 参数:tcAlias:要锁定的表 * 返回:逻辑型,.t.:锁定成功,.f.:锁定失败 FUNCTION _FLock(tcAlias) LOCAL nSelect,nNum,nMax,lOK nSelect = SELECT() IF TYPE('tcAlias') = 'C' AND !EMPTY(tcAlias) SELECT (tcAlias) ENDIF nNum = 1 && 记录锁定的次数 nMax = 10 && 最多锁定次数,约 5 秒 lOK = .t. && 是否锁定成功 DO WHILE !FLOCK() WAIT WINDOW '正在对表“'+ALIAS()+'”进行锁定,请稍候……' TIMEOUT 0.5 nNum = nNum + 1 IF nNum > nMax IF MESSAGEBOX('无法锁定表“'+ALIAS()+'”,单击“重试”继续执行锁定,单击“取消”中止操作。',37,'提示')= 4 nNum = 1 ELSE MESSAGEBOX('数据库加锁失败,请稍后重试!',16,'提示') lOK = .f. EXIT ENDIF ENDIF ENDDO SELECT (nSelect) RETURN lOK ENDFUNC
上述代码使用 FLOCK() 函数对表进行锁定,若锁定不成功,则再重试 9 次,每次间隔 0.5 秒。若重试 9 次后仍不能锁定,则提示用户选择重新开始锁定,还是放弃。比如,为所有财务部员工加薪 500元,可采用如下代码:
程序代码:
SELECT employee IF _FLock() REPLACE salary WITH salary + 500 FOR department = '财务部' UNLOCK ENDIF
在调用 _FLock() 函数对多个表进行数据处理时,有一定的技巧。比如:要对两个表进行操作,应该先对这两个表进行整体锁定,待锁定成功后再分别进行处理。这样,可以保证数据处理的完整性。不要先锁定一个表,待数据处理完毕后再锁定下一个表。
程序代码:
IF !_FLock('表1') OR !_FLock('表2') && 锁定 2 个表 UNLOCK ALL RETURN ENDIF REPLACE 字段 WITH 值 IN 表1 && 处理表1中的记录 REPLACE 字段 WITH 值 IN 表2 && 处理表2中的记录 UNLOCK ALL && 解除锁定
对表锁定后,应该立即执行数据处理,然后尽快解锁。不要在锁定表后再执行 MESSAGEBOX() 或 INPUTBOX()等执行时间不确定的操作,这些操作应该在锁定表之前执行。在对数据处理的过程中,若出现特殊情况需要中断程序运行时,应该先对表解除锁定,再执行 MESSAGEBOX()反馈信息。
所谓数据冲突,是指多个用户对同一记录进行修改时数据的相互覆盖。若出现了数据冲突,需要用到 CURVAL()、OLDVAL()、GETFLDSTATE()等函数,还需要将冲突提交给用户,由用户决定是选择强制更新,还是放弃操作,相当麻烦。
为了简化处理,我一般采取“谁最后提交,谁的数据有效”的原则,结合“谁添加,谁修改”策略,经实践检验是可行的,但这种方法不是绝对的。具体来说,就是在使用 Replace、Update、Delete等对无缓冲记录进行修改前,不判断数据冲突而直接进行修改。在对缓冲区数据进行更新时,采用 TABLEUPDATE(.t.,.t.) 格式,其中第二个参数.t.表示忽略冲突强制更新。
[ 本帖最后由 liuxingang28 于 2014-4-17 10:00 编辑 ]