Welcome

首页 / 软件开发 / VFP / 用vfp与sql server构建Client/Server应用程序(远程视图)(4)

用vfp与sql server构建Client/Server应用程序(远程视图)(4)2007-05-09缓冲(Buffering)

Visual FoxPro 中的缓冲技术

当远端数据下载到客户端时,这些数据就被压入缓冲之中。在客户端用户对数据的各种移动并不反映到数据源,而是在用户确认对数据的变动后,才把各种变动以SQL描述的形式发送到后端。那么为什么不让Visual FoxPro直接一步一动的操控远程数据,就像在不使用缓冲技术控制Visual FoxPro本地数据那样。我想原因在于:

  1. 在 Client/Server 构架的应用中,数据库服务器需要同时处理许多客户的请求,如果有一个客户“直接”控制(锁定)它,多用户的系统就无从谈起了。

  2. Visual FoxPro 通过 ODBC 与远端数据库通讯,如果一步一动,两者之间的通讯肯定会成倍增加,这样既加重了网络负担又加重的数据库服务器的负担。

基于上述原因,Visual FoxPro在远程数据处理时强制使用缓冲技术。我们知道,在Visual FoxPro中缓冲技术与锁结合有四种选择:

  1. 保守式行缓冲。所谓“保守”,就是“编辑时锁定”的意思,“行缓冲”是指“只缓冲处理一笔使用者加以编辑的数据记录”。因此一旦使用这种模式,当编辑动作刚开始,数据源的对应数据记录便被锁定,而且在执行以下两项动作时,数据变动才会被发送:移动数据指针、执行TABLEUPDATE()函数。
    由于在开始编辑时就锁定数据源的对应行,所以这种模式不被远程数据处理采用。

  2. 开放式行缓冲。所谓“开放”,就是“更新时锁定”的意思,“行缓冲”是指“只缓冲处理一笔使用者加以编辑的数据记录”。因此使用这种模式,只有在执行以下两项动作时,数据变动才会被发送,数据源对应行记录才被锁定:移动数据指针、执行TABLEUPDATE()函数。

  3. 保守式表缓冲。所谓“保守”,就是“编辑时锁定”的意思,“表缓冲”是指“缓冲处理整个使用者加以编辑的数据集(光标)”。因此一旦使用这种模式,当编辑动作刚开始,数据源的相关记录集便被锁定,而且在执行以下动作时,数据变动才会被发送:执行TABLEUPDATE()函数。
    由于在开始编辑时就锁定数据源的整个对应表或是记录集,所以这种模式不被远程数据处理采用。

  4. 开放式行缓冲。所谓“开放”,就是“更新时锁定”的意思,“表缓冲”是指“缓冲处理整个使用者加以编辑的数据集(光标)”。因此使用这种模式,只有在执行TABLEUPDATE()函数时,数据变动才会被发送,数据源的相关记录集才被锁定。

好了,我们得到以下结论:在操控远程数据时,Visual FoxPro将对光标采用“开放式行缓冲”或“开放式表缓冲”,默认设置是“开放式行缓冲”。

以后在讨论远程数据处理时,不特别指出,行缓冲就是指开放式行缓冲,表缓冲是指开放式表缓冲。

在“开放式行缓冲”下,因为只对一条被编辑的记录开启缓冲,所以有两种方式可以确认编辑、发送更新:移动指针(在上面的例子中我们已经使用过了)、TABLEUPDATE()函数。不知您能否理解“指针移动确认更新”的意思?我是这样理解的:行缓冲只对一条被编辑的记录有用,如果移动指针,那就必须确认更新(如果数据有变动),因为如果不确认更新(释放缓冲区),Visual FoxPro便没法为下一行制定缓冲区了——记住:这是行缓冲。

“在开放式表缓冲”下,Visual FoxPro对整个记录集开启缓冲区,所以移动指针并不会确认更新。只有使用TABLEUPDATE()函数了。

乍一看,“开放式行缓冲”比“开放式行缓冲”需要更少的系统资源,好像是个好选择,我看不尽然:

  1. 有些Visual FoxPro的命令或函数会“不由自主”地移动指针,使得开发人员对更新的确认失去控制。

  2. 有时对数据的维护是成批的。

下面的代码说明了怎样控制缓冲:

USE VCustomers
CURSORSETPROP("Buffering", 3, "VCustomers")
*设定VCustomers的缓冲模式为“开放式行缓冲”。由于这时Visual FoxPro的默认设置,这一句可省略。
USE VOrders
CURSORSETPROP("Buffering", 5, "VOrders")
*设定VOrder的缓冲模式为“开放式表缓冲”。

以缓冲理解更新冲突

在图8中我在发生更新错误时提示:“原先Phone=030-0074321,现在Phone=00000,两者不等……”,那么这个原先是“什么时候”,“现在”又是怎样的概念?(假设进程一、二采用行缓冲模式、用“关键字和已更新字段”检测冲突)。

