我正在使用proserve来启用多用户会话。这是我在浏览时双击鼠标触发的代码:
DO WITH FRAME MAIN-FRAME:
IF EMP-BROWSE:NUM-SELECTED-ROWS > 0 THEN
DO:
EMP-BROWSE:FETCH-SELECTED-ROW(1).
FIND CURRENT EMPLOYEE NO-ERROR NO-WAIT.
IF AVAILABLE (EMPLOYEE) THEN
DO:
DO TRANSACTION ON ERROR UNDO, LEAVE:
C-Win:SENSITIVE = NO.
FIND CURRENT EMPLOYEE EXCLUSIVE-LOCK.
MESSAGE STRING(EMPLOYEE.emp-num) + " locked.".
C-Win:SENSITIVE = YES.
END.
RELEASE EMPLOYEE.
END.
ELSE IF NOT AVAILABLE (EMPLOYEE) THEN
DO:
MESSAGE "The employee details is currently in-use in another session. Please try again later." VIEW-AS ALERT-BOX TITLE "System Message".
RETURN NO-APPLY.
END.
ELSE
DO:
MESSAGE "The record has been deleted in another session.".
RETURN NO-APPLY.
END.
END.
END.
场景:
会话A双击浏览记录1。然后它会发出类似"2001 locked"的消息。在那之后,会话B双击浏览记录1,它会在IF NOT AVAILABLE (EMPLOYEE)
块上触发消息。
我的问题是RELEASE EMPLOYEE
代码不应该让会话B访问同一条记录吗?
我也尝试过FIND CURRENT EMPLOYEE NO-LOCK
,并将代码放在DO TRANSACTION
块的内部和外部,但什么也没发生。
编辑:
我应用了这些更改,但在相同的场景中,会话B在ELSE
块上得到消息,即MESSAGE "The record has been deleted in another session.".
。我做错了什么?
当我在MESSAGE STRING(EMPLOYEE.emp-num) + "locked.".
之后添加对新窗口RUN newWindow.w.
的调用时,在会话a上没有关闭新窗口的情况下,我会得到会话B中另一个会话中使用的记录的正确响应。
DO WITH FRAME MAIN-FRAME:
IF EMP-BROWSE:NUM-SELECTED-ROWS > 0 THEN
DO:
EMP-BROWSE:FETCH-SELECTED-ROW(1).
FIND CURRENT EMPLOYEE NO-LOCK NO-ERROR NO-WAIT.
DEFINE VARIABLE iOk AS LOGICAL NO-UNDO.
DEFINE BUFFER myEMPLOYEE FOR EMPLOYEE.
iOk = NO.
DO FOR myEMPLOYEE TRANSACTION:
FIND myEMPLOYEE WHERE myEMPLOYEE.emp-num = EMPLOYEE.emp-num EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
IF AVAILABLE (myEMPLOYEE) THEN
DO:
IF LOCKED (myEMPLOYEE) THEN
DO:
MESSAGE "The employee details is currently in-use in another session. Please try again later." VIEW-AS ALERT-BOX TITLE "System Message".
RETURN NO-APPLY.
END.
ELSE
DO:
C-Win:SENSITIVE = NO.
MESSAGE STRING(EMPLOYEE.emp-num) + " locked.".
C-Win:SENSITIVE = YES.
END.
END.
ELSE
DO:
MESSAGE "The record has been deleted in another session.".
RETURN NO-APPLY.
END.
END.
END.
END.
RELEASE不会做您认为它会做的事情。RELEASE的使用是一个危险信号。这几乎总是意味着编码人员对记录和事务范围问题没有把握。
来自文档:
发布
验证记录是否符合必填字段和唯一索引定义。它清除缓冲区中的记录,并在记录更改后将其合并到数据库中。
注意到定义中没有提到锁吗?
在事务中嵌入用户界面是一个重大错误——这样做本质上保证了以后会出现锁争用问题和可伸缩性问题。
正如Jens所指出的,你的整个方法需要重新思考。
为了正确控制记录和事务范围,最佳实践是非常明确地说明它,并将更新限制在非常紧凑的代码块中。理想情况下,您可以将其封装在一个过程或函数中,如下所示:
function updEmpName returns character ( input empNum as character, input newName as character ):
define buffer employee for employee.
do for employee transaction:
find employee exclsuive lock where employee.employeeNum = empNum no-error.
if locked employee then
return "locked".
else if available employee then
do:
assign
employee.name = newName.
.
return "updated".
end.
else
return "no such employee".
end.
end.
如果您无法将更新放入函数或过程中,则需要"强范围"用于更新记录的缓冲区:
define variable ok as logical no-undo.
define buffer updEmployee for employee.
ok = no.
do for updEmployee transaction:
find updEmployee exclusive-lock where updEmployee.employeeNum = empNum no-error.
if available updEmployee then
assign
/* update whatever fields are needed */
ok = yes
.
end.
"DO FOR updEmployee"被称为"强作用域"。这意味着在该块之外不能有对updEmployee缓冲区的"自由引用"。如果编译器抱怨有问题,那么你需要修复它,因为你的作用域不是你想象的那样。
我认为你有一些事情需要重新考虑。
此行:
FIND CURRENT EMPLOYEE NO-ERROR NO-WAIT.
将导致共享锁定。有些人(包括我)倾向于回避这些。我会换成:
FIND CURRENT EMPLOYEE EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
后一行:
IF AVAILABLE (EMPLOYEE) THEN
将只处理记录的存在,而不是它的锁定状态。总而言之,请考虑在线(F1)帮助:
锁定功能
如果记录对以前的FIND不可用,则返回TRUE值。NO-WAIT语句,因为另一个用户锁定了一条记录。
可用功能
如果您命名的记录缓冲区包含一条记录,则返回TRUE值;如果记录缓冲区为空,则返回FALSE值。
查找。。。无错误
抑制ABL错误或错误消息,否则会发生,并将其转移到error-STATUS系统句柄。如果发生错误,则不执行该语句的操作,并继续执行下一个语句。如果该语句失败,则该语句的任何持续副作用都将被排除。如果语句包含包含其他可执行元素(如方法)的表达式,则这些元素执行的工作可能完成,也可能不完成,这取决于AVM解析表达式元素的顺序和错误的发生。
查找。。。NO-WAIT
如果记录被另一个用户锁定,则会导致FIND立即返回并引发错误条件(除非在同一FIND语句中使用NO-error选项)。例如:
您可以将其更改为(并将ELSE语句更改为类似的内容):
FIND CURRENT EMPLOYEE EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
IF LOCKED(EMPLOYEE) THEN DO:
MESSAGE "Locked by another user".
END.
ELSE DO:
IF AVAILABLE (EMPLOYEE) THEN DO:
MESSAGE "Go ahead and change".
END.
ELSE DO:
MESSAGE "Not available".
END.
END.
请记住,您编写的代码是GUI、数据访问和业务逻辑的混合体。这是一种糟糕的做法。你可以为较小的示例、测试等做这件事,但实际上你应该避免。即使你现在不使用AppServer,你将来也很可能想这样做——如果代码已经有了很好的关注点分离(例如GUI、业务逻辑和数据访问),那么未来的现代化将容易得多
命令FETCH-SELECTED-ROW()
已经使用browse定义中定义的LOCK将浏览中的所选记录放入记录缓冲区,那么您不需要使用FIND
语句来执行此操作。不管怎样,当我需要执行类似您尝试执行的操作时,我会使用以下代码:
DO WITH FRAME {&FRAME-NAME}:
DO iCount = 1 TO brEmployee:NUM-SELECTED-ROWS:
brEmployee:FETCH-SELECTED-ROW(iCount).
IF AVAIL Employee THEN DO:
FIND CURRENT Employee EXCLUSIVE-LOCK.
/* Do whatever I need */
FIND CURRENT Employee NO-LOCK.
END.
ELSE DO:
IF LOCKED(Employee) THEN DO:
MESSAGE 'Record locked!'
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
ELSE DO:
MESSAGE 'Record deleted!'
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
END.
END.
END.
在本例中,FETCH-SELECTED-ROW()
aways返回记录。如果不可用,您可以检查LOCKED
条件。为了避免浏览返回数据库中不存在的记录,可以使用-rereadnolock
会话参数。
但是您使用的是浏览的MOUSE-SELECT-DBLCLICK
事件中的代码。然后,你可以用这样的东西替换代码:
DO WITH FRAME {&FRAME-NAME}:
brEmployee:SELECT-FOCUSED-ROW() NO-ERROR.
IF AVAIL Employee THEN DO:
FIND CURRENT Employee EXCLUSIVE-LOCK.
/* Do whatever I need */
FIND CURRENT Employee NO-LOCK.
END.
ELSE DO:
IF LOCKED(Employee) THEN DO:
MESSAGE 'Record locked!'
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
ELSE DO:
MESSAGE 'Record deleted!'
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
END.
END.
希望能有所帮助。