本人在网上听说过张老师写有关VFP的书非常好,于是慕名买了一本《Visual Foxpro 软件开发模式与应用案例》,昨晚抽了一些时间看了点,无意间发现了该书的例程代码中的“SET DELETED ON/OFF”命令使用不当,这一错误使系统留下严重的隐患。
书中第18页:
IF NOT DBUSED("dbMain")
&&打开数据库
OPEN DATABASE dbMain EXCLUSIVE
ENDIF
SET DATABASE TO dbMain
IF NOT USED("tblOperator")
&&打开操作员表
USE tblOperator ALIAS tblOperator IN 0
ENDIF
SELECT tblOperator
SET DELETED ON
IF NOT USED("tblDepartment")
&&打开部门表
USE tblDepartment ALIAS tblDepartment IN 0
ENDIF
SELECT tblDepartment
SET DELETED ON
IF NOT USED("tblFormula")
&&打开公式表
USE tblFormula ALIAS tblFormula IN 0
ENDIF
SELECT tblFormula
SET DELETED ON
IF NOT USED("tblPerson")
&&打开员工表
USE tblPerson ALIAS tblPerson IN 0
ENDIF
SELECT tblPerson
SET DELETED ON
IF NOT USED("tblSalaryItem")
&&打开工资项目表
USE tblSalaryItem ALIAS tblSalaryItem IN 0
ENDIF
SELECT tblSalaryItem
SET DELETED ON
SET ORDER TO fldSort
IF NOT USED("tblTally")
&&打开账套名称表
USE tblTally ALIAS tblTally IN 0
ENDIF
SELECT tblTally
SET DELETED ON
IF NOT USED("tblBank")
USE tblBank ALIAS tblBank IN 0
ENDIF
SELECT tblBank
SET DELETED ON
还第109页的代码中,作者对每一个表都分别执行了“SET DELETED ON”,虽然在这两处的代码中多次执行这一命是不会对运行结果是不会有影响的,但令我感到作者这个命令的理解产生了怀疑。“SET DELETED ON | OFF”是决定使用范围子句处理记录(包括在相关表中的记录)的命令是否忽略标有删除标记的记录。SET DELETED 的作用域是当前数据工作期。从以上代码可以看出对作者把SET DELETED 的作用域看成了是当前工作区,所以在每一个表的工作都使用了“SET DELETE ON”。虽然在这里是不会对当前运行有影响,但在一定的情况下对该命令使用不当会发生问题。
在第141页代码证实了我对作者的怀疑是对的。
SELECT tblStock
*!* 因为在更新tblStock表时,要依据表中当前现存的药品数量加上新开单的药品数量
*!* 或修改单的药品变动数量来计算最新库存数量,所以锁定整个表,防止其他用户同
*!* 时根据相同的库存数量计算,而不是依据最新的库存数量。
WAIT WINDOW "尝试锁定表... 按Esc键取消!" NOWAIT
IF NOT FLOCK()
WAIT WINDOW "表锁定失败,当前进货单未能保存,请稍后再试!" TIMEOUT 5
ROLLBACK
RETURN .F.
ELSE
&&锁定成功
SELECT curIn
SET DELETED OFF &&处理已经删除的记录,对于这些数据要从tblStock中减去
SCAN
*!* OLDVAL()为空表示是新增加记录,否则为更新记录SELECT INTO CURSOR
nOldIn_Num=IIF(ISNULL(OLDVAL("In_Num"))=.T.,0,OLDVAL("In_Num"))
lIsDeleted=.F.
*!* 判断删除的是否是已经保存过的记录,如果是则要从tblStock表中减去
IF DELETED() AND ISNULL(OLDVAL("Product"))=.F.
lIsDeleted=.T.
ENDIF
SELECT tblStock
LOCATE FOR ALLTRIM(tblStock.Product)==ALLTRIM(curIn.Product)
*!* 如果找到记录,表示该药品已经存在于库存中,所以实际库存应该是当前库存+当前单子中药品的数量。
IF FOUND()
nCurProductNum=IIF(ISNULL(CURVAL("ProductNum"))=.T.,ProductNum,CURVAL("ProductNum"))
IF lIsDeleted=.T.
REPLACE tblStock.ProductNum WITH nCurProductNum-nOldIn_Num
ELSE
*!* 保存临时表数据时,如果数据已经保存过,则表中的最新值和OLDVAL( )函数的返回值将相同,
*!* 使用curIn.In_Num-nOldIn_Num,这样可以防止在用户再按下保存按钮时不至于在tblStock中
*!* 重复增加(保存)数据
REPLACE tblStock.ProductNum WITH nCurProductNum+(curIn.In_Num-nOldIn_Num)
ENDIF
ELSE &&没有找到,则追加当前药品到tblStock表中
IF lIsDeleted=.F.
INSERT INTO tblStock (Product,ProductNum) VALUES ;
(curIn.Product,curIn.In_Num)
ENDIF
ENDIF
SELECT curIn
ENDSCAN
ENDIF
代码中在“curIn”的工作区中执行了“SET DELETED OFF”,如果这个命令只对“curIn”的工作区起作用那是没错的,可是,这个命令对整个当前数据工作期都起作用,还会影响到在“tblStock”工作区里也没忽略做了删除标记的记录,当“tblStock”里有做了删除标记和没做删除标记的两条记录的“Product”字段值相同,且有删除标记的记录的存储顺序在前面的,接下来的“LOCATE”会定位到有删除标记的那条记录,那下面的操作就会使用数据产生混乱了。书中例程是很容易出现这种情况的,首先,例程中本来没有控制产品的名称重名,更不可能有控制不管有没有删除标记的记录是否有重名。其次,系统开始使用时必须设置商品,很容易会把一个商品加了上去,又删除了,再加回上去,最后定案使用。
写于2006.6.2