|
可更新视图(Updateble views)
上面我们讲解了“怎样通过远程视图从服务器中把数据读取过来”,接着我们要讲解怎样操作远程视图光标,当然我们不会在这里讨论一些Visual FoxPro数据集的普通操作,这里我们只讲解远程视图的数据更新。
当远程视图被打开,用户就可以使用Visual FoxPro的命令与函数作光标进行各种操作,例如查询、新添数据、删除记录、修改记录等,后三者都会使数据发生变动,远程视图有着自动分析各种变动并把变动的结果发送到后端数据库更新数据源的功能。
顺便提一下,Visual FoxPro 在数据更新这方面的的能力是非常强大的——如果一个视图是由多个数据源表连接而成,Visual FoxPro 能够自动分析客户端数据变动所对应的数据源信息,“分门别类”对远程数据数据进行的更新。现在一些非常著名的数据引擎中都不具备这个功能,详细情况请看本站文集中的《Visual FoxPro 漫谈》一文。
键值栏、可更新字段、SQL 更新开关
我们可以通过“视图设计器”制作可更新的远程视图(当然可以用它设计上文提到的那些远程视图),如图 7。为了设定一个远程视图为可更新视图,在视图设计器中您必须多做三件事情(比不可更新的远程视图):
设定键值栏。系统之所以能够知道你变动了视图中的那一笔记录并在数据源中作出相应的变动,就是依靠键值来判断的。可以这样理解:键值就是能够惟一标识表中数据记录的标记。键值是不可以重复的。设想一下在一个员工表中,您把性别设定为键值,并修改某一员工的姓名,然后发送更新,这会造成什么结果(事实上Visual FoxPro会报告键值不唯一的错误,我们假设Visual FoxPro不报错):与该被修改姓名的员工具有相同性别的员工的名字都被修改了,很可笑吧!
一般Visual FoxPro会自动从SQL Server中把键值信息读到,并自动设定键值栏。
如果没有现成能够唯一标识记录的字段,则可以使用联合字段。
设定可更新字段。只有那些被设置为可更新的字段,它们的变动才会反映到数据源表中。并不是所有的字段都是可更新字段,例如:在SQL Server中的 带有 Identity 属性的字段(由系统维护其数值,常作为键值)。
打开“发送SQL更新”选项。很多人在视图设计器中“千辛万苦”的设定了好多信息,但客户端数据变动就是不能发送到后端,原因就是他们忘记了这最最基本的选项。记住:要视图可更新,此项必须设定。
| http://writeblog.csdn.net/SQLSERVER%E5%AE%9E%E4%BE%8B-2.files/cs_7.jpg
图 7. 视图设计器
我们可以通过DBSETPROP()函数设定以上三个选项:
DBSETPROP("VCustomers.CustomerID", "Field", "KeyField", .T.)
*设定键值栏
DBSETPROP("VCustomers.CustomerID", "Field", "Updatable", .T.)
*设定可更新字段,有很多字段要写,这里省略
DBSETPROP("VCustomers", "View", "SendUpdates", .T.)
*打开“发送 SQL 更新”
在图 7 中我们还有两项属性没有设定:“使用更新”,“SQL WHERE 子句包括”。
更新方式
“使用更新”的作用就是:告诉Visual FoxPro怎样分解更新操作,也就是一种“更新方式”的选择。举个例子,我们对视图中某一行的字段值做了修改。如果本属性设为“SQL UPDATE”,发送更新时Visual FoxPro会往后端传送一条UPDATE-SQL语句;如果本属性设为“SQL DELETE 然后 UPDATE”,发送更新时Visual FoxPro会往后端传送一条DELETE-SQL语句——删去原来的记录,再传送一条INSERT-SQL语句——把带有新值的记录加入数据源。很容易发现:前一种设定更有效率,但为什么还要有后一种选择呢?原来有一些老式的数据库不支持UPDATE-SQL……对于主流的数据库系统应该不会有这种问题,所以我们在通常情况下选择“SQL UPDATE”。
SQL WHERE 子句包括——更新冲突的检测方式
“更新冲突”是指当某一位使用者在修改某一笔数据时,同一笔数据记录的内容已被其他使用者修改。下图就是一个更新冲突的示意图,红色代表数据源,黑色代表进程一,蓝色代表进程二。
http://writeblog.csdn.net/SQLSERVER%E5%AE%9E%E4%BE%8B-2.files/cs_8.jpg
图 8. 更新冲突示意
此设定有四个选项:关键字段,关键字和可更新字段,关键字和已更新字段,关键字和时间戳。
关键字段:若选择此选项,系统只会检查来源表的键值栏的内容是否被修改。
关键字和可更新字段:若选择此选项,系统将会检查数据源表的键值栏与可更新的字段内容是否被修改。
关键字和已更新字段:若选择此选项,系统将会检查数据源表的键值栏与已修改的字段内容是否被修改。这里提请大家注意,大文本对象(Image,text类型的字段)被设定为可更新字段时,不应该使用这种更新冲突检测方案,因为这样对系统消耗太大,事实上Visual FoxPro也不允许你这样做。
这是最常用检测更新冲突检测方案。
关键字和时间戳:当SQL SERVER 的任意一笔数据记录的任何字段被更新(新增、删除、修改),系统都会打上一个烙印——时间戳。时间戳是一种全局唯一的二进制字符,千万不要理解为时间日期型的数据。在使用关键字和时间戳更新冲突检测方案时还必须注意,数据源表的时间戳字段必须出现在远程视图中(Visual FoxPro会将其转换成为Character Binary类型)。关键字和时间戳更新冲突检测方案主要用于多人使用的更新冲突的核查。若选择此选项系统将会利用键值栏与时间戳字段来检查数据记录是否已被修改,这一项比上三个选项检查效率更快也更严谨(从理论上讲),因为它的系统负担最小。
| 为使您更好的理解更新冲突以及更新冲突检测方案,我们把图8结合实例讲解一下。
CREATE SQL VIEW VCustomers ;
REMOTE CONNECTION Northwind SHARE;
AS SELECT CustomerID,CompanyName,Phone FROM Customers
*新建远程视图
DBSETPROP("VCustomers.CustomerID", "Field", "KeyField", .T.)
DBSETPROP("VCustomers.CompanyName", "Field", "Updatable", .T.)
DBSETPROP("VCustomers.Phone", "Field", "Updatable", .T.)
DBSETPROP("VCustomers", "View", "SendUpdates", .T.)
DBSETPROP("VCustomers", "View", "WhereType", 3)
*设定更新冲突解决方案为“关键字和已更新字段”
DBSETPROP("VCustomers", "View", "UpdateType", 1)
*进程一
USE VCustomers
BROWSE
*将指针停留在第一条记录上,即:CustomerID='ALFKI'
REPLACE Phone with '123456'
*离开Visual FoxPro,千万别移动记录指针
进程二
使用 SQL Server 的 Enterprise Manager 打开 Customers表,把指针停在第一条记录上,修改Phone的值为'00000',移动指针到下一条记录。
回到Visual FoxPro,移动指针,您会看到图9:
http://writeblog.csdn.net/SQLSERVER%E5%AE%9E%E4%BE%8B-2.files/cs_9.jpg
图 9.更新冲突
按“还原”按钮。试验结束。
仔细想想,您就会明白什么是更新冲突了。
上例中,如果我们设定“关键字”方式检测更新冲突:
DBSETPROP("VCustomers", "View", "WhereType", 1)
其他均按原先步骤进行,您会发现没有更新冲突产生。因为Visual FoxPro仅检测关键字是否变化,这里进程一、二都没有修改关键字,当然不会有更新冲突。
上例中,如果我们设定“关键字和可更新字段”方式检测更新冲突:
DBSETPROP("VCustomers", "View", "WhereType", 2)
其他均按原先步骤进行,这时会有更新冲突产生。因为Visual FoxPro不仅检测关键字是否变化,还要检测所有的可更新字段字段(本例是所有字段)是否发生变化,这里进程二先进程一修改了可更新字段 Phone,进程一当然会有更新冲突发生。
如果使用 SQL Server 的 Profiler 程序您能更好的了解以上内容:
http://writeblog.csdn.net/SQLSERVER%E5%AE%9E%E4%BE%8B-2.files/cs_10.jpg
图 10。 SQL Server 的 Profiler 程序
1.使用“关键字段”冲突检测方式,发送更新时,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'可见,UPDATE 的 WHERE 子句只包括关键字段:CustomerID。在Visual FoxPro 缓冲中 CustomerID='ALFKI',Visual FoxPro 就以这个值作为数据源是否发生改变的依据。如果 SQL Server执行这条UPDATE语句时找不到CustomerID='ALFKI'的记录(我们认为是其它用户先期修改了CustomerID)——SQL Server 告诉 Visual FoxPro 更新冲突发生了。
2.使用“关键字和可更新字段”冲突检测方式,发送更新时,Visual FoxPro 自动生成以下语句在 SQL Server 中执行:
sp_executesql N'UPDATE dbo.Customers SET Phone=@P1 WHERE CustomerID=@P2 AND CompanyName=@P3 AND Phone=@P4', N'@P1 nvarchar(24),@P2 varchar(50),@P3 nvarchar(40),@P4 nvarchar(24)', N'123456 ', 'ALFKI', N'Alfreds Futterkiste ', N'030-0074321 '可见,UPDATE 的 WHERE 子句包括关键字段:CustomerID,和所有可更新字段:CompanyName、Phone。Visual FoxPro 缓冲中CustomerID='ALFKI'、CompanyName='Alfreds Futterkiste'、Phone='030-0074321',如果 SQL Server执行这条UPDATE语句时找不到(CustomerID='ALFKI' AND CompanyName='Alfreds Futterkiste' AND Phone='030-0074321')的记录(我们认为是其它用户先期修改了这三者中的任何一个多几个的值)——SQL Server 告诉 Visual FoxPro 更新冲突发生了。
3.使用“关键字和已更新字段”冲突检测方式,发送更新时,Visual FoxPro 自动生成以下语句在 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 的 WHERE 子句包括关键字段:CustomerID,和所有以经被 Visual FoxPro 更新的字段:Phone。Visual FoxPro 缓冲中CustomerID='ALFKI'、Phone='030-0074321',如果 SQL Server执行这条UPDATE语句时找不到(CustomerID='ALFKI' AND Phone='030-0074321')的记录(我们认为是其它用户先期修改了这两者中的任何一个多几个的值)——SQL Server 告诉 Visual FoxPro 更新冲突发生了。
4.如果希望尝试“关键字和时间戳”冲突检测方式,请在 SQL Server与Visual FoxPro的远程视图中加入TimeStamp字段。发送更新时,Visual FoxPro 自动生成以下语句在 SQL Server 中执行:
UPDATE dbo.Customers SET Phone=N'12345 ' WHERE CustomerID='ALFKI' AND timestamp=0x0000000000000199可见,UPDATE 的 WHERE 子句包括关键字段:CustomerID,和时间戳字段。Visual FoxPro 缓冲中CustomerID='ALFKI'、时间戳是:0x0000000000000199,如果 SQL Server执行这条UPDATE语句时找不到(CustomerID='ALFKI' AND timestamp=0x0000000000000199')的记录(我们认为是其它用户先期修改了数据源表中该行的数据,只要有任何变化,时间戳就会自动更改)——SQL Server 告诉 Visual FoxPro 更新冲突发生了。
| 如果您还没有理解更新冲突——这很正常,请往下看。
缓冲(Buffering)
Visual FoxPro 中的缓冲技术
当远端数据下载到客户端时,这些数据就被压入缓冲之中。在客户端用户对数据的各种移动并不反映到数据源,而是在用户确认对数据的变动后,才把各种变动以SQL描述的形式发送到后端。那么为什么不让Visual FoxPro直接一步一动的操控远程数据,就像在不使用缓冲技术控制Visual FoxPro本地数据那样。我想原因在于:
- 在 Client/Server 构架的应用中,数据库服务器需要同时处理许多客户的请求,如果有一个客户“直接”控制(锁定)它,多用户的系统就无从谈起了。
- Visual FoxPro 通过 ODBC 与远端数据库通讯,如果一步一动,两者之间的通讯肯定会成倍增加,这样既加重了网络负担又加重的数据库服务器的负担。
| 基于上述原因,Visual FoxPro在远程数据处理时强制使用缓冲技术。我们知道,在Visual FoxPro中缓冲技术与锁结合有四种选择:
保守式行缓冲。所谓“保守”,就是“编辑时锁定”的意思,“行缓冲”是指“只缓冲处理一笔使用者加以编辑的数据记录”。因此一旦使用这种模式,当编辑动作刚开始,数据源的对应数据记录便被锁定,而且在执行以下两项动作时,数据变动才会被发送:移动数据指针、执行TABLEUPDATE()函数。
由于在开始编辑时就锁定数据源的对应行,所以这种模式不被远程数据处理采用。
开放式行缓冲。所谓“开放”,就是“更新时锁定”的意思,“行缓冲”是指“只缓冲处理一笔使用者加以编辑的数据记录”。因此使用这种模式,只有在执行以下两项动作时,数据变动才会被发送,数据源对应行记录才被锁定:移动数据指针、执行TABLEUPDATE()函数。
保守式表缓冲。所谓“保守”,就是“编辑时锁定”的意思,“表缓冲”是指“缓冲处理整个使用者加以编辑的数据集(光标)”。因此一旦使用这种模式,当编辑动作刚开始,数据源的相关记录集便被锁定,而且在执行以下动作时,数据变动才会被发送:执行TABLEUPDATE()函数。
由于在开始编辑时就锁定数据源的整个对应表或是记录集,所以这种模式不被远程数据处理采用。
开放式行缓冲。所谓“开放”,就是“更新时锁定”的意思,“表缓冲”是指“缓冲处理整个使用者加以编辑的数据集(光标)”。因此使用这种模式,只有在执行TABLEUPDATE()函数时,数据变动才会被发送,数据源的相关记录集才被锁定。
| 好了,我们得到以下结论:在操控远程数据时,Visual FoxPro将对光标采用“开放式行缓冲”或“开放式表缓冲”,默认设置是“开放式行缓冲”。
以后在讨论远程数据处理时,不特别指出,行缓冲就是指开放式行缓冲,表缓冲是指开放式表缓冲。
在“开放式行缓冲”下,因为只对一条被编辑的记录开启缓冲,所以有两种方式可以确认编辑、发送更新:移动指针(在上面的例子中我们已经使用过了)、TABLEUPDATE()函数。不知您能否理解“指针移动确认更新”的意思?我是这样理解的:行缓冲只对一条被编辑的记录有用,如果移动指针,那就必须确认更新(如果数据有变动),因为如果不确认更新(释放缓冲区),Visual FoxPro便没法为下一行制定缓冲区了——记住:这是行缓冲。
“在开放式表缓冲”下,Visual FoxPro对整个记录集开启缓冲区,所以移动指针并不会确认更新。只有使用TABLEUPDATE()函数了。
乍一看,“开放式行缓冲”比“开放式行缓冲”需要更少的系统资源,好像是个好选择,我看不尽然:
- 有些Visual FoxPro的命令或函数会“不由自主”地移动指针,使得开发人员对更新的确认失去控制。
- 有时对数据的维护是成批的。
| 下面的代码说明了怎样控制缓冲:
USE VCustomers
CURSORSETPROP("Buffering", 3, "VCustomers")
*设定VCustomers的缓冲模式为“开放式行缓冲”。由于这时Visual FoxPro的默认设置,这一句可省略。
USE VOrders
CURSORSETPROP("Buffering", 5, "VOrders")
*设定VOrder的缓冲模式为“开放式表缓冲”。
以缓冲理解更新冲突
在图8中我在发生更新错误时提示:“原先Phone=030-0074321,现在Phone=00000,两者不等……”,那么这个原先是“什么时候”,“现在”又是怎样的概念?(假设进程一、二采用行缓冲模式、用“关键字和已更新字段”检测冲突)。
“原先”是指:进程一中视图被打开或是最近一次刷新成功时刻SQL Server数据表中的记录值。让我们先停下来,怎样才会刷新光标和缓冲呢?
远程视图光标被打开(无论使用行缓冲还是表缓冲)。
成功执行REQUERY()函数(无论使用行缓冲还是表缓冲)。
发送更新(无论成功与否)
| 您可以想象:进程一打开远程视图,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()函数:
语法:TABLEUPDATE(0[,lForce][,nWorkAear|cTableAlias])
返回值:更新成功——.T.,更新失败——.F.
必选参数:0。代表只更新当前记录到数据源——这里是记录缓冲,当然是:“只更新当前记录到数据源”。
可选参数——lForce。默认为.F.,指:如果发生更新错误就确认更新错误,本函数返回.F.;如果设此参数为.t., 表示发生更新错误时,以本客户端的新值为准,覆盖网络上被确认已经的其他用户的更新,如果覆盖成功,本函数返回.T.。
缺省表示本参数时取默认值。
该参数设为.T.的实质就是临时改变更新冲突的检测方式为“关键字段”,所以只要关键字不发生冲突,就不会发生更新冲突,本客户端的新值将覆盖其他用户做的变更。
可选参数——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()函数:
语法:TABLEUPDATE(nRows[,lForce][,nWorkAear|cTableAlias][,cErrorArray])
返回值:更新成功——.T.,更新失败——.F.
必选参数——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
可选参数——lForce。默认为.F.,指:如果发生更新错误就确认更新错误,本函数返回.F.;如果设此参数为.t., 表示发生更新错误时,以本客户端的新值为准,覆盖网络上被确认已经的其他用户的更新,如果覆盖成功,本函数返回.T.。
缺省表示本参数时取默认值。
该参数设为.T.的实质就是临时改变更新冲突的检测方式为“关键字段”,所以只要关键字不发生冲突,就不会发生更新冲突,本客户端的新值将覆盖其他用户做的变更。
可选参数——nWorkAear|cTableAlias。表示实行TABLEUPDATE()的工作区,缺省表示对当前工作区有效。
可选参数——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()函数:
语法:TABLEUPDATE(.f.[,nWorkAear|cTableAlias])
返回值:1。如果当前记录没有被修改,则返回0。
可选参数——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()函数:
语法:TABLEUPDATE(lAllRows[,nWorkAear|cTableAlias])
返回值:放弃更新的记录数目
必选参数——lAllRows。默认值为.F.,表示对当前记录放弃更新;本参数设定为 .T.,放弃更新所有被修改过的记录。
可选参数——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
刷新缓冲(refreshing buffers)
记得在“以缓冲理解更新冲突”一节中我们提到过什么情况下Visual FoxPro会刷新缓冲区。其中“远程视图光标被打开”是很好的理解,这里不再累述。
以 REQUERY()函数刷新远程视图光标
REQUERY()函数的作用就是重新执行远程视图的SELECT-SQL描述,也就是重新打开远程视图光标,所以对系统造成较大的负担。对于这个函数,我有几点建议:
执行成功,返回:1;反之,函数返回:0。
执行成功,记录指针将停在首记录,因为本函数就如同重新打开远程视图。
由于是重新下载数据,本函数对缓冲进行全部刷新。
行缓冲下,如果修改了一行记录,但未发送更新时,使用本函数,Visual FoxPro将先发送更新,如果没有更新冲突,才重新下载数据光标;如果发生更新冲突,本函数不被执行,返回 0。
表缓冲下,如果修改了任何记录,但没有发送更新,本函数将不被执行,出现如图11的提示,返回0。
| http://writeblog.csdn.net/SQLSERVER%E5%AE%9E%E4%BE%8B-2.files/cs_11.jpg
图 11。 表缓冲下更新没有被确认,不能使用REQUERY()函数
发送更新成功,只刷新被更新记录的相关字段的缓冲
也许这个标题很难理解,那么我们就分析一下:
无论是行缓冲还是表缓冲模式,发送更新成功,Visual FoxPro将已更新字段的新值填入缓冲。也就是说,没有被更新字段的缓冲区不被刷新。
行缓冲下发送更新成功,只刷新当前记录地被更新字段的缓冲。
表缓冲下发送更新成功,只刷新被更新的若干记录的各自被更新字段的缓冲。
| 我认为,Visual FoxPro所谓刷新缓冲,只不过是Visual FoxPro自己的行为而与远程数据源无关,也就是由Visual FoxPro生成UPDATE-SQL语句时,Visual FoxPro自动将新值填入缓冲,Visual FoxPro再发送此SQL描述。所以被刷新的就是被更新的字段的缓冲,并且无论更新是否成功!
远程视图的其他属性
远程视图的高级属性可以通过可视工具设定,下面我们就讲解一下:
http://writeblog.csdn.net/SQLSERVER%E5%AE%9E%E4%BE%8B-2.files/cs_12.jpg
FetchASNeed 和 FetchSize 即:取得远端所需的数据和每次提取的记录数
这两个属性是成对工作的,默认设定是:
DBSETPROP("ViewName","View","FetchSize",100)
DBSETPROP("ViewName","View","FetchAsNeed",.F.)
表示远程视图打开时,每批下载100条记录,当第一批100条记录被下载完毕后,Visual FoxPro将把控制权还给用户或继续往下执行程序。这样就有问题了:如果几个远程视图共享一条连接,接可能造成连接堵塞。如下:
CREATE SQL VIEW VOrders ;
REMOTE CONNECTION Northwind SHARE;
AS SELECT * FROM Orders
CREATE SQL VIEW VCustomers ;
REMOTE CONNECTION Northwind SHARE;
AS SELECT * FROM Customers
*在命令窗口选中以下命令,按回车键
Use VOrders in 0
Use VCustomers in 0
*弹出“连接 Northwind忙" 的提示窗口
让我们分析原因:当VOrder的一批100条记录被下载完毕后,Visual FoxPro就执行打开VCustomers表的命令,这时对于连接Northwind将面临两项任务:继续下载VOrder的第二批100条记录、下载VCustomers的第一批记录,一个连接无法同时应付两项任务,所以“连接忙”。
解决以上问题,可以这样设置:
DBSETPROP("VOrder","View","FetchSize",-1)
*表示一次下载所有记录,完成此任务才将控制权交回或继续执行程序。
如果你这样设置:
DBSETPROP('VOrder',"View","FetchSize",100)
DBSETPROP("ViewName","View","FetchAsNeed",.T.)
USE VOrder
*下在100条记录,并将控制权交还用户,由于FetchAsNeed=.T.,不像刚才——Visual FoxPro自动控制继续下载数据。这时VOrder"霸占"连接Northwind,时刻等待用户指令,已决定是否下载剩余数据
go 100
*第100条记录已经下载,所以VOrder不下载任何数据,但依然霸占连接
go 105
*第105条记录还没有下载到客户端,所以VOrder下载5条数据以满足用户的需要,但依然霸占连接
go bottom
*下载所有记录,释放连接
MaxRecords 即:要提取的最大记录数
这个属性是指远程视图光标最多可以下载的记录数,并且它的优先级比上两个属性高。也就是说无论上两个属性怎样设置,客户端就只能拥有小于等于MaxRecords条从远端下载的记录,并且达到这个数量后,“霸占”连接就被释放、可以供其它视图使用。这个属性可以这样设定:
DBSETPROP("ViewName", "View", "MaxRecords", 25)
FetchMemo 即:取备注字段
如果远程视图中包含备注字段,一般认为:一次性下载这些数据到客户端是很没有意义的、也是没有效率的,而且猛增了网络流量。比较好的做法是:用的时候才下载。所谓用的时候就是指:明显或隐含的Modi Memo命令。可以这样设置它的属性:
DBSETPROP("ViewName", "view", "FetchMemo", .F.)
CompareMemo 即:在 Where 子句中包含备注字段
当远程视图包含备注字段或是通用字段时,这个属性非常重要的。这个属性的默认值是.T.,即在检测更新冲突时把这两种字段与其它类型的字段同等看待。笔者认为,这种设置有一个不合理、一个错误,请听我道来:
先说错误,当一个通用型(不包括“备注型字段”)字段被设定为可更新,并使用了“关键字与可更新字段”或“关键字与已更新字段”的更新冲突检测方式,Visual FoxPro 不允许这种设定。在视图设计阶段,Visual FoxPro 不会指出这个错误,但当实际发送 UPDATE-SQL 时会出现错误提示。
在讲“不合理”。我刚才指出,备注字段是可以参与更新冲突检测的,但是这样对服务器的压力很大、对网络的压力也很大。
所以一般应用中,我们会把该值设定为 .F.,即当更新冲突检测方式为“关键字与可更新字段”或“关键字与已更新字段”的时候,即使有关备注字段或通用型字段客户端程序被更改,Visual FoxPro 发送 UPDATE-SQL 会把这两类字段从 Where 子句中挖去。这样可以避免以上我提出的“一个错误、一个不合理”。
可以这样设定:
DBSetProp('VEMPLOYEES', 'View', 'CompareMemo', .F.)
字段属性
DefaultValue
如果您希望对远程视图执行Append命令时,系统自动填列有关字段,那么就有必要设定该属性:
DBSETPROP("myview.myfield", "field", "DefaultValue", ".T. ")
DBSETPROP("myview.myfield", "field", "DefaultValue", "'Bob'")
DBSETPROP("myview.myfield", "field", "DefaultValue", Date())
DataType
以下是Visual FoxPro字段类型与SQL Server字段类型的比较:
SQL type
| Visual FoxPro type
| binary, varbinary
| Memo
| bit
| Logical
| char, varchar
| Character
| datetime, smalldatetime
| Datetime
| decimal
| Numeric
| float
| Double
| image
| General
| int, smallint, tinyint
| Integer
| money, smallmoney
| Currency
| numeric
| Numeric
| sysname
| Character
| text
| Memo
| timestamp
| Memo
|
| 您可以这样设置:
DBSETPROP("Vemployees.birthdate", "field", "DataType", "D")
UpdateName
这个属性在多表连接是很重要,在多个表中许多列可能有相同的名称,所以必须明确的告诉远程视图,视图中的列与数据源表的列的对应关系。如:
DBSETPROP("VCustomers.postalcode", "field", "UpdateName", "customers.postalcode") |
|