“原先”是指:进程一中视图被打开或是最近一次刷新成功时刻SQL Server数据表中的记录值。让我们先停下来,怎样才会刷新光标和缓冲呢?

  1. 远程视图光标被打开(无论使用行缓冲还是表缓冲)。

  2. 成功执行REQUERY()函数(无论使用行缓冲还是表缓冲)。

  3. 发送更新(无论成功与否)

您可以想象:进程一打开远程视图,Visual FoxPro自动把这个时刻SQL Server的数据值压入缓存中,这时进程一认为:我对SQL Server上数据的修改应建立在这个基础上,即Phone=030-0074321,如果这个基础不存在了,这发生更新错误。

在进程一还没有把它在客户端对Phone的修改发送到数据源的时候,进程二也读取了SQL Server的数据,注意这时进程二认为:我对SQL Server上数据的修改应建立在这个基础上,即Phone=030-0074321。于是进程二修改Phone为00000,并在进程一之前确认的数据变动,这时是不会发生更新冲突的,因为进程二修改数据的依据是成立的。

进程一慢慢吞吞地把数据改为了123456,发送更新。这时问题来了:进程一告诉SQL Server这样修改数据:

sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3", N"@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)", N"123456 ", "ALFKI", N"030-0074321"

我把参数填入,您就能很清楚地看到问题所在:

UPDATE dbo.Customers SET Phone="123456" WHERE CustomerID="ALFKI" AND Phone="030-0074321"

看到没有:Visual FoxPro自动的把缓冲里的Phone=030-0074321(在BROWSE 窗口中您已经把Phone改为了123456,Visual FoxPro“早有预谋”,把原始数据存在缓冲中,任你表面变化万次——我都不怕)拿出来并结合关键字作为更新依据,然而由于进程二已经修改了Phone的值,在SQL Server 中哪里还会有存在符合条件 CustomerID= "ALFKI" AND Phone="030-0074321"的行了,只有CustomerID= "ALFKI" AND Phone="00000"的记录行了。于是SQL Server 告诉 Visual FoxPro找不到目标记录,Visual FoxPro就对用户说:更新冲突。

所以,缓冲作用是就在这里:客户机与服务器通过 ODBC 这个翻译“传情达意”——但 ODBC 很苯——只能传一些SQL语句。事实上任何对数据的变动,都可归结为:Insert、Update、Delete。SQL语句与Visual FoxPro的命令函数有很大的不同——目标定位必须依靠条件语句(Where 子句)(Visual FoxPro可以很容易定位到第N行);缓冲为这些至关重要的定位条件提供了依据,没有缓冲就无法生成定位语句!

确认更新、放弃更新

确认更新

上文我们多次提到确认更新有基本上算是两种方式:移动指针、使用TABLEUPDATE()函数。移动指针只能在“开放式行缓冲“下使用,并且开发人员对法的可控性较差,一般用于交互式工具中,如上文我们使用过的SQL Server 的Enterprise Manager工具。这里我们只讨论TABLEUPDATE()函数。

在开放式行缓冲下使用TABLEUPDATE()函数:

  1. 语法:TABLEUPDATE(0[,lForce][,nWorkAear|cTableAlias])

  2. 返回值:更新成功——.T.,更新失败——.F.

  3. 必选参数:0。代表只更新当前记录到数据源——这里是记录缓冲,当然是:“只更新当前记录到数据源”。

  4. 可选参数——lForce。默认为.F.,指:如果发生更新错误就确认更新错误,本函数返回.F.;如果设此参数为.t., 表示发生更新错误时,以本客户端的新值为准,覆盖网络上被确认已经的其他用户的更新,如果覆盖成功,本函数返回.T.。
    缺省表示本参数时取默认值。
    该参数设为.T.的实质就是临时改变更新冲突的检测方式为“关键字段”,所以只要关键字不发生冲突,就不会发生更新冲突,本客户端的新值将覆盖其他用户做的变更。

  5. 可选参数——nWorkAear|cTableAlias。表示实行TABLEUPDATE()的工作区,缺省表示对当前工作区有效。

例如:

USE VCustomers
REPLACE PHONE WITH "123456"
?TABLEUPDATE(0)
*返回 .T.更新成功,反之失败。

USE VCustomers
REPLACE PHONE WITH "123456"
?TABLEUPDATE(0,.t.,"VCostomers")
*由于lForce设置为.t.,Visual FoxPro 临时修改更新检测方式为“关键字段”方式,所以只要关键字CustomerID不发生冲突,即使其他字段已经被其他用户修改,Visual FoxPro也不会检测,Visual FoxPro将强制覆盖其它用户做的修改。
*在“关键字段和已修改字段”的冲突检测方式下:Visual FoxPro向SQL Server发送如下语句:
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"123456", "ALFKI"

