数据共享程序设计
如果创建的应用程序在网络环境中的多台计算机上运行,或者一个表单的多个实例对相同的数据进行访问,这时就需要进行共享访问程序设计。共享访问不仅能为用户使用数据和数据共享提供更有效的方法,而且可以在必要时对访问进行限制。
VFP支持如下功能:对数据的共享或独占访问;锁定选项;数据工作期;记录缓冲和表缓冲以及事务处理。尽管这些功能主要应用在共享环境里,但在单用户环境下也可以使用。
一、控制对数据的访问
因为访问数据是在文件里进行,所以有效的数据管理首先从控制这些文件开始。您需要选择访问数据的方式,还要选择在什么时间、如何限制对数据的访问。
1、访问数据
在共享环境中,可以用两种方式访问数据:从独占文件中访问或从共享文件中访问。若打开一个共享访问的表,其他用户也可以对该文件进行访问;若打开一个独占访问的表,其他用户就不能对该表进行读写。独占访问不具备在网络环境中共享数据的许多优点,所以应该避免使用。
1)以独占方式使用表
打开一个文件最严格的限制方式就是独占方式。通过界面打开一个表时,默认情况下是独占使用的,也可以利用VFP命令明确地以独占方式打开一个表。
如果要打开一个独占使用的表,可以在命令窗口中输入如下命令:
SET EXCLUSIVE ON
USE cMyTable
或者在命令窗口中输入:
USE cMyTable EXCLUSIVE
下列命令要求以独占方式打开一个表:
·ALTER TABLE
·INDEX:当创建、添加或删除一个复合索引标志时。
·INSERT [BLANK]
·MODIFY STRUCTURE:如果要以此命令更改一个表结构,必须以独占方式打开该表。但是,当以共享方式打开这个表时,只能在只读方式下使用此命令。
·PACK
·REINDEX
·ZAP
在一个共享表中执行上述命令,VFP将返回出错信息:“文件必须以独占方式打开”。
可以使用FLOCK()函数限制对表的访问。如果使用FLOCK()锁定表,则其它用户不能对该表进行写操作,但可以读该表。
2)以共享访问方式使用表
以共享方式打开一个表时,多个工作站可以同时访问该表。通过界面打开表时,可以不理会SET EXCLUSIVE默认的ON设置,而明确地用VFP命令打开一个表供共享使用。
若要打开一个共享使用的表,可以在命令窗口中输入下列命令:
SET EXCLUSIVE OFF
USE cMyTable
或者:
USE cMyTable SHARED
当在一个共享表中添加或更改数据时,必须首先锁定要更改的记录或整个表。对一个以共享方式打开的表或记录,可通过下列方式进行锁定:
·使用命令来自动锁定记录或表。
·用记录和表锁定函数,人工锁定一个或多个记录。
·用CURSORSETPROP()函数设置缓冲。
与共享表相关的备注和索引文件也是以共享方式打开的。如果应用程序所使用的表只用于查找数据,而该应用程序的所有用户都要访问它,那么将该表标记为只读可以提高性能。
2、锁定数据
如果要共享访问文件,必须通过锁定表和记录来对这种访问进行管理。锁定不同于访问允许权限,它既可以对数据进行长期控制,也可以短期控制。VFP提供自动和人工锁定方式。
1)选择记录或表锁定
无论是哪一种锁定方式,目的都是为了防止两个用户同时写一个记录。表锁定用来防止其他用户向表中写入,但允许读整个表。由于表锁定阻止其他用户更新表中的记录,因而很少使用。
2)人工或自动锁定
除了选择记录锁定还是表锁定,还可以选择自动锁定或人工锁定,许多VFP命令在执行之前都会自动锁定一个记录或一个表,如果成功锁定了记录或表,则继续执行该命令,然后再解锁。下面列出了自动锁表和记录的命令:
命令 锁定范围
——————————————————————————
ALTER TABLE 整个表
APPEND 整个表
APPEND BLANK 表头
APPEND FROM 整个表
APPEND FROM ARRAY 表头
APPEND MEMO 当前记录
BLANK 当前记录
BROWSE、CHANGE和EDIT 一旦开始编辑字段,锁定对象为当前记录和相关表中别名字段的所有记录
CURSORSETPROP() 取决于参数
DELETE 当前记录
DELETE NEXT 1 当前记录
DELETE RECORD n 记录n
DELETE 删除多个记录 整个表
DELETE-SQL 当前记录
GATHER 当前记录
INSERT 整个表
INSERT-SQL 表头
MODIFY MEMO 编辑开始时,锁定当前记录
READ 当前记录和别名字段的所有记录
RECALL 当前记录
RECALL NEXT 1 当前记录
RECALL RECORD n 记录n
RECALL 恢复多个记录 整个表
REPLACE 当前记录和别名字段的所有记录
REPLACE NEXT 1 当前记录和别名字段的所有记录
REPLACE RECORD n 记录n
REPLACE 替换多个记录 整个表和别名字段的所有记录
SHOW GETS 当前记录和别名字段引用的所有记录
TABLEUPDATE() 取决于缓冲
UPDATE 整个表
UPDATE-SQL 整个表
3)记录锁定的特点
锁定记录的命令比锁定表的命令破坏性要小。锁定一个记录时,其他用户仍然可以添加或删除其他记录。如果记录或表已经被其他用户锁定,或者表已经被其他用户用独占方式打开,则锁定记录或表的操作失败。如果不能锁定记录,则尝试锁定当前记录的命令将返回出错信息“其他用户正在使用记录”。
BROWSE、CHANGE、EDIT和MODIFY MEMO命令只有在编辑时才锁定记录。如果您正在编辑来自相关表记录中的字段,那么该相关记录可能会被锁定。如果当前记录或相关记录已被其他用户锁定,则锁定失败。如果锁定成功,则可以编辑该记录;在移到另一个记录或激活其他窗口时,记录解锁。
4)表头和表锁定的特点
一些VFP命令锁定整个表,而有些命令则只锁定表头。表锁定命令比表头锁定命令更加严厉。锁定表头时,其他用户不能添加或删除记录,但可以修改字段内的数据。
当发出APPEND BLANK命令时,用户可以共享表而不发生冲突,但是当其他用户也向该表追加一个空白记录时,就会出错。出错信息是:“其他用户正在使用文件”。这个信息表明有两个或两个以上的用户在同时执行APPEND BLANK命令。如果无法锁定一个表,则锁定整个表的命令将返回一个错误信息:“其他用户正在使用文件”。要取消正在进行的锁定操作,可按[ESC]键。
5)自动锁定示例
在下面的示例中,即使customer表是以共享方式打开的,用户为了从其他表追加记录也将自动锁定整个表。
SET EXCLUSIVE OFF
USE customer
APPEND FORM oldcust FOR state="OPEN"
6)人工锁定
您可以用锁定函数人工锁定一个记录或一个表。这些函数有:
RLOCK() LOCK() FLOCK()
RLOCK()函数等同于LOCK()函数,都可以锁定一个或多个记录;FLOCK()锁定一个文件。LOCK()和RLOCK()函数可以用户锁定表头,如果把0作为记录编号提供给LOCK()或RLOCK(),而且测试表明表头没有锁定,则该函数将锁定表头并返回.T.。
如果锁定一个记录或一个表,那么一定要尽快解锁,以便其他用户访问。解锁可使用UNLOCK命令。
这些人工锁定函数可以完成如下操作:
*测试记录或表的锁定状态。
*如果测试表明该记录没有锁定,则将该记录锁定并返回.T.。
*如果该记录或表不能锁定,则根据SET REPROCESS的当前设置,再次进行锁定。
*返回.T.或.F.,表明锁定尝试是否成功。
注意:如果不想锁定记录,而测试该记录的锁定状态,请使用ISRLOCKED()或ISFLOCKED()函数。
如果锁定记录或表的操作失败,SET REPROCESS命令和您当前的错误处理例程将确定是否再次尝试锁定。SET REPROCESS会影响不成功的锁定结果,因此可以利用SET REPROCESS来控制锁定尝试的次数和时间。
下面的示例以共享方式打开customer表,并用FLOCK()函数锁定该表。如果该表锁定成功,则REPLACE ALL将更新该表中的所有记录,UNLOCK对该文件解锁;如果其它用户已经锁定该文件或文件中的记录,现在还不能锁定该表,则显示一条信息。
SET EXCLUSIVE OFF
SET REPROCESS TO 0
USE customer &&打开共享表
IF FLOCK()
REPLACE ALL contact; &&替换并解锁
WITH UPPER(contact)
UNLOCK
ELSE &&输出信息
WAIT "文件正被其他用户使用"
ENDIF
3、解锁数据
在共享环境下锁定记录或文件并完成了相应的数据操作后,要及时解锁。解锁的方法有很多种,在某些情况下,只需简单的移动到下一个记录就能解锁,有些情况则需要明确的解锁命令。
要解锁被自动锁定的记录,只需移动记录指针,甚至在设置了MULTILOCKS ON的情况下也是如此。而对于人工锁定的记录,则必须对记录明确地解锁。
下面列出了对人工和自动的锁定记录和表进行解锁的各种命令:
命令 效果
——————————————————————————————————
UNLOCK 解锁当前工作区内的记录和文件
UNLOCK ALL 对所有工作区的记录和文件解锁
SET MULTILOCKS OFF 建立新锁定的同时自动解锁当前锁定
FLOCK() 在锁定文件之前解锁文件中所有记录
CLEAR ALL、CLOSE ALL、USE、QUIT 对所有文件和记录解锁
END TRANSACTION 对所有的自动锁定项解锁
TABLEUPDATE() 在更新表之前解锁所有锁定项
注意:如果记录在UDF中被自动锁定,那么当移开记录指针然后又移回该记录时,锁定将会被解除。可以使用表缓冲解决这个问题。
4、使用数据工作期
为确保共享环境中的每个用户都具有安全的、正确的环境,确保表单的多个实例能独立操作,VFP提供了数据工作期。
数据工作期是对当前动态工作环境的描述。可以将数据工作期看成是一个小型数据环境,这个数据环境是在一台机器上运行一个开发的VFP工作期。每个数据工作期包括:
* 表单的数据环境中的各项备份
* 表示打开的表、索引和关系的临时表
请考虑当在多用户应用程序的各自工作站同时打开相同的表单时,会发生什么,这样可以很容易理解数据工作期的概念。这种情况下,每个工作站运行一个独立的VFP工作期,因此有自己的工作区设置以及自己表示打开的表、索引和关系的临时表。
但是,如果您在一个机器上,同一个VFP工作期,打开单个项目中的同一个表单的多个实例,则这些表单共享默认的数据工作期,它代表了单个的动态工作环境。在同一个VFP工作期中打开的表单的每一个实例使用相同的工作区设置,并且在一个表单实例中移动工作区中的记录指针会自动影响相同表单的其他实例。
1)使用私有数据工作期
如果想更多的控制表单的多个实例,可以使用私有数据工作期。当表单使用私有数据工作期时,VFP为应用程序创建的表单、表单集或工具栏中的每个实例创建一个新的数据工作期。每个私有数据工作期包括:
* 表单的数据环境中每个表、索引和关系的独立备份。
* 数目不限的工作区。
* 独立于表单基表的每个备份表的记录指针。
可用数据工作期数目只受系统内存和磁盘空间的限制。
通过设置表单的DataSession属性可实现私有数据工作期,DataSession属性有如下两个设置:
* 1-默认的数据工作期(默认设置)
* 2-私有数据工作期
如果要使用私有数据工作期,请选择下列选项之一:
* 在“表单设计器”中,将表单的DataSession属性设置为“2-私有数据工作期”。
* 在代码中将DataSession属性设置为2。例如,可输入:
frmMyForm.DataSession=2
注意:只能在设计时设置DataSession属性。运行时DataSession为只读属性。
当表单使用的是私有数据工作期时,在单个机器上,单个的VFP工作期中打开表单的每个实例使用自己的数据环境。使用私有数据工作期类似于在不同的工作站上同时运行同一个表单。
2)识别数据工作期
每个私有数据工作期是单独识别的。可以在“数据工作期”窗口中查看每个数据工作期的内容。也可以通过Load事件代码中的命令改变数据工作期的说明。
使用DataSessionID的运行时属性,可以查看每个数据工作期的识别号。下面的示例显示名为frmMyForm的表单的DataSessionID属性。
DO FORM frmMyForm
?frmMyForm.DataSessionID
如果使用NAME子句激活表单,可使用该表单的名称访问DataSessionID属性,如下面的代码所示:
DO FORM frmMyForm NAME one
?one.DataSessionID
DataSessionID属性只是用来识别特殊的数据工作期。不要更改一个表单实例的DataSessionID属性,当更改DataSessionID属性时,与数据绑定的控件会丢失它们的数据源。
3)使用多个表单实例更新数据
私有数据工作期生成各自独立的工作区,工作区包含了表单的表、索引和关系的独立备份,表单的每个备份引用了相同的基表和索引文件。当用户从表单的一个实例中更新记录时,更新该表单引用的基表。当定位到更改的记录时,就可以看到表单的另一个实例所做的更改。
如果在一个私有数据工作期对记录或表进行了锁定,其他私有数据工作期就不能再进行锁定。例如,数据工作期1的用户锁定了一个记录,数据工作期2的用户就不能锁定这个记录了。如果数据工作期1的用户以独占方式打开了一个表,数据工作期2的用户就不能打开这个表了。通过遵守其他数据工作期所做的锁定,VFP可以保护基表更新的完整性。
4)定制数据工作期的环境
由于数据工作期控制着某些SET命令的范围,可以在单一的VFP工作期中使用私有数据工作期建立自定义的SET命令设置。
例如,SET EXACT命令,控制比较不同长度字符串时使用的规则,作用于当前数据工作期。SET EXACT命令的设置为ON时,规定直到表达式末尾每个字符必须都完全匹配时,两个表达式才算相等。通过在默认的数据工作期中将SET EXACT命令设置为OFF,可以使用“模糊”搜索或相似查找;但是应用程序可能包含特殊的表单,它需要精确匹配。可以将需要精确匹配的表单的DataSession属性设置为2,使用私有数据工作期,然后将SET EXACT命令设置为ON。由于仅在使用私有数据工作期的表单使用一个SET命令,则当为一个特定的表单启用自定义工作期设置时,您可以保留整个的VFP工作期设置。
5)使自动数据工作期的设置无效
当使用表单的私有数据工作期时,在一个表单中对数据所做的更改不会自动地体现到相同表单的其他实例中。如果想让表单的所有的实例都访问相同的数据,并立即反应对公共数据做的改动,可以忽略自动数据工作期的设置。
若要使自动数据工作期设置无效,请使用命令SET DATASESSION TO 1或SET DATASESSION TO。这两个命令都将启用命令窗口以及项目管理器所控制的默认数据工作期。
5、缓冲访问数据
如果希望在更新时保护数据,可以使用缓冲技术。在多用户环境下,VFP的记录缓冲和表缓冲技术可以保护对单个记录或多个记录所做的数据更新以及数据维护操作。缓冲区可以自动测试、锁定以及解锁记录或表。
借助缓冲技术,可以很容易地检测并解决数据更新操作过程中所遇到的冲突:当前记录被复制到一个由VFP进行管理的内存区域或磁盘区域,其他用户仍然可以同时访问原来的记录。当离开该记录或以编程方式更新记录时,VFP将准备锁定该记录,确认其他用户没有做修改,然后写入编辑结果。在试图更新数据时,还必须解决冲突,若有冲突则应防止编辑结果写入原来的表。
1)选择缓冲的方法
在启用缓冲之前,应对数据环境进行评估,根据应用程序、记录以及表的类型和大小,信息的使用与更新方式以及其他因素,选择缓冲方法和锁定选项。一旦启用了缓冲,则在它被废止或关闭表之前一直保持有效。
VFP提供两种缓冲:记录缓冲和表缓冲:
* 若一次只对一个记录进行访问、修改或写操作,那么请选择记录缓冲。在一个多用户环境中,记录缓冲能够提供适当的有效性检查机制,对其他用户所做的数据更新操作影响最小。
* 如要对多个记录的更新使用缓冲,请选择表缓冲。如果想在一个表中处理多个记录或在一对多的关系中处理子表的记录,表缓冲提供了最佳的工作方式。
如果要对已有数据提供最大程度的保护,请使用VFP事务。可以单独使用事务,但是如果将事务和记录缓冲命令或表缓冲命令一起使用将会获得更好的效果。
2)选择锁定方式
VFP以两种锁定方式提供缓冲:保守式和开放式。这两种方式决定了一个或多个记录何时被锁定、优势何时、怎样被解锁。
a、保守式缓冲
在多用户环境下,保守式缓冲能防止其他用户在对某一特定记录或表进行修改时访问它。
保守式缓冲为单个记录的修改提供最安全的工作环境,但是会降低用户的操作速度。这种缓冲方式非常象FoxPro以前版本的标准锁定机制。此外,它还带有内在数据缓冲等更多的好处。
b、开放式缓冲
开放式缓冲是更新记录的有效方法,因为锁定只在写记录时生效,这样在多用户环境中使单个用户独占系统的时间最少。
当在一个远程表上使用记录或表缓冲时,VFP将强制使用开放式锁定。由函数CURSORSETPROP()设置的Buffering属性值决定了缓冲和锁定的方法。
下面列出了Buffering属性的有效值:
若要启用 请使用此缓冲值
————————————————————————————————————
无缓冲,默认值 1
保守式记录锁定。它锁定当前记录,在记录指针移动或 2
发出TABLEUPDATE()命令后更新
开放式记录锁定。它一直等到记录指针移动,然后锁定 3
并更新。
保守式表锁定。它锁定当前记录,在发出TALBEUPDATE() 4
命令后更新。
开放式表锁定。它一直等到发出TABLEUPDATE()命令,然后 5
锁定并更新已编辑的记录。
对于表,Buffering的默认值为1;对于视图,默认值为5。如果您使用缓冲访问远程数据,则Buffering属性可以是3(开放式记录缓冲)或5(开放式表缓冲)。此外,对除1以外的缓冲方式,设置MULTILOCKS为ON。
3)启用记录缓冲
可用CURSORSETPROP()函数启用记录缓冲。
若要在当前数据区启用保守式记录锁定,请使用如下函数和参数:
CURSORSETPROP("Buffering",2)
VFP在指针位置锁定记录。如果锁定成功,VFP将该记录放入缓冲区并允许编辑。当移动记录指针或发出TABLEUPDATE()命令时,VFP将把缓冲记录写入原来的表。
若要在当前数据区启用开放式记录锁定,请使用如下函数和参数:
CURSORSETPROP("Buffering",3)
VFP将指针位置的记录放入缓冲区并允许编辑。当移动记录指针或发出TABLEUPDATE()命令时,VFP将对该记录进行锁定。若锁定成功,VFP将对磁盘上的记录的当前值与原来的缓冲区值进行比较。如果两值相同,则编辑结果写入原来的表;否则将发出错误信息。
4)启用表缓冲
可用CURSORSETPROP()函数启用表缓冲。
若要在当前数据区对多个记录启用保守式锁定,请使用如下函数和参数:
CURSORSETPROP("Buffering",4)
VFP在指针位置锁定记录。如果锁定成功,VFP将该记录放入缓冲区并允许编辑。使用TABLEUPDATE()命令把缓冲记录写入原来的表。
若要在当前数据区对多个记录启用开放式锁定,请使用如下函数和参数:
CURSORSETPROP("Buffering",5)
在发出TABLEUPDATE()命令之前,VFP把记录写入缓冲区并允许编辑,然后VFP对缓冲区内的每一个记录执行下列操作:
* 对每一个已编辑记录锁定。
* 一旦锁定成功,即对磁盘上的每一个记录的当前值与原来的缓冲区值进行比较。
* 如果两值相等,将编辑结果写入原来的表。
* 如果两值不相等,给出错误信息。
在启用表缓冲之后,VFP只在TABLEUPDATE()命令之后更新。
5)在表缓冲区中追加和删除记录
可以在启用表缓冲之后追加和删除记录。追加的记录将添加到缓冲区的末尾。要访问缓冲区中所有的记录,请使用RECNO()函数,RECNO()函数根据追加到表缓冲区中的记录情况返回序列负值。比如,如果您启用了表缓冲,编辑记录7、8、9,然后追加三个记录,则此时缓冲区将包含RECNO()的值为7、8、9、-1、-2和-3。
只能使用TABLEREVERT()命令从缓冲区中删除追加记录。对于任何追加的记录,当维护序列时,TABLEUPDATE()和TABLEREVERT()都删除RECNO()为负值的记录。
在使用表缓冲时,您可以用带负RECNO()值的GO 命令来访问指定的追加记录。比如,对于前面的示例,可以输入:
GO 7 &&转到第一个缓冲记录
GO -3 &&转到第六个缓冲记录(第三个追加记录)
若要追加记录到表缓冲区,可在启用表缓冲后使用APPEND或APPEND BLANK命令。追加的记录具有序列递增的负RECNO()值。
若要从表缓冲区中移去追加的记录,可以:
a、使用带负值的GO命令将记录指针定位到要删除的记录。
b、使用DELETE命令将该记录加上删除标记。
c、使用TABLEREVERT()函数将该记录从缓冲区中移去。
注意:TABLEREVERT()函数也影响到被删除和更改的行的状态。
若要从表缓冲区中移去所有追加的记录,可使用带(.T.)值的TABLEREVERT()函数。TABLEREVERT()函数把追加的记录从表缓冲区中移去,而不将这些记录写入表。TABLEUPDATE()命令将所有当前的记录写入一个表,包括已经打上删除标记的记录。
二、更新数据
可以使用缓冲、事务或视图更新数据。
1、使用缓冲进行更新
在选择缓冲方法和锁定类型之后,就可以启用记录或表缓冲。若要启动缓冲,请选择下列选项之一:
* 在“表单设计器”中,设置表单数据环境中的临时表的BufferModeOverride属性。
* 在代码中设置Buffering属性。
例如,将下面的代码放进表单的init过程中,可以启用保守式记录缓冲:
CURSORSETPROR("Buffering",2)
然后将进行更新操作的代码放在控件的适当方法程序代码中。
要把编辑结果写入原来的表,可以使用TABLEUPDATE()。由于规则限制,造成对表的更新操作失败之后,要取消编辑结果,可以使用TABLEREVERT()命令。TABLEREVERT()即使在没有明确启用表缓冲的情况也都有效。
下面的示例说明在启用保守式记录缓冲的情况下,如何更新记录:
OPEN DATABASE testdata 在表单的init代码中,打开表单启用保守式记录缓冲。
USE customers
=CURSORSETPROP("Buffering",2)
lModified=.F.
FOR nFieldNum=1 to FCOUNT() 遍历字段,检查是否有字段进行了修改。注意:此段
IF GETFLDSTATE(nFieldNum)=2 代码可以写在“更新”或“保存”按钮的click事件中。
lModified=.T.
EXIT
ENDIF
ENDFOR
IF lModified 定位下一个已修改的记录。
nResult=MESSAGEBOX;
("记录已经被修改,是否保存?"),;
4+32+256,"数据更新")
IF nResult=7 提交当前值并为用户提供选择,询问是否还原对当前
=TABLEREVERT(.F.) 字段所做的修改。
ENDIF
ENDIF
SKIP SKIP确保最后一个修改内容也已写入。
IF EOF()
=MESSAGEBOX("已经到最后")
SKIP -1
ENDIF
THISFORM.refresh
2、事务管理更新
即便使用缓冲,事情有时也会出错。如果希望保护更新操作,并从一整段代码中还原回来,可以使用事务。
在应用程序中添加事务所提供的保护机制,超过了记录缓冲和表缓冲提供的保护功能,它将整段代码作为一个受保护的、可恢复的单元。可以嵌套使用事务,并用它保护已操作的缓冲更新。VFP事务只能用于数据库中的表和视图。
1)包装代码段
事务类似一层外包装,用来缓冲对内存或磁盘的数据更新操作,而不直接对数据库进行更新。实际的数据库更新在事务结束后进行。如果由于某种原因,系统不能执行对数据库的更新操作,就可以回滚整个事务,而不执行任何更新。
注意:在同一数据工作期的事务中,忽略事务外面的缓冲更新操作。
2)控制事务处理的命令
VFP提供了三个命令和一个函数控制事务处理:
若要 请使用此命令
——————————————————————————————————
初始化一个事务 BEGIN TRANSACTION
确定当前事务的等级 TXNLEVEL()
取消最近一条BEGIN TRANSACTION语句 ROLLBACK
以来所做的全部修改
锁定记录,将最近一条BEGIN TRANSACTION END TRANSACTION
语句以来对数据库中表所做的全部修改
写入磁盘,然后解锁记录
您可以使用事务缓冲对以下文件进行修改:表、结构化的.CDX文件以及数据库表相关的备注文件。涉及内存变量和其他对象的操作不属于事务,因此不能回滚这些操作。
注意:当使用远程表中存储的数据时,事务命令只更新视图临时表的本地备份中的数据;对远程基表的更新不起作用。要对远程表启用人工事务,请使用SQLSETPROP(),然后用SQLCOMMIT()和SQLROLLBACK()控制事务处理。
一般来讲,除非采用TABLEUPDATE()包装调用,事务最好和记录缓冲一起使用,而不和表缓冲一起使用。如果您在事务中使用了TABLEUPDATE()命令,那么可以回滚失败的更新操作,找到失败的原因,然后重试TABLEUPDATE()命令而不丢失任何数据,这样,就确保了更新操作一定是所谓的“要么全都做,要么全不做”的原则。
尽管在正常的情况下,简单的事务处理能够提供安全的数据更新操作,但是它并不能提供对系统失败的完全保护。如果在处理END TRANSACTION命令期间断电或产生系统中断,则数据更新仍然将会失败。
请使用下列事务处理代码模板:
BEGIN TRANSACTION
*更新数据
IF lSuccess=.F. &&出错
ROLLBACK
ELSE &&执行修改
*确认数据
IF &&出错
ROLLBACK
ELSE
END TRANSACTION
ENDIF
ENDIF
3)使用事务
下列规则适用于事务:
a、一个事务起始于BEGIN TRANSACTION命令,以END TRANSACTION或ROLLBACK命令终止。只有END TRANSACTION语句,而没有BEGIN TRANSACTION与之相匹配将会出错。
b、ROLLBACK语句前没有BEGIN TRANSACTION将会出错。
c、除非应用程序中止(这将导致回滚操作),事务一旦开始,到遇到相应的END TRANSACTION语句(或回滚语句)这一期间,将保持有效,在跨程序、跨函数的情况下也是如此。
d、对于涉及事务数据的查询,VFP在使用磁盘数据前先使用在事务缓冲区内的缓冲数据,确保使用最近的数据。
e、如果在事务处理过程中应用程序中止,则所有操作回滚。
f、事务处理只在数据库容器中进行。
g、如果INDEX命令改写了一个已有的索引文件,或者任一索引文件已打开,则不能使用INDEX命令。
h、事务只在数据工作期中起作用。
事务处理完成了下列锁定动作:
a、当一个命令直接或间接调用事务时,VFP将强制锁定。任何系统或用户的直接或间接命令将被缓冲,直至ROLLBACK或BEGIN TRANSACTION命令结束事务的操作。
b、如果在事务处理中使用了锁定命令,如FLOCK()或RLOCK()、则END TRANSACTION语句将不会解锁。因此您必须明确地解除任何在事务处理中进行的明确锁定。应该使包含FLOCK()或RLOCK()命令的事务尽量简短;否则用户会长时间不能访问被锁定的记录。
4)嵌套事务处理
对于分隔在不同并发进程中的表,嵌套事务可以为表的更新操作提供逻辑组。事务处理命令BEGIN TRANSACTION…END TRANSACTION不需要在同一函数或过程中。下列规则适用与嵌套事务处理:
a、可以嵌套5层BEGIN TRANSACTION…END TRANSACTION。
b、直到最外层的END TRANSACTION被调用时,才执行嵌套事务中的更新。
c、在嵌套事务中,一个END TRANSACTION只对最近的BEGIN TRANSACTION所引起的事务进行操作。
d、在嵌套事务中,ROLLBACK语句只对最近的BEGIN TRANSACTION所引起的事务进行操作。
e、在嵌套事务中,对同一数据的更新操作,最内层的更新优先于嵌套事务处理块中的其他更新。
请注意下面的示例,因为在嵌套事务中所做的修改没有写入磁盘,而写入事务处理缓冲区,内层事务处理将改写外层事务处理对同一STATUS字段所做的修改。
BEGIN TRANSACTION &&事务1
UPDATE EMPLOYEE; &&第一次修改
SET STATUS="Contrcet" WHERE EMPID BETWEEN 9001 AND 10000
BEGIN TRANSACTION &&事务2
UPDATE EMPLOYEE SET STATUS="Exempt" WHERE HIREDATE>{1/1/93} &&改写
END TRANSACTION &&事务2
END TRANSACTION &&事务1
5)保护远程更新
在对远程表进行数据更新的过程中,事务可以避免系统造成的错误。
6)性能管理
如果有一个能够正常工作的多用户应用程序,可以采用下面的方法提高程序性能:
a、将临时文件放在本地驱动器上:VFP在默认文件夹下创建自己的临时文件。文本编辑工作期也临时地创建被编辑文件的备份(一个.BAK文件)。如果本地工作站的硬盘上有足够的剩余空间,将临时文件放在这个本地盘或RAM驱动器上可以提高程序性能。重定向这些文件到本地盘或RAM驱动器上,可以减少访问网络驱动器的次数,从而提高性能。也可以在配置文件CONFIG.FPW中使用EDITWORK、SORTWORK、PROGWORK及TMPFILES语句,为这些文件指定其他存放的位置。
b、在排序和索引文件之间选择:当一个表中的数据相对稳定时,处理没有设置索引顺序的表会提高性能,这并不意味着已经经过排序整理的表就不能或不应该利用索引文件,象SEEK命令就需要一个索引,而且在定位记录时速度相当快。但是,一旦用SEEK完成定位操作后,就应该关闭索引。
c、安排对文件的独占访问:对于在其他用户不访问数据时运行的命令,象夜间的更新操作,可以通过独占方式打开文件提高性能。当文件以独占方式打开时,由于VFP不需要测试记录或文件锁定的状态,因此性能得到提高。
d、控制锁定文件的时间:要减少用户对表或记录写操作的竞争,就必须减少锁定记录或锁定表的时间。可以通过在编辑完成之后的非编辑过程中锁定记录来做到这一点。开放式记录缓冲锁定时间最短。
3、使用视图管理更新
可以使用视图具有的更新冲突管理技术来管理多用户对数据的访问。使用WhereType属性,视图控制发送到基于视图的基表的内容。可以为本地和远程视图设置该属性。WhereType属性提供了四种设置:
DB-KEY
DB-KEYANDUPDATABLE
DB-KEYANDMODIFIED(默认)
DB-KEYANDTIMESTAMP
通过选择四种设置之一,可以控制VFP如何生成发送到视图基表的SQL Update语句的WHERE子句。可以使用“视图设计器”的“更新条件”选项卡来选择需要的设置,或者使用DBSETPROP()函数设置一个视图定义的WhereType属性。要想更改一个活动视图临时表的WhereType属性,请使用CURSORSETPROP()。
1)只比较关键字段
使用DB-KEY设置对更新的限制最小。用来更新远程表的WHERE子句只包含用KeyField或KeyFieldList属性指定的主关键字段。如果基表中的主关键字段的值在检索记录之后没有被更改或删除,更新将正常进行。
2)比较视图中的主关键字段和已更改字段
默认的DB-KEYANDMODIFIED设置比DB-KEY多了一些限制。DB-KEYANDMODIFIED只将关键字段和在视图中更改的可更新字段与基表相应的字段进行比较。如果在视图中更改了一个字段,但该字段不是可更新字段,则字段不与基表中的数据比较。
用来更新基表的WHERE子句包含用KeyField属性指定的主关键字段和其他在视图中更改的任意字段。
3)比较关键字段和所有可更新字段
DB-KEYANDUPDATABLE设置将关键字段以及任何视图中的可更新字段(不管是否被修改)与基表相对应的的字段进行比较。如果字段是可更新的,即使在视图中并没有更改它,但如果其他人在基表中更改了这个字段,更新就会失败。
用来更新基表的WHERE子句包含用KeyField或KeyFieldList属性指定的主关键字段和其他任何可更新字段。
4)比较基表记录中所有字段的时间戳
DB-KEYANDTIMESTAMP设置是对更新的限制最多的,并且只有在基表中具有时间戳字段时才能使用该设置。VFP将基表记录中的当前时间戳与当数据被提取到视图中的时间戳进行比较。如果基表记录的任何字段被改变了,即使它不是视图更改的字段,甚至不是视图中的一个字段,更新也会失败。
为了能够在多表视图中使用DB-KEYANDTIMESTAMP设置成功地更新数据,必须在每个可更新表的视图中包含时间戳字段。例如,在视图中有三个表,但是却只想更新其中的两个,而且选择了DB-KEYANDTIMESTAMP设置,必须从两个可更新表中将时间戳字段带入到结果集合中。可以使用CompareMemo属性的逻辑值来确定在冲突检查中是否包含备注字段。
三、冲突管理
不管选择了缓冲、事务还是视图、都必须在更新过程中管理冲突。那么,如何具体管理冲突呢?
1、管理缓冲冲突
在一个多用户环境中,通过精心选择打开、缓冲并锁定数据的时间和方式,可以更高效地进行数据更新操作。应该减少访问记录或表时发生冲突的时间,同时必须预测到不可避免的的冲突将导致什么样的后果,并对这种冲突进行管理。冲突一般在一个用户试图锁定一个当前正被其他用户锁定的记录或表时发生,两个用户不能同时锁定同一个表或记录。
应用程序中应包含管理冲突的例程。如果没有冲突例程,系统将死锁。死锁通常在这种情况下发生:第一个用户已经锁定一个表或记录,现在试图去锁定已被第二个用户锁定的记录或表,而第二个用户又反过来试图锁定第一个用户锁定的记录或表。尽管这种情况很少发生,但记录或表锁定的时间越长,这种死锁的可能性就越大。
无论是设计一个多用户的应用程序还是为一个单用户系统添加网络支持服务,都要求找到错误并对其进行处理。使用VFP记录缓冲和表缓冲可以简化这种工作。
如果试图锁定一个已被其他用户锁定的记录或表,VFP将返回错误信息。可使用SET REPROCESS自动处理不成功的锁定操作。此命令可与ON ERROR例程和RETRY命令组合,继续或取消锁定操作。
2、检测并解决冲突
在数据更新操作过程中,特别是在共享环境下,您可能希望确定哪些字段已经被更改、确定已更改字段的原有值或当前值是什么。VFP的缓冲和GETFLDSTATE()、GETNEXTMODIFIED()、OLDVAL()及CURVAL()函数可以提供这些功能。通过它们来确定哪些字段已经被更改;查找已更改的数据;比较当前值、原有值及已编辑的值。这样,可以决定如何处理错误或冲突。
如果要检测字段中所做的更改,可在更新操作之后使用GETFLDSTATE()函数。GETFLDSTATE()可以对非缓冲数据进行操作,但在启用了记录缓冲的情况下,此函数更加有效。比如将GETFLDSTATE()用于一个表单的Skip按钮代码中。当您移动记录指针时,VFP将检查记录中所有字段的状态,如下例所示:
lModified=.F.
FOR nFieldNum=1 TO FCOUNT() &&检查所有的字段
IF GETFLDSTATE(nFieldNum)=2 &&已修改
lModified=.T.
EXIT &&可以在此处插入一个
ENDIF
ENDFOR
若要检测并定位缓冲数据中已更改的记录,请使用GETNEXTMODIFIED()函数。GETNEXTMODIFIED()函数以0作为参数,查找第一个已修改的记录。如果其他用户对缓冲表进行了修改,则您的缓冲区中TABLEUPDATE()命令遇到任何修改都将导致冲突。可以用CURVAL()、OLDVAL()和MESSAGEBOX()计算冲突值并解决冲突,CURVAL()返回磁盘上记录的当前值,而OLDVAL()返回记录缓冲时的值。
若要确定缓冲字段的原有值,请使用OLDVAL()函数,OLDVAL()返回一个缓冲字段的值。
若要确定磁盘上一个缓冲字段的当前值,请使用CURVAL()函数,CURVAL()返回一个在编辑操作执行前磁盘缓冲字段的当前值。
在共享环境中,可以创建一个出错处理过程,比较当前值和原有值,并决定是接受当前的修改还是更早一点的修改。
1)使用备注字段检测冲突
使用CompareMemo属性来控制何时使用备注字段检查更新冲突。这个视图和临时表属性确定了在更新的WHERE子句中是否包含备注字段(备注或通用型)。默认设置是.T.,即在WHERE子句中包含备注字段。如果这个属性设置为.F.,不论UpdateType如何设置,在更新的WHERE子句中就不会包含备注字段。
当CompareMemo属性设置为假时,备注字段上的开放式冲突检查是不可用的。如果要用备注字段的冲突检查,请将CompareMemo属性设置为真。
2)管理冲突的规则
管理在多用户环境中的冲突需要有扩展和可重用的代码。一个完整的冲突管理例程进行如下操作:
a、检查冲突;
b、确定冲突的性质和位置;
c、提供足够的信息,用户可以有效地解决这些冲突;
有关冲突管理例程的示例,可以参阅位于VFP\SAMPLES\CLASSES的SAMPLES.VCX中的datachecker类。将该类添加到表单中,在将缓冲数据写入表的操作之前调用CheckConflicts方法程序,例如记录缓冲移动记录指针,关闭表,或者发出TABLEUPDATE()命令。