在开放式表缓冲下使用TABLEUPDATE()函数:

  1. 语法:TABLEUPDATE(nRows[,lForce][,nWorkAear|cTableAlias][,cErrorArray])

  2. 返回值:更新成功——.T.,更新失败——.F.

  3. 必选参数——nRows。可以有两种取值:1、2。假设用户对光标的第 1、2、4、5、7条记录作了修改,如果更新变动,第4条记录上将发生更新冲突。现在,执行本函数,Visual FoxPro将依次发送五条UPDATE-SQL描述:
    设本参数为1时,执行到第4条记录时发生更新冲突,Visual FoxPro将停止发送的5、7条记录的更新描述,并使本函数返回.F.值,Visual FoxPro的记录指针停留在第四条记录上。
    设本参数为2时,执行到第4条记录时发生更新冲突,Visual FoxPro将继续发送的5、7条记录的更新描述,并使本函数返回.F.值,Visual FoxPro的记录指针最终停在最后一条被修改的记录上(这里是第7条记录)。如果可选参数——cErrorArray存在,Visual FoxPro将把发生更新错误的记录号(RECNO())写入该数组,如果第5条记录也发生冲突,那么该数组将是一个一列两行的数组,cErrorArray[1]=4、cErrorArray[2]=5

  4. 可选参数——lForce。默认为.F.,指:如果发生更新错误就确认更新错误,本函数返回.F.;如果设此参数为.t., 表示发生更新错误时,以本客户端的新值为准,覆盖网络上被确认已经的其他用户的更新,如果覆盖成功,本函数返回.T.。
    缺省表示本参数时取默认值。
    该参数设为.T.的实质就是临时改变更新冲突的检测方式为“关键字段”,所以只要关键字不发生冲突,就不会发生更新冲突,本客户端的新值将覆盖其他用户做的变更。

  5. 可选参数——nWorkAear|cTableAlias。表示实行TABLEUPDATE()的工作区,缺省表示对当前工作区有效。

  6. 可选参数——cErrorArray。这是一个一列数组,且只有当必选参数nRows为2时有效,这时它记录着发生更新冲突的记录的记录号;如果没有发生任何更新冲突或是当必选参数nRows不为2时,本数组为一行一列,值为-1。

举个例子:(假设使用“关键字段和已修改字段”作为更新冲突检测方案)

USE VCustomers
CURSORSETPROP("Buffering", 5, "VCustomers")
REPLACE Phone with "9999" next 4
*将第1、2、3、4、条记录的Phone改为9999
BROWSE

*使用 SQL Server 的Query Analyzer 制造更新冲突
*启动 SQL Server Query Analyzer,登录Northwind 数据库
*输入如下语句并执行:
update customers set phone="00000" where customerid="ANATR" or customerid="ANTON"

情况一:TABLEUPDATE(1,.F.,"Vcustomers")

*返回Visual FoxPro
?TABLEUPDATE(1,.F.,"Vcustomers")
*由于记录 2更新时发生冲突,函数返回.F.
?recno("Vcustomers")
*指针停在第2条记录上
?Aerror(err)
用Aerror函数取得Visual FoxPro错误信息存入err数组中
?err(1)
*错误号:1585
?err(2)
*错误信息:“更新冲突”

*回到SQL Server Query Analyzer
*输入如下语句并执行:
select customerid,phone from customers
*您将看到:第一条记录Phone的值已经被Visual FoxPro的客户端修改了,值是:9999。而后三条记录没有发生变化。说明Visual FoxPro依次发送SQL描述到SQL Server时,遇到更新错误就停止继续往下工作。
事实上,查看 SQL Server 的 Profiler 工具也证明了以上论述:
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3", N"@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)", N"9999 ", "ALFKI", N"bbbb "
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3", N"@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)", N"9999 ", "ANATR", N"1234 "//此处发生更新错误,Visual FoxPro停止往下工作

情况二:TABLEUPDATE(1,.T.,"Vcustomers")

*返回Visual FoxPro
?TABLEUPDATE(1,.T.,"Vcustomers")
*函数返回.T.
?recno("Vcustomers")
*指针停在第4条记录上

*回到SQL Server Query Analyzer
*输入如下语句并执行:
select customerid,phone from customers
*您将看到:头四条记录Phone的值已经被Visual FoxPro的客户端修改了,值是:9999。按理说第2条记录被更新时会发生冲突,但由于Visual FoxPro临时变更了更新冲突的检测方案为“关键字段”,这样原本应该能检测到的冲突被忽略了,Visual FoxPro客户端的新值强行覆盖其它客户端的修改。
事实上,查看 SQL Server 的 Profiler 工具也证明了以上论述:
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"9999 ", "ALFKI"
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"9999 ", "ANATR"
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"9999 ", "ANTON"
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"9999 ", "AROUT"

情况三:TABLEUPDATE(2,.F.,"Vcustomers",Arry)

*返回Visual FoxPro
?TABLEUPDATE(2,.F.,"Vcustomers",Arry)
*参数nRows设为2,即使记录 2、3发生更新冲突,Visual FoxPro仍然继续往下执行,但参数函数返回.F.
?recno("Vcustomers")
*指针停在第4条记录上
?Aerror(err)
用Aerror函数取得Visual FoxPro错误信息存入err数组中
?err(1)
*错误号:1585
?err(2)
*错误信息:“更新冲突”
?Arry[1]
*2
?Arry[2]
*3
*Arry返回发生更新错误的记录号

*回到SQL Server Query Analyzer
*输入如下语句并执行:
select customerid,phone from customers
*您将看到:第一条、第四条记录Phone的值已经被Visual FoxPro的客户端修改了,值是:9999。而第二条、第三条记录没有发生变化。说明Visual FoxPro依次发送SQL描述到SQL Server时,遇到更新冲突忽略之,继续往下工作。
事实上,查看 SQL Server 的 Profiler 工具也证明了以上论述:
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3", N"@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)", N"9999 ", "ALFKI", N"cccc "
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3", N"@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)", N"9999 ", "ANATR", N"cccc "//发生更新冲突,Visual FoxPro继续往下执行
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3", N"@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)", N"9999 ", "ANTON", N"cccc "//发生更新冲突,Visual FoxPro继续往下执行
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND Phone=@P3", N"@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(24)", N"9999 ", "AROUT", N"cccc "

情况四:TABLEUPDATE(2,.T.,"Vcustomers",Arry)

*返回Visual FoxPro
?TABLEUPDATE(2,.T.,"Vcustomers",Arry)
*但参数函数返回.T.
?recno("Vcustomers")
*指针停在第4条记录上
?Arry[1]
*-1
*没有发生更新错误

*回到SQL Server Query Analyzer
*输入如下语句并执行:
select customerid,phone from customers
*您将看到:所有记录Phone的值已经被Visual FoxPro的客户端修改了,值是:9999。按理说第2条、第3条记录被更新时会发生冲突,但由于Visual FoxPro临时变更了更新冲突的检测方案为“关键字段”,这样原本应该能检测到的冲突被忽略了,Visual FoxPro客户端的新值强行覆盖其它客户端的修改。
事实上,查看 SQL Server 的 Profiler 工具也证明了以上论述:
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"9999 ", "ALFKI"
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"9999 ", "ANATR"
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"9999 ", "ANTON"
sp_executesql N"UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2", N"@P1 nvarchar(24),@P2 varchar(50)", N"9999 ", "AROUT"

放弃更新

如果要“放弃客户端对光标已经实施的变动”,该怎么办呢?这很简单,请使用TABLEREVERT()函数。这里有一个概念很重要:任何情况下执行本函数均不都会与远程数据源发生通讯,Visual FoxPro只是从缓冲中把原先的数值取回填写入光标中。

那么Visual FoxPro是怎样从缓冲中取回数据的呢?您可以用OLDVAL()函数得到相同的效果,它的用法这里暂不介绍!

在开放式行缓冲下使用TABLEREVERT()函数:

  1. 语法:TABLEUPDATE(.f.[,nWorkAear|cTableAlias])

  2. 返回值:1。如果当前记录没有被修改,则返回0。

  3. 可选参数——nWorkAear|cTableAlias。表示实行TABLEREVERT()的工作区,缺省表示对当前工作区有效。

例如:

USE VCustomers
CURSORSETPROP("Buffering", 3, "VCustomers")
REPLACE Phone with "9999"
?VCustomer.phone
*9999
?TABLEREVERT(.F.,"VCustomers")
*返回1
?VCustomer.phone
*123456

USE VCustomers
CURSORSETPROP("Buffering", 3, "VCustomers")
?TABLEREVERT(.F.,"VCustomers")
*返回0

在开放式表缓冲下使用TABLEREVERT()函数:

  1. 语法:TABLEUPDATE(lAllRows[,nWorkAear|cTableAlias])

  2. 返回值:放弃更新的记录数目

  3. 必选参数——lAllRows。默认值为.F.,表示对当前记录放弃更新;本参数设定为 .T.,放弃更新所有被修改过的记录。

  4. 可选参数——nWorkAear|cTableAlias。表示实行TABLEREVERT()的工作区,缺省表示对当前工作区有效。

USE VCustomers
CURSORSETPROP("Buffering", 5, "VCustomers")
REPLACE Phone with "9999" next 4
*将第1、2、3、4、条记录的Phone改为9999
BROWSE
go 2
?TABLEREVERT(.F.,"VCustomers")
*1
?TABLEREVERT(.T.,"VCustomers")
*3