vfp远程视图与spt应用详解
-
远程视图(Remote Views)与SPT(SQL pass through)是vfp为开发Client/Server程序提供的两个内置的解决方案,如果您更深入的应用vfp,这两部分的内容是必须掌握的。
由于内容比较多,文章分成若干篇几次发完。但个人认为,远程视图与spt又是不可分割的整体,虽然spt应用得比较多,但若对远程视图没有比较深入的了解,我想你也不见得能用好spt。
因此做了这个目录,希望您能从远程视图开始,逐步了解这两个伟大的开发工具的使用。
一、SPT详解................................................................................................................... 1 二、用vfp与sql server构建Client/Server应用程序(SPT)(1) .............................................. 7 三、用vfp与sql server构建Client/Server应用程序(SPT)(2) ............................................ 22 四、用vfp与sql server构建Client/Server应用程序(远程视图)(1) .................................... 25 五、用vfp与sql server构建Client/Server应用程序(远程视图)(2) .................................... 30 六、用vfp与sql server构建Client/Server应用程序(远程视图)(3) .................................... 34 七、用vfp与sql server构建Client/Server应用程序(远程视图)(4) .................................... 39 八、用vfp与sql server构建Client/Server应用程序(远程视图)(5) .................................... 48 foxpro for graph对象全接触(1) ........................................................................................ 52 foxpro for graph对象全接触(2) ........................................................................................ 55 用vfp动态生成数据网页(一) ..................................................................................... 60 用vfp动态生成数据网页(二) ..................................................................................... 64
一、SPT详解
-
说在前面
熟悉 Fox 的朋友都知道,在 VFP 里我们可以使用远程视图 (Remote View) 和 SPT(SQL Pass Through) 技术控制远程异构数据库。这些技术其实是 VFP 对 ODBC 的 API 的封装,所以对于用户来说访问远程数据库就像操作传统的DBF一样简单。关于这两种技术的使用,完全可以洋洋洒洒地写下一本书,鉴于本文主题及篇幅,这里仅枚举 SPT 技术访问远程数据的应用。
SPT与远程视图
很多人搞不懂有了远程视图这样直观、简单的工具,为什么还需要 SPT 呢?确实 SPT 较远程视图难以掌握,但细细体会你会发现:远程视图其实是对 SPT 的可视化工具!SPT 较远程视图更具威力,远程视图提供的功能只是 SPT 的一个子集。仔细探索两者优劣,我们发现:
SPT 的优势:
1. 一次得到多个Cursor
2. 执行除 Select 以外的其他 SQL 语句,如 Insert、Update、Delete等 3. 执行远程数据库的存储过程
4. 执行远程数据库的特殊函数、命令 5. 事务管理
SPT 的劣势:
1. 没有图形用户界面
2. 必须人工维护连接
3. 得到的Cursor默认是\可读写\,要使它成为\可更新\必须经过设定
下面就顺着我们对 SPT 的认识,来浏览一下这种伟大的工具吧!(注意:本文所有例程均使用 SQL Server的NorthWind 数据库演示)
管理连接:
建立连接:
(注意:本文所有示例代码若用到连接的,默认采用\建立连接\代码中产生的连接句柄\)
WAIT ' 连接到 SQL Server 上去 ' NOWAIT NOCLEAR WINDOW
SQLSETPROP(0,\设置环境为:\从不显示 ODBC 登陆对话框\ con=SQLSTRINGCONNECT(\
Server;Server=BOE;Uid=sa;pwd=;database=northwind\
*假定 SQL Server 服务器名为 BOE, 用户 ID 是sa, 口令是空串
*如果你的 SQL Server 的服务器名, 用户 ID, 口令与上不同,请修改以上代码中的相关部分以符合你系统中的设置
WAIT clear IF con<=0
MESSAGEBOX(' 连接失败 ',64,' 连接到 SQL Server 上去 ') ELSE
MESSAGEBOX(' 连接成功 ',64,' 连接到 SQL Server 上去 ') ENDIF
断开连接:
SQLDISCONNECT(con)
一次得到多个Cursor
我们可以用一次 SPT 传回多个Cursor,如下:
cSQL=\* FROM EMPLOYEES\CUSTOMERS\ ?SQLEXEC(con,cSQL,\
SQLEXEC( ) 的返回值表示Cursor的数量,这里返回 3 。这三个Cursor分别以: TEMP,TEMP1,TEMP2 命名。
执行除 SQL-Select 以外的 SQL 语句
cSQL=\ cSQL=cSQL+\ cSQL=cSQL+\ELSE INSERT VALUES('TEST',' 这是一个测试! ')\
CUSTOMERS(CUSTOMERID,COMPANYNAME)
*
FROM
IF SQLEXEC(CON,cSQL)<=0
MESSAGEBOX(' 执行失败 ',64,' 发送语句到 SQL Server 上去 ') ELSE
MESSAGEBOX(' 执行成功 ',64,' 发送语句到 SQL Server 上去 ') ENDIF
行文至此,也许有朋友会问:如果 SQL 语句中 CUSTOMERID 是一个变量怎么办呢?有两个常用的解决方案:
拼接字符串
CUSTID='TEST' cSQL=\EXISTS(SELECT CUSTOMERID='\
*
FROM
CUSTOMERS
WHERE
cSQL=cSQL+\ cSQL=cSQL+\ELSE INSERT CUSTOMERS(CUSTOMERID,COMPANYNAME) VALUES('\这是一个测试! ')\ ?SQLEXEC(CON,cSQL)
SPT 标准变量传递法
CUSTID='TEST'
cSQL=\ cSQL=cSQL+\ cSQL=cSQL+\ELSE INSERT CUSTOMERS(CUSTOMERID,COMPANYNAME) VALUES(?CUSTID,' 这是一个测试! ')\ ?SQLEXEC(CON,cSQL)
执行远程数据库的存储过程
存储过程的好处自是不必多言,下面就让我们看看怎样用 SPT 调用远程数据库的存储过程。下面我们演示的是 NorthWind 数据库中的存储过程\,它的作用是返回指定客户关于产品的消费数量合计。据我所知,这里有两种书写格式供大家选择:
使用 T-SQL 的写法:
CUSTID='VINET'
?SQLEXEC(CON,'EXEC CustOrderHist ?CUSTID','TEMP1')
使用 ODBC 的写法:
CUSTID='VINET'
?SQLEXEC(CON,'{CALL CustOrderHist(?CUSTID)}','TEMP2')
存储过程常常会需要返回一些变量,通用的方法就是使用输出参数。在演示之前,我们先用 SPT 在 SQL Server 建立一个包含输入、输出参数的存储过程。
cSQL=\EXISTS(select * from sysobjects where id=object_id('MY_PROC') and OBJECTPROPERTY(id,'IsProcedure')=1)\
cSQL=cSQL+\如果存储过程My_proc已经存在,就删除它 ?SQLEXEC(con,cSQL) cSQL=\TE PROCEDURE MY_PROC @EmployeeID int,@Desc varchar(100) output as /* 只支持寻找直接下属 */\
cSQL=cSQL+\ cSQL=cSQL+\SELECT * FROM
EMPLOYEES
WHERE
REPORTSTO=@EMPLOYEEID\
cSQL=cSQL+\ cSQL=cSQL+\
cSQL=cSQL+\SELECT @Desc=' 找到了 '+CAST(@ROW AS VARCHAR(4)) +' 位下属 '\
cSQL=cSQL+\这是一位普通员工 '\ ?SQLEXEC(con,cSQL)
使用 T-SQL 的写法:
EMPID=2
DESCRIPTION=\
?SQLEXEC(CON,'EXEC MY_PROC ?EMPID,?@DESCRIPTION','TEMP1') ?DESCRIPTION
使用 ODBC 的写法:
EMPID=2
DESCRIPTION=\
?SQLEXEC(CON,'{CALL MY_PROC(?EMPID,?@DESCRIPTION)}','TEMP2') ?DESCRIPTION
执行远程数据库的特殊函数、命令
如果在 SQL Server 中你有足够的权限,通过 SPT 你可以完全控制 SQL Server ,这里我们演示\怎样取得数据库服务器的时间\:
?SQLEXEC(con,\ ?temp1.serverdatetime USE IN (\
事务管理
在一些复杂的应用中,往往会有一项操作影响好几个表的情况。就客户端来说,发送到远程数据库的数据变动可能来源很多:表缓冲的多行记录的变动,行缓冲的单行记录变化,以及前文我们演示的直接用 SQL 语句传递的数据维护,林林总总??怎样把这些更新行为控制在一个事务中要么--一起成功,要么一起回滚。
cSQL=\
cSQL=cSQL+\CUSTOMERS(CUSTOMERID,COMPANYNAME) VALUES('TEST1',' 这是一个测试! ')\
SQLSETPROP(CON,\开始一个事务 IRETURN=SQLEXEC(CON,cSQL) IF IRETURN=1
SQLCOMMIT(CON)&& 事务交付 ELSE
SQLROLLBACK(CON)&& 事务回滚
ENDIF
SQLSETPROP(CON,\重新回到自动事务处理状态
&&就本例而言,\总是不能执行的,SQL Server会返回错误: &&DELETE statement 'FK_Orders_Customers'.
conflicted
with
COLUMN
REFERENCE
constraint
&&The conflict occurred in database 'Northwind', table 'Orders', column 'CustomerID'. &&所以这笔事务总是被回滚的!!
从例程中我们看到,我们开启的事务其实是针对\连接\的,也就是说通过该\连接\的所有数据更新都包含于事务中,直到事务被回滚或交付。
SQLSETPROP(CON,\,2 ), 其实是开启了人工事务处理,也就是说必须由用户明确的给出交付或者回滚指令,事务才会结束。所以笔者以为:完成一笔事务以后,应执
行 SQLSETPROP(CON,\) 将\连接\的事务模式设为默认的\自动\,这样可以防止用户陷入未知的事务中去。
设为可更新
到目前为止,我们已经演示了 6 个 SPT 专题了,除了第一个\连接管理\在远程视图中能够实现之外,其余的远程视图都无法实现。下面我们要讨论一下怎样把 SPT 传回的结果集合设为\可更新\,总的来说远程视图提供的就是这个功能。
在默认状况下, SPT 从远程数据库得到的Cursor是\可读写\的,即:可以对它进行\、 Update 、 Insert 、 Delete \的操作,但数据的变化不会反映到数据源。\可更新\的特色就是可以直接将客户端的数据变动,自动生成一系列 SQL 描述更新远程数据库。
实现\可读写\到\可更新\必须经历以下五步的设置:
1. CURSORSETPROP(\数据源表名 , 可更新Cursor名 )
此步骤设定的是数据源里(SQL Server)待更新的表名,如果涉及多个表就这样写: CURSORSETPROP(\。
2. CURSORSETPROP(\关键字段 , 可更新Cursor名 )
此步骤是设定关键字段的,这个关键字段是可更新Cursor的字段,而不是数据源里的字段。
3. CURSORSETPROP(\TABLEFIELDLIST\可更新字段列表 , 可更新Cursor名 ) 此步骤设定的是在可更新Cursor里哪些字段的变动要被反映到数据源,即哪些字段时可更新的。
4. CURSORSETPROP(\TENAMELIST\前后段字段对应关系列表 , 可更新Cursor名 )
此步骤设定数据源字段与可更新Cursor字段的对应关系。
5. CURSORSETPROP(\TES\可更新Cursor名 ) 这个步骤是打开 SQL 发送开关,最关键的一步。
为便于大家理解,现将以上五步实例化:
例一:
SQLEXEC(con,\ SELECT mycursor
CURSORSETPROP(\ CURSORSETPROP(\
CURSORSETPROP(\ CURSORSETPROP(\categories.categoryid,categoryname categories.categoryname,\
\ CURSORSETPROP(\
例二:
SQLEXEC(con,\
a.productid,a.productname,a.unitprice,b.categoryid,b.categoryname,c.supplierid,\ \
\ \ SELECT mycursor
CURSORSETPROP(\ CURSORSETPROP(\ CURSORSETPROP(\
\,\
CURSORSETPROP(\ \
\ \
suppliers.supplierid,suppliername
suppliers.companyname,contactname
suppliers.contactname\
CURSORSETPROP(\
行笔匆匆,终于把我认识的 SPT 基本操作写完了,掌握这些,已能编写不错的 C/S 程序。虽然,本文是用 SQL Server 作为远程数据库,但是如果你使用 DB2 、 Oracle ,在 VFP 中也是一样处理。
本文来自编程入门网:http://www.bianceng.cn/Programming/vfp/200705/974.htm
二、用vfp与sql server构建Client/Server应用程序(SPT)(1)
-
一些题外话
最近有一种好的现象——越来越多的Visual FoxPro用户开始注意到Client/Server应用程序的重要性,他们开始安装SQL Server了。作为开发人员,我们没有必要深入认识SQL Server的种种(我个人以为那是数据库管理员-DBA的事情),我们要解决的是怎样使Visual FoxPro与SQL Server在一起顺利的运行,怎样发挥两者的优越特性。
“远程视图”一章已经涉及到了许多深层次的问题,但遗憾的是(可能是文章太过枯燥)很少有人关注它!笔者到现在还是认为,“远程视图”一文绝对是目前网络上能得到的最好的简
体中文学习资料,没有哪篇文章会为你深入介绍“更新冲突发生的原理”,没有哪篇文章会为你讲解“Visual FoxPro发送更新到SQL Server 的原理”,没有哪篇文章会为你讲解“各种连接属性的意义”??
在国内众多Visual FoxPro图书中,我最喜欢的作者是台湾的章立民先生。我个人以为Visual FoxPro的章立民、Delphi的李维、Visual Basic的王国荣是那种多数读者看到是他们写的作品就应该买的那类作者。三位大家的书我都有拜读,窃以为:文采最棒的是章立民先生,名气最小的也是章立民先生(也许是因为Visual FoxPro的关系)。中国铁道出版社为国内的Visual FoxPro事业做了一件好事——在99年前后出版了章立民先生的四册图书,分别是:《Visual FoxPro 6.X 中文版程序设计--教学指南》、《Visual FoxPro 6.X 中文版程序设计--基础加强》、《Visual FoxPro 6.X 中文版程序设计--问题集》、《Visual Foxpro 6.X 中文版程序设计--应用实务》。除了教学指南外,其余三本都是进阶应用的好书,但很奇怪的是他们在市场上销售的并不理想,直到今年上半年在上海书市中还能觅到,相反的一些粗制滥造的东西却是卖得火爆,真是咄咄怪事!我不认识章立民先生,与中国铁道出版社更无关系,但我愿意为他们做广告,如果章立民能为Visual FoxPro 7写出更好的东西,我们将感激不尽,也希望国内出版社积极参与引进!本文在写作上参考了《Visual Foxpro 6.X 中文版程序设计--应用实务》的内容,但绝对不是抄袭,大家有兴趣可以买一本读上一读。
为什么要引入SPT的概念
我们已经讲解了“远程视图”,您也许已经发现了远程视图的不足,是的远程视图的缺陷正是SPT的优势所在:
执行除Select以外的其他的SQL语句,如Insert 及Update等
执行后端数据库的存储过程
执行后端数据库的特殊的函数、命令
一次得到多个数据集合
这些都是我们在系统开发时要遇到的实现问题,Visual FoxPro 小组注意到了,于是提出了SPT的概念。SPT是SQL Pass Through的简写,它与远程视图在一起共同组成Visual FoxPro远程数据访问体系,利用它们你就可以开发完整的Client\\Server构架的系统了!与“远程视图”相比,SPT技术也有以下缺陷:
没有图形用户界面
必须人工维护连接
数据集合是“可读写光标”,要使它成为“可更新光标”必须进行设定
从我的经验来看待“远程视图”与SPT,我以为两者是Visual FoxPro远程数据访问体系的双臂,相辅相成。不用SPT,系统就会变得弱智、愚笨;不用远程视图,系统开发会变得缓
慢、繁琐。我以为,“远程视图”是组件化的SPT,是微软公司对SPT部分功能的图像化封装,所以它显得更有生产效率,同时由于系统作得多了,功能也就弱了,“远程视图”能做得就是那些事情——从数据源读取一个结果集,在客户端对它维护,并自动将变动结果更新到数据源。然而作为系统开发,我们会在实践中遇到林林总总、奇奇怪怪的需求,只有“远程视图”独立担当显然是不行的,所以SPT的出现有它的必然性。在其他的远程数据处理组件中也有类似的概念,例如ADO中的RecordSet相当于Visual FoxPro的远程视图,Command相当于Visual FoxPro的SPT。
13个SPT函数
Visual FoxPro 中内置了13个以SQL开头的函数,我们把它们称为SPT函数。就是这13个函数完成了Visual FoxPro的所有的SQL Pass Though功能。从功能上看,我们可以把它们分成五个部分:
连接函数
连接建立函数:SqlConnect(),SqlStringConnect()
连接的断开函数:SqlDisconnect()
核心的执行函数
SQL语句传输及执行函数:SqlExec(),SqlPrapare()
批次模式下更多数据集的取得函数:SqlMoreResults()
异步模式下撤销正在执行的SPT的函数:SqlCancel()
事务处理函数
SqlCommit(),SqlRollBack()
连接通道属性函数
SqlGetProp(),SqlSetProp()
数据源的信息截取函数
SqlTables(),SqlColumns()
连接到SQL Server
连接
SqlConnect()的两种用法
一般来说SqlConnect有两种用法:直接调用操作系统里的用户型或系统型DNS;使用当前数据库DBC的连接对象。
如果已经在操作系统中制作了一个名为LocalServer的系统型DNS,在命令窗口中我们就可以直接键入:
SQLCONNECT(\,\
如果当前数据库中存在连接对象,我们就可以这样调用它:
SQLCONNECT(\
SqlStringConnect()函数的用法
在用了SqlConnect以后,大家也许会发现不太自由,能不能及时动态建立连接呢?可以的,用SqlStringConnect()就可以了:
SQLSTRINGCONNECT(\
Server;Server=See-you;Uid=sa;pwd=;database=northwind\
我们解释一下参数字符串的意义,driver指明了使用哪一个ODBC驱动程序,这里是SQL Server;Server是指SQL Server的服务器名称,我的服务器叫:See-You;UID是在SQL Server的用户名称,这里使用SA;PWD是用户口令,这里为空值;最后我们指定了要连接的数据库的名字,这次我们仍然使用NorthWind数据库。所有这些条件组成了一个字符串,各条件用分号分割,我的实验表明对这大小写的要求不高,当然不包括用户口令和用户名。
关于连接的两大问题
连接建立起来很简单,但尚有问题要解决:怎么判断连接是否成功?怎么屏蔽SQL Server的登陆对话框?
第一个问题很好解决,只要SqlConnect()或是SqlStringConnect()返回正整数(>0的数)就表示连接成功,得到的正整数很重要——就是连接句柄!
STORE SQLCONNECT('LocalServer', 'sa','错误密码') TO gnConnHandle IF gnConnHandle <= 0
= MESSAGEBOX('连接错误', 16, '连接到SQL Server') ELSE
= MESSAGEBOX('连接成功', 48, '连接到SQL Server')
ENDIF
在有些时候,连接发生错误会弹出以下两个提示框(有时出现前一个、有时出现后一个,有
时连续出现):
连接错误提示
SQL Server的登录提示
这是一个让人迷惑的的问题,我以为:第一个“连接错误提示”是限于Visual FoxPro或ODBC的层次,也就是说与SQL Server无关,这种错误的出现往往是找不到SQL Server服务器之类的问题,如以下的语句:
SQLSTRINGCONNECT(\
Server;Server=See-y1ou;Uid=sa;pwd=;database=northwind\
这是为了让用户更正连接信息的,但是在实际开发中没有哪个程序员希望此对话框暴露在用户面前。如果我们使用的连接是DBC的连接对象,问题就比较好解决,因为在建立连接时我们可以设定连接对话框的出现规律:
同样的我们可以通过命令来控制“显示 ODBC 登录提示”:
DBSETPROP(\发生连接错误时弹出 DBSETPROP(\任何时候总是误时弹出 DBSETPROP(\发任何时候都不弹出
但是当我们使用连接字符串、直接使用操作系统的DNS时,它们都不是Visual FoxPro的DBC中的连接对象,就不能用以上的方法控制,我们可以设定Visual FoxPro的环境:
SQLSETPROP(0,\
注意我对第一个参数赋值为0,表明设定的是对Visual FoxPro的系统环境进行设定。值得注意的是,系统环境设定只是与这种即时产生连接有效,对存放在DBC中的连接对象并不起作用,根据连接对象建立的连接的有关属性还是要通过DBSETPROP()来设定。
断开连接
建立的连接就应该再不用时及时断开,断开连接请使用SqlDisConnect()函数。
Local hConn
hconn=SQLCONNECT(\ ?SQLDISCONNECT(hconn) //返回1表示连接断开成功
这里有三个问题提请大家注意。第一,如果hconn是一个有效的连接句柄,对它执行SqlDisConn()函数会有这样的结果,成功返回1,其他表示断开操作失败;第二:如果hconn不是一个有效的连接句柄,Visual FoxPro将触发一个错误,错误代号为1466,怎样判断某个连接句柄是不是有效的方法,我们在后文介绍;第三,如果想一下子断开所有的连接,您可以使用这样的方法:
SQLDISCONNECT(0)
从后端得到数据集
得到一个数据集
SQLEXEC(nConnectionHandle, [cSQLCommand, [cCursorName]])
先来解释一下SQLEXEC()函数:参数nConnectionHandle表示连接句柄;参数cSQLCommand表示要传送的语句,注意此语句一定是数据源认得的语句,而不是Visual FoxPro的语句。这一点从SQL Pass Though这个名字上就能看得出了,顾名思义:Visual FoxPro只不过是是将别人的语言传送给别人,这一点在SQLEXEC()函数体现的特别突出;参数cCursorName表示得到的结果集的名字,如果省略,返回的结果集将以Sqlresult命名。
如果返回1表示,执行成功;如果返回0表示正在执行;如果返回负数表示执行失败。
如果我们希望得到NorthWind数据库中的Employees表的数据,就可以执行以下命令:
SQLEXEC(hconn,\Browse
判断连接有效性
这是一个非常实际的问题,我们建立了一条连接以后,怎样知道Visual FoxPro与SQL Server的通信是正常的,机连接是确实有效的,只有一种方法——应用这条连接,可以这样:
?SQLEXEC(hconn,\
&&一个空的命令发送,返回1的话证明连接可用!
大型语句的传递技巧
如果想得到一个更为复杂的结果集合,我们可以传送依据大型的SQL-Select描述。这里有一些经验送给大家:
如果传送的语句太大(大约是超过256个字符,其实没有必要去计数只要你觉得语句很大时就应该考虑我的建议,不然您就没有办法传送大型的语句),您直接把要传送的语句放入SQLEXEC()中,Visual FoxPro就会报错,说无法认得此语句,于是大家就又多了诽谤Visual
FoxPro的一条把柄!(根据我的研究,在Delphi中也有此问题)解决之道是:把长长的语句先放在一个变量中,再将此变量作为参数赋给SQLEXEC()函数。
关于分隔符的使用技巧,Visual FoxPro中分隔字符串使用三种符号:双引号、单引号、方括号。在配置要传送的语句时一定要用双引号作为字符串的分隔符,其他的两个符号应另作他用。
以上的建议绝对经典,以下我们就用实例来说明:
在SQL Server中要得到详细销售情况的SQL-Select语句如下(详细的知识见本人撰写的《SELECT-SQL 周周谈》一文):
SELECT
ORDERS.SHIPNAME,ORDERS.SHIPADDRESS,ORDERS.SHIPCITY,ORDERS.SHIPREGION,ORDERS.SHIPPOSTALCODE, ORDERS.SHIPCOUNTRY,ORDERS.CUSTOMERID,CUSTOMERS.COMPANYNAME CUSTOMERNAM,
EMPLOYEES.FIRSTNAME+' '+EMPLOYEES.LASTNAME AS SALESPERSON, ORDERS.ORDERID,ORDERS.ORDERDATE,ORDERS.REQUIREDDATE, ORDERS.SHIPPEDDATE,SHIPPERS.COMPANYNAME AS SHIPPERNAME, [ORDER
DETAILS].PRODUCTID,PRODUCTS.PRODUCTNAME,[ORDER
DETAILS].UNITPRICE,[ORDER DETAILS].QUANTITY,[ORDER DETAILS].DISCOUNT, [ORDER DETAILS].UNITPRICE*[ORDER DETAILS].QUANTITY*(1-[ORDER DETAILS].DISCOUNT) AS EXTENDEDPRICE, ORDERS.FREIGHT FROM ((((EMPLOYEES INNER JOIN ORDERS ON EMPLOYEES.EMPLOYEEID=ORDERS.EMPLOYEEID)
INNER JOIN CUSTOMERS ON ORDERS.CUSTOMERID=CUSTOMERS.CUSTOMERID) INNER JOIN SHIPPERS ON ORDERS.SHIPVIA=SHIPPERS.SHIPPERID)
INNER JOIN [ORDER DETAILS] ON ORDERS.ORDERID=[ORDER DETAILS].ORDERID) INNER JOIN PRODUCTS ON [ORDER DETAILS].PRODUCTID=PRODUCTS.PRODUCTID
这是一句12行,不计空格864个字符的大型语句,现在要通过Visual FoxPro的SQLEXEC()函数传送:
local csq
&&配置待传送的语句 csql=csql+\
ORDERS.SHIPNAME,ORDERS.SHIPADDRESS,ORDERS.SHIPCITY,ORDERS.SHIPREGION,\
csql=csql+\,ORDERS.CUSTOMERID,\
csql=csql+\csql=csql+\'+EMPLOYEES.LASTNAME SALESPERSON,ORDERS.ORDERID,ORDERS.ORDERDATE,\
AS AS
csql=csql+\TE,ORDERS.SHIPPEDDATE,SHIPPERS.COMPANYNAME AS SHIPPERNAME,\csql=csql+\DETAILS].PRODUCTID,PRODUCTS.PRODUCTNAME,[ORDER DETAILS].UNITPRICE,\
csql=csql+\,[ORDER DETAILS].DISCOUNT,\csql=csql+\DETAILS].UNITPRICE*[ORDER DETAILS].QUANTITY*(1-[ORDER DETAILS].DISCOUNT) AS EXTENDEDPRICE,\csql=csql+\csql=csql+\FROM ((((EMPLOYEES
INNER
JOIN
ORDERS
ON ON
EMPLOYEES.EMPLOYEEID=ORDERS.EMPLOYEEID)\csql=csql+\INNER JOIN
ORDERS.CUSTOMERID=CUSTOMERS.CUSTOMERID)\
CUSTOMERS
csql=csql+\
csql=csql+\INNER JOIN [ORDER DETAILS] ON ORDERS.ORDERID=[ORDER DETAILS].ORDERID)\csql=csql+\INNER
JOIN
PRODUCTS
ON
[ORDER
DETAILS].PRODUCTID=PRODUCTS.PRODUCTID\&&传送,得到的光标名称为默认的SqlResult SQLEXEC(hconn,csql)
解释:由于语句很长,我花费了多条赋值语句才完成配置这个字符串。大家注意到了我选用双引号作为字符串分隔符,这里面是有大学问的。在SQL Server中有待空格的表名如:Order Details,当它用于SQL语句时,就必须要用方括号标示,所以如果在Visual FoxPro中如果用方括号分割字符串的话,就会有冲突!;单引号是SQL Server中分隔字符串、日期值的符号,在Visual FoxPro中如果使用单引号分隔字符串的话,也会造成冲突,当然在以上语句中我们没有看到单引号的身影,不过我们做一个变动,大家就会感到问题的存在了!
local csq
&&配置待传送的语句 csql=csql+\
ORDERS.SHIPNAME,ORDERS.SHIPADDRESS,ORDERS.SHIPCITY,ORDERS.SHIPREGION,\
csql=csql+\,ORDERS.CUSTOMERID,\
csql=csql+\csql=csql+\'+EMPLOYEES.LASTNAME SALESPERSON,ORDERS.ORDERID,ORDERS.ORDERDATE,\
csql=csql+\TE,ORDERS.SHIPPEDDATE,SHIPPERS.COMPANYNAME AS SHIPPERNAME,\csql=csql+\DETAILS].UNITPRICE,\
csql=csql+\,[ORDER DETAILS].DISCOUNT,\
csql=csql+\DETAILS].UNITPRICE*[ORDER DETAILS].QUANTITY*(1-[ORDER DETAILS].DISCOUNT) AS EXTENDEDPRICE,\
DETAILS].PRODUCTID,PRODUCTS.PRODUCTNAME,[ORDER
AS
csql=csql+\
csql=csql+\FROM ((((EMPLOYEES INNER EMPLOYEES.EMPLOYEEID=ORDERS.EMPLOYEEID)\csql=csql+\INNER JOIN
ORDERS.CUSTOMERID=CUSTOMERS.CUSTOMERID)\
JOIN
ORDERS
ON ON
CUSTOMERS
csql=csql+\csql=csql+\INNER JOIN [ORDER DETAILS] ON ORDERS.ORDERID=[ORDER DETAILS].ORDERID)\csql=csql+\INNER
JOIN
PRODUCTS
ON
[ORDER
DETAILS].PRODUCTID=PRODUCTS.PRODUCTID\
csql=csql+\TE BETWEEN '1996-10-01' AND '1997-09-30'\csql=csql+\csql=csql+\&&传送,得到的光标名称为默认的SqlResult SQLEXEC(hconn,csql)
传递变量
上面一个例子充分证明了我关于字符串分隔符的经验,接着我又为大家提出了一个新课题:怎样传递变量到SQL Server中。上例中,我们为语句加入了Where字句,下面我们把其中的条件值变为变量,这样这句SQL传递才变得真正的有意义!
我以为关于变量的传递有两种方法,下面逐一介绍。
第一种是利用:问号+变量来传递的,这是一种常用的方法,用起来也很简单,不用担心变量的类型,ODBC会自动的转化。下面的例子中就涉及到了日期型、数值型、字符型变量,我们根本不用担心变量类型的转化,只要在前面加上问号填入语句中就行了:
local csq
&&要传送的变量 dDate1={^1996-10-01} dDate2={^1997-09-30} cCustomerID=\nQty=50
&&配置待传送的语句 csql=csql+\
ORDERS.SHIPNAME,ORDERS.SHIPADDRESS,ORDERS.SHIPCITY,ORDERS.SHIPREGION,\
csql=csql+\,ORDERS.CUSTOMERID,\
csql=csql+\
csql=csql+\'+EMPLOYEES.LASTNAME AS SALESPERSON,ORDERS.ORDERID,ORDERS.ORDERDATE,\csql=csql+\TE,ORDERS.SHIPPEDDATE,SHIPPERS.COMPANYNA
ME AS SHIPPERNAME,\
csql=csql+\DETAILS].PRODUCTID,PRODUCTS.PRODUCTNAME,[ORDER DETAILS].UNITPRICE,\
csql=csql+\,[ORDER DETAILS].DISCOUNT,\
csql=csql+\DETAILS].UNITPRICE*[ORDER DETAILS].QUANTITY*(1-[ORDER DETAILS].DISCOUNT) AS EXTENDEDPRICE,\csql=csql+\
csql=csql+\FROM ((((EMPLOYEES INNER EMPLOYEES.EMPLOYEEID=ORDERS.EMPLOYEEID)\csql=csql+\
INNER
JOIN
JOIN
ORDERS
ON ON
CUSTOMERS
ORDERS.CUSTOMERID=CUSTOMERS.CUSTOMERID)\
csql=csql+\csql=csql+\INNER JOIN [ORDER DETAILS] ON ORDERS.ORDERID=[ORDER DETAILS].ORDERID)\
csql=csql+\INNER JOIN PRODUCTS DETAILS].PRODUCTID=PRODUCTS.PRODUCTID\
ON
[ORDER
csql=csql+\TE BETWEEN ?dDate1 AND ?dDate2\csql=csql+\csql=csql+\&&传送,得到的光标名称为默认的SqlResult SQLEXEC(hconn,csql)
分析:我们先来看一看书写的规范,请看:
csql=csql+\TE BETWEEN ?dDate1 AND ?dDate2\csql=csql+\csql=csql+\
问号+变量作为参数放置在SQL语句中,你也许会奇怪:问号+变量怎么还要放在Visual FoxPro的双引号里面,这不是作为一个字符串了吗?我们首先要清楚Visual FoxPro的SQLEXEC()传递的只能是一个字符串,所以问号+变量应当作为字符串的一部分!SQLEXEC()会将这个字符串给ODBC,ODBC分析一下这个字符串,他会发现问号+变量的特殊写法,于是它传递给SQL Server的语句是(整句太长,我就选择最后一段):
WHERE ORDERS.ORDERDATE BETWEEN @P1 AND @P2 AND ORDERS.CUSTOMERID LIKE @P3 AND [ORDER DETAILS].QUANTITY>@P4 ', N'@P1 datetime,@P2 datetime,@P3 varchar(3),@P4 float', 'Oct 1 1996 12:00AM', 'Sep 30 1997 12:00AM', '%C%', 5.000000000000000e+001
看到没有,所有变量都被重新命名,并用SQL Server的写法规范!
第二种方法是利用Visual FoxPro把变量合并到语句中去,产生一个字符串,这样的话传递给SQL Server的语句就是一个完整的字符串,其中不带变量,这样做的好处是更有效率,因为ODBC不用处理变量的传递,SQL Server也不用处理变量信息了。
local csq
&&要传送的变量 dDate1={^1996-10-01} dDate2={^1997-09-30} cCustomerID=\nQty=50
&&配置待传送的语句 csql=csql+\
ORDERS.SHIPNAME,ORDERS.SHIPADDRESS,ORDERS.SHIPCITY,ORDERS.SHIPREGION,\
csql=csql+\,ORDERS.CUSTOMERID,\
csql=csql+\
csql=csql+\'+EMPLOYEES.LASTNAME SALESPERSON,ORDERS.ORDERID,ORDERS.ORDERDATE,\
AS
csql=csql+\TE,ORDERS.SHIPPEDDATE,SHIPPERS.COMPANYNAME AS SHIPPERNAME,\csql=csql+\DETAILS].PRODUCTID,PRODUCTS.PRODUCTNAME,[ORDER DETAILS].UNITPRICE,\
csql=csql+\,[ORDER DETAILS].DISCOUNT,\
csql=csql+\DETAILS].UNITPRICE*[ORDER DETAILS].QUANTITY*(1-[ORDER DETAILS].DISCOUNT) AS EXTENDEDPRICE,\csql=csql+\csql=csql+\FROM ((((EMPLOYEES INNER JOIN ORDERS ON EMPLOYEES.EMPLOYEEID=ORDERS.EMPLOYEEID)\csql=csql+\INNER JOIN
CUSTOMERS
ON
ORDERS.CUSTOMERID=CUSTOMERS.CUSTOMERID)\
csql=csql+\
csql=csql+\INNER JOIN [ORDER DETAILS] ON ORDERS.ORDERID=[ORDER DETAILS].ORDERID)\csql=csql+\INNER
JOIN
PRODUCTS
ON
[ORDER
DETAILS].PRODUCTID=PRODUCTS.PRODUCTID\ccsql=csql+\TE BETWEEN '\AND '\
csql=csql+\csql=csql+\&&传送,得到的光标名称为默认的SqlResult SQLEXEC(hconn,csql)
这里的写法是由一些技巧的,就是努力做到模仿SQL Server的规范塑造字符串:
ccsql=csql+\TE BETWEEN '\AND '\
csql=csql+\csql=csql+\
如果将变量计算后代入,大家会发现如下的字符串:
\ORDERS.ORDERDATE BETWEEN '1996-10-01' AND '1997-09-30' AND ORDERS.CUSTOMERID LIKE '%C%' AND [ORDER DETAILS].QUANTITY>50\
看这就是前文我们没有涉及到变量传递的那句SQL语句,很简单吧——合并变量到SQL语句中,塑造纯正的SQL 语句!完全是Visual FoxPro在字符串计算方面的技巧,这里值得一提的是TransForm()函数,这是个很有用的函数,它能够将各种类型的值改造成为标准的字符串,特别像日期型这样做在多种写法的数据,他也能轻易规范!
把数据集设为可更新
Visual FoxPro的光标类型
光标的英文称呼是Cursor,在Visual FoxPro中习惯的提法是临时表(Temp Table),不过我想应该根据惯例叫它光标。因为Visual FoxPro的光标绝对强劲,如果称呼Temp Table恐造成误会,好像Visual FoxPro不支持光标一样。
Visual FoxPro的光标有三种:只读光标、可读写光标、可更新光标。
只读光标是那种不能被修改的光标,在Visual FoxPro中使用SQL-Select语句产生的光标就是典型的只读光标:
SELECT * FROM ORDERS INTO CURSOR MYCURSOR
对于结果集合MyCursor来说我们不能对它执行任何写操作,如:Replace、Delete、Update等。
可读写光标是那种可以进行读写操作,但光标上的数据变更不被反映到数据源的光标:
典型的可读写光标有三类,其一就是用SQLEXEC()得到的光标,我们可以对它进行各种操作(从了Zap、Pack这样的表压缩命令),但是任何数据的变动都不会反映到数据源。
第二类可读写光标是Visual FoxPro 7 的新特性,我们在SQL-Select语句上加入关键字readwrite就可以得到这种光标。这是一个非常棒的特性,有了它在Visual FoxPro中操作中间结果就更自由了:
SELECT * FROM ORDERS INTO CURSOR MYCURSOR READWRITE
第三类可读写光标是“没有设置发送更新”的本地视图和远程视图。
记得我在“远程视图”一章里反复强调:要想视图是可更新的就必须设定它的SendUpdate属性为.t.,如果没有设,试图就是可读写光标了,任何对视图数据的操作都不能反映到数据源里了。
可更新光标是那种可以进行读写操作,并且任何数据变动都会反映到数据源的光标:
典型的可更新光标就是可更新视图,对它的好处我就不多加议论了,因为详细的内容在“远程视图”一章里已经讨论过了。
把SQLEXEC()得到的结果集设定为可更新光标的五大步骤
在“远程视图”一章里我就反复强调,Visual FoxPro是怎么产生语句SQL描述,发送到SQL Server中去的。大家可以想象配置一条SQL-Update或是SQL-Insert或是SQL-Delete需要的要素,怎样把客户端变动转化为SQL语句需要的东西,就是我们要设定的东西:
A.CURSORSETPROP(\数据源表名,可更新光标名)
此步骤设定的是数据源里(SQL Server)待更新的表名,如果涉及多个表就这样写:CURSORSETPROP(\。
B.CURSORSETPROP(\关键字段,可更新光标名)
此步骤是设定关键字段的,这个关键字段是这可更新光标的字段,而不是数据源里字段。
C.CURSORSETPROP(\TABLEFIELDLIST\可更新字段列表,可更新光标名)
此步骤设定的是在可更新光标里哪些字段的变动要被反映到数据源,即哪些字段时可更新的。
D.CURSORSETPROP(\TENAMELIST\前后段字段对应关系列表,可更新光标名)
此步骤设定前后端字段的对应关系。
E.CURSORSETPROP(\TES\可更新光标名)
这个步骤就不应多说了,最关键的一步,不做的话前面的努力都白搭。
下面我用三个实例来说明问题:
例一:
cnn=SQLCONNECT(\
SQLEXEC(cnn,\SELECT mycursor
CURSORSETPROP(\
CURSORSETPROP(\
CURSORSETPROP(\CURSORSETPROP(\categories.categoryid,categoryname categories.categoryname,\
\CURSORSETPROP(\
数据源表是 NorthWind 数据库的Categories 表,可更新光表是mycursor
CURSORSETPROP(\,TABLES属性设定的是:被更新的数据源表Gategories
CURSORSETPROP(\,关键字用可更新光标的字段名:ID,而不是数据源表的字段名:categoryid
CURSORSETPROP(\,可更新字段列表都用可更新光标的字段名表示,而不是数据源表的字段名。
CURSORSETPROP(\categories.categoryid,categoryname categories.categoryname,description categories.description\,请注意这里的写法:每一组对应关系用逗号分开,前面写可更新光标的字段名,再放置一个空格,接着写数据源表的字段名(注意一定要加上数据源表名称)
例二
cnn=SQLCONNECT(\
SQLEXEC(cnn,\
a.productid,a.productname,a.unitprice,b.categoryid,b.categoryname,c.supplierid,\ \
\\SELECT mycursor
CURSORSETPROP(\CURSORSETPROP(%ursor\CURSORSETPROP(\
\,\
CURSORSETPROP(\\
\\suppliers.supplierid,suppliername suppliers.contactname\
CURSORSETPROP(\
suppliers.companyname,contactname
这是一个三个表(Categories,Products,Suppliers)的连接结果集合,算是复杂了,我们的目标就是使所有的字段都能更新到相应的表中。
注意“Tables”属性的写法,涉及三个表就罗列三个表!
注意“KeyFieldList”属性的写法,三个表的关键字都要列上。如果你没有把supplierid了如的话,那么来自于suppliers表的字段就无法更新到suppliers表中了。
例三
cnn=SQLCONNECT(\ SQLEXEC(cnn,\details]\
orderid,productid,unitprice,quantity,discount
from
[order
SELECT mycursor
CURSORSETPROP(\
CURSORSETPROP(\
CURSORSETPROP(\
CURSORSETPROP(\[order details].orderid,unitprice [order details].unitprice,quantity [order details].quantity,discount [order details].discount\CURSORSETPROP(\
这个结果集来自于一个表:Order Details。
注意“Tables”属性的写法,在SQL Server中这种带空格的表名请用方口号分隔,Tables属性指定的是数据源表,所以必须用:[order details]填入。
注意“KeyFieldList”属性的写法,这个order details表的主关键字是一个复合关键字,有orderid与productid联合组成,所以这里就要将他们一起填入。
一个很重要的属性——WhereType
当我们设定结果集为可更新光标后,还有一个重要的属性没有设定,就是WhereType。即,Where字句产生的依据,有四种情况:
CURSORSETPROP(\根据关键字
CURSORSETPROP(\根据关键字+可更新字段 CURSORSETPROP(\根据关键字+已更新字段 CURSORSETPROP(\根据关键字+时间戳
更详细的内容大家可以参看“远程视图”章节,在那里面我已经讲得很多了。
三、用vfp与sql server构建Client/Server应用程序(SPT)(2)
-
把数据集设为可更新
Visual FoxPro的光标类型
光标的英文称呼是Cursor,在Visual FoxPro中习惯的提法是临时表(Temp Table),不过我想应该根据惯例叫它光标。因为Visual FoxPro的光标绝对强劲,如果称呼Temp Table恐造成误会,好像Visual FoxPro不支持光标一样。
Visual FoxPro的光标有三种:只读光标、可读写光标、可更新光标。
只读光标是那种不能被修改的光标,在Visual FoxPro中使用SQL-Select语句产生的光标就是典型的只读光标:
SELECT * FROM ORDERS INTO CURSOR MYCURSOR
对于结果集合MyCursor来说我们不能对它执行任何写操作,如:Replace、Delete、Update等。
可读写光标是那种可以进行读写操作,但光标上的数据变更不被反映到数据源的光标:
典型的可读写光标有三类,其一就是用SQLEXEC()得到的光标,我们可以对它进行各种操作(从了Zap、Pack这样的表压缩命令),但是任何数据的变动都不会反映到数据源。
第二类可读写光标是Visual FoxPro 7 的新特性,我们在SQL-Select语句上加入关键字readwrite就可以得到这种光标。这是一个非常棒的特性,有了它在Visual FoxPro中操作中间结果就更自由了:
SELECT * FROM ORDERS INTO CURSOR MYCURSOR READWRITE
第三类可读写光标是“没有设置发送更新”的本地视图和远程视图。
记得我在“远程视图”一章里反复强调:要想视图是可更新的就必须设定它的SendUpdate属性为.t.,如果没有设,试图就是可读写光标了,任何对视图数据的操作都不能反映到数据源里了。
可更新光标是那种可以进行读写操作,并且任何数据变动都会反映到数据源的光标:
典型的可更新光标就是可更新视图,对它的好处我就不多加议论了,因为详细的内容在“远程视图”一章里已经讨论过了。
把SQLEXEC()得到的结果集设定为可更新光标的五大步骤
在“远程视图”一章里我就反复强调,Visual FoxPro是怎么产生语句SQL描述,发送到SQL Server中去的。大家可以想象配置一条SQL-Update或是SQL-Insert或是SQL-Delete需要的要素,怎样把客户端变动转化为SQL语句需要的东西,就是我们要设定的东西:
A.CURSORSETPROP(\数据源表名,可更新光标名)
此步骤设定的是数据源里(SQL Server)待更新的表名,如果涉及多个表就这样写:CURSORSETPROP(\。
B.CURSORSETPROP(\关键字段,可更新光标名)
此步骤是设定关键字段的,这个关键字段是这可更新光标的字段,而不是数据源里字段。
C.CURSORSETPROP(\TABLEFIELDLIST\可更新字段列表,可更新光标名)
此步骤设定的是在可更新光标里哪些字段的变动要被反映到数据源,即哪些字段时可更新的。
D.CURSORSETPROP(\TENAMELIST\前后段字段对应关系列表,可更新光标名)
此步骤设定前后端字段的对应关系。
E.CURSORSETPROP(\TES\可更新光标名)
这个步骤就不应多说了,最关键的一步,不做的话前面的努力都白搭。
下面我用三个实例来说明问题:
例一:
cnn=SQLCONNECT(\
SQLEXEC(cnn,\SELECT mycursor
CURSORSETPROP(\CURSORSETPROP(\
CURSORSETPROP(\CURSORSETPROP(\categories.categoryid,categoryname categories.categoryname,\
\
CURSORSETPROP(\
数据源表是 NorthWind 数据库的Categories 表,可更新光表是mycursor
CURSORSETPROP(\,TABLES属性设定的是:被更新的数据源表Gategories
CURSORSETPROP(\,关键字用可更新光标的字段名:ID,而不是数据源表的字段名:categoryid
CURSORSETPROP(\,可更新字段列表都用可更新光标的字段名表示,而不是数据源表的字段名。
CURSORSETPROP(\categories.categoryid,categoryname categories.categoryname,description categories.description\,请注意这里的写法:每一组对应关系用逗号分开,前面写可更新光标的字段名,再放置一个空格,接着写数据源表的字段名(注意一定要加上数据源表名称)
例二
cnn=SQLCONNECT(\ SQLEXEC(cnn,\
a.productid,a.productname,a.unitprice,b.categoryid,b.categoryname,c.supplierid,\ \
\\SELECT mycursor
CURSORSETPROP(\CURSORSETPROP(%ursor\CURSORSETPROP(\
\,\
CURSORSETPROP(\\
\\suppliers.supplierid,suppliername suppliers.contactname\
suppliers.companyname,contactname
CURSORSETPROP(\
这是一个三个表(Categories,Products,Suppliers)的连接结果集合,算是复杂了,我们的目标就是使所有的字段都能更新到相应的表中。
注意“Tables”属性的写法,涉及三个表就罗列三个表!
注意“KeyFieldList”属性的写法,三个表的关键字都要列上。如果你没有把supplierid了如的话,那么来自于suppliers表的字段就无法更新到suppliers表中了。
例三
cnn=SQLCONNECT(\ SQLEXEC(cnn,\orderid,productid,unitprice,quantity,discount details]\SELECT mycursor
CURSORSETPROP(\
CURSORSETPROP(\
CURSORSETPROP(\CURSORSETPROP(\
[order
details].orderid,unitprice
[order
details].unitprice,quantity [order details].quantity,discount [order details].discount\CURSORSETPROP(\
这个结果集来自于一个表:Order Details。
注意“Tables”属性的写法,在SQL Server中这种带空格的表名请用方口号分隔,Tables属性指定的是数据源表,所以必须用:[order details]填入。
注意“KeyFieldList”属性的写法,这个order details表的主关键字是一个复合关键字,有orderid与productid联合组成,所以这里就要将他们一起填入。
一个很重要的属性——WhereType
当我们设定结果集为可更新光标后,还有一个重要的属性没有设定,就是WhereType。即,Where字句产生的依据,有四种情况:
CURSORSETPROP(\根据关键字
CURSORSETPROP(\根据关键字+可更新字段 CURSORSETPROP(\根据关键字+已更新字段 CURSORSETPROP(\根据关键字+时间戳
更详细的内容大家可以参看“远程视图”章节,在那里面我已经讲得很多了。
from [order
四、用vfp与sql server构建Client/Server应用程序(远程视图)(1)
-
本文是《用 Visual FoxPro 与 SQL Server 构建 Client/Server 应用程序》系列的一部分,照例“远程视图”应不是开篇章节,但我们发现:在我们为网站准备的文章中有太多的理论性的东西,为了缓解这一矛盾,我们决定把“远程视图”提上来先写。
当下最流行的 ADO 脱胎于 Visual FoxPro,在实际使用中两者各有特色,所以不要小看 Visual FoxPro 在远程数据处理上的能力,它绝对强大!读者可以参看本站的《Microsoft Visual FoxPro 开发人员 ADO 快速入门》与《Visual FoxPro 漫谈》。
我们希望通过连载的形式完成《用 Visual FoxPro 与 SQL Server 构建 Client/Server 应用程序》一文,非常希望得到您宝贵的建议。
说在前面
由于本文是在两天之内赶出来的,构思不够严密,我在选择 SQL Server 端数据库时犯了一个错误:Northwind 数据库中绝大多数“字符串”型字段(列)使用了 nVarchar 型,Visual FoxPro 对此支持的不好(具体原因本站将另文论述)。以上可能造成演示程序不能正常工作,对此我们可以在 SQL Server 的 Enterprise Manager 中把有关字段的的类型从 nVarchar 改为 Varchar。我建议把 Customers 表中的 CustomerID 和 Phone字段的类型变化即可。同样的,我们可以把 Employees 的 Notes 字段类型变为 text。
不是我的错
Visual FoxPro 的视图设计器虽然能完成许多烦人的远程视图的操作,但对于特别复杂的远程视图可能会支持很差。在阅读本文做实验时,遇到以上问题请不要责怪作者。Visual FoxPro 的远程视图的功能非常强大的,大到工具程序“视图设计器”不能很好的支持!
前言
Visual FoxPro 为 Client/Server 构架的应用提供了两个内置的解决方案:远程视图(Remote Views)与 SPT(SQL pass through)。当然在Visual FoxPro中我们可以使用其它的远程数据连接的方法,比如 ADO。所有这些解决方案都各有优劣,远程视图最大的好处就是它可以非常方便地与Visual FoxPro内置控件绑定。每一个远程视图就是一个存储于Visual FoxPro数据库容器(DBC)的对象,是一句SQL-SELECT语句。远程视图通过ODBC(Open Database Connectivity)与异构数据库通讯。
虽然本文的实例使用SQL Server 作为后端数据库,但大家同样可以使用其它数据源作为后端数据库,例如 Oracle,IBM DB2,Informix,Sybase,Microsoft Access,Excel,甚至是Visual FoxPro自己。使用远程视图操作远程数据就像操作Visual FoxPro本地数据一般,以下我们将介绍这一伟大工具的初级应用。
连接-Connections
使用远程视图操作远程数据的第一步就是建立与远程数据源的通讯,这里有好几种方法可供选择,请注意所有这些方法都使用 ODBC 与远程数据连接。
这里有一个非常简单的远程视图,它的作用是:读取 Northwind 数据库中 Customers 表的记录到远程视图 Vcustomers 中。
CREATE SQL VIEW Vcustomers ; REMOTE CONNECTION Northwind ; AS SELECT * From Customers
*先别试用这条语句,因为连接还没有建立
在上述命令的第二行我们告诉Visual FoxPro使用连接 Northwind 与 SQL Server 通讯。在运行上面的语句时,Visual FoxPro将在两个地方寻找这个连接:
当前 数据库容器(DBC)中查看是否存在数据库“连接对象”——Northwind。笔者称之为基于 DSN 的连接对象。
如果没有发现,Visual FoxPro将在客户机的 OCBC Data Source Names(DSNs)中查看是否存在连接 Northwind。笔者称之为 DSN 连接。
建立 DSNs 连接
建立连接的最快、最方便的方法是建立DSNs,您可以在控制面板中打开 ODBC 控制器,如图1。
图 1. ODBC Data Source Administrator 面板
我们发现有三种DSN 连接:
用户型DSN、系统型DSN、文件型DSN。其中用户型只对建立它的用户有效,譬如你以 Administrator 的身份登录系统并建立了一个用户DSN,那么除非你以 Administrator 登录系统否则你不能使用这一DSN;系统型则对当前用机器所有用户生效,无论你以什么身份登录系统;文件型DSN实际上是一个以DSN为后缀名的文本文件。从 Visual FoxPro 的角度,笔者把这三种 DSN 分成两类:
用户型、系统型 DSN。它可以单独作为连接为远程视图使用;也可以作为“基于 DSN 连接对象”的基础,“基于 DSN 连接对象”依赖于用户型或是系统型 DSN。
文件型 DSN。它不可以单独作为连接为远程视图使用;它可以为设计“基于字符串的连接对象”提供资料,但“基于字符串的连接对象”不依赖于文件型 DSN。
我们先建立一个系统型 DSN。
在 ODBC Data Source Administrator 面板中选中 “System DSN”页,按“Add”键,再选择 SQL Server 驱动程序,按“完成”按钮。出现图2。
图 2. 设定 DSN 的名称及服务器
这里我们选择“(local)”,注意如果系统将尝试连接目标服务器如果无法连接将报错,如果顺利的话您将看到图3的画面,要求选择用户认证方式,这里我们选择 SQL Server 与 Windows 混合认证方式,并输入登录SQL Server的用户名:“sa”,口令为空。
图 3. 设定 DSN 的登录信息
如果顺利画面4将出现在屏幕上,这里有一个选项要特别的注意,就是选择目标数据库。这里我们当然是选“Northwind”。
图 4. 设定 DSN 的登录数据库
废话就不多说了,如果一些顺利,这条叫“Northwind”的System DSN 就建成了。
在Visual FoxPro的命令窗口中建立如下语句:
CREATE DATABASE Northwind
*建立本地数据库容器,存储数据库对象。 CREATE SQL VIEW VCustomers ;
REMOTE CONNECTION Northwind ; AS SELECT * FROM Customers
*建立远程视图,注意这个视图是数据库(DBC)的一个对象,所以必须先建立DBC USE VCustomers *打开远程视图 BROWSE
*浏览远程视图
建立数据库容器(DBC)中的“连接对象”
记得在前文我们讲过连接可以存在于两个地方,就是 DSNs与DBC,那么DSN与基于DSN的连接对象有什么区别?
基于DSN的连接对象可以为数个远程视图共享,而DSN连接不可以。我们知道,每一条与SQL Server的连接都是要收费的,共享连接就可以省钱;我们还知道,SQL Server管理每条连接大约要花去24K的内存空间,有人讲:24K不多,但您别忘了SQL Server是服务器,不只是您一个人使用它——东一条连接、西一条连接——系统效率一定会受影响。因此无论从开发成本还是系统工作效率的角度,共享连接都是开发人员必然的选择。
为了说明什么是共享连接,为了证明DSN连接不能为试图共享,我们做以下试验:
CREATE SQL VIEW VOrders ;
REMOTE CONNECTION Northwind SHARE;
AS SELECT * FROM Orders
*新建一个远程视图,仍然使用名称为Northwind 的DSN连接 DBSETPROP('VCustomers', 'View', 'ShareConnection', .T.) USE VCustomers IN 0 *打开刚才的那个视图 USE VOrders IN 0 *打开新建的远程视图
?CURSORGETPROP(\*显示 1
?CURSORGETPROP(\*显示 2
*这两个远程视图(光标)的连接句柄不是同一个。如果一条连接被共享了,那么这两个光标的连接句柄应是同一个数字;这两个视图虽然使用同一个 DSN 但实际上它们没有共享连接。
为了使用共享连接着一特性,我们建立“基于 DSN 的 连接对象”:
CREATE CONNECTION Northwind DATASOURCE Northwind
以上这条连接是建立在上面我们建立的DSN连接之上的,连接成为数据库容器的一个对象了。但注意:这种连接对象并不能摆脱DSN的控制,它的连接信息仍然存储在DSN中。
为了在视图间共享连接,我们必须设定设图的共享连接的属性,凡是参与共享连接的远程视图都必须具备共享连接的属性,否则共享连接还是不能实现。设定共享连接的方法有两种:
建立视图设计使用关键字 SHARE。如: CREATE SQL VIEW VOrders ;
REMOTE CONNECTION Northwind SHARE; AS SELECT * FROM Orders
设定已视图的ShareConnection的属性为.t.。如: DBSETPROP('VCustomers', 'View', 'ShareConnection', .T.)
为了证明连接共享的特性我们执行以下代码:
USE VCustomers IN 0 USE VOrders IN 0
?CURSORGETPROP(\*显示:1
?CURSORGETPROP(\*显示:1
我们已经学习了怎样建立“基于 DSN 的连接对象”,并了解了连接共享。仔细思考,您也许会发现“基于 DSN 的连接对象”存在下列问题:
运行时(RUN TIME)的可控性差。比如我们希望应用程序在不同的条件下使用不同的用户登录数据库服务器、或是不同的数据库,是不是要建立 N 个 DSN 以及 N 个“基于 DSN 的连接”?太可怕了!
由于“基于 DSN 的连接”对 DSN 的依赖性很强,例如:用户删除或是更改了 DSN 信息,那么我们应用程序就无法正常运行了!
为了解决以上问题,Visual FoxPro 提出了“连接字符串的概念”。笔者称之为:基于字符串的连接。一个连接字符串基本上包括五个主要内容:ODBC 驱动器、服务器、用户名、登录名、数据库。(针对 SQL Server 而言)
CREATE CONNECTION Northwind2 ;
CONNSTRING \TABASE=Northwind;SERVER=(local);PWD=\
五、用vfp与sql server构建Client/Server应用程序(远程视图)(2)
-
通过工具建立连接
以上我们都是通过命令的方式建立连接,现在我们将使用Visual FoxPro提供的连接设计器建立连接。上文我们讲到:连接对象分为两类:“基于 DSN 的连接对象”和“基于字符串的连接对象”。图5:
在连接设计器中选择“数据源、用户标识、密码”就说明在建立“基于 DSN 的连接对象”,在“数据源”列表框中可选择当前可用的用户型、系统型 DSN。
在连接设计器中选择“连接串”就说明在建立“基于字符串的连接对象”,为了快速、正确的设计连接字符串,我们可以打开文件型 DSN 参考,注意:“基于字符串的连接对象”不依赖文件型 DSN。
图 5. 连接设计器
连接属性的设定
从连接设计器的画面上我们可以发现很多连接属性,他们代表什么意思呢?在讲述这些问题之前,我想告诉大家:所有这些属性的设定都可以通过DBSETPROP()函数,所有这些属性值的获取都可以通过DBGETPROP()函数得到。下面,我们简要介绍几个属性:
异步执行。默认值为 .F.。同步执行是指:通过连接传送到后端的命令会被一句一句地执行,前一句没有执行完毕后一句就不会被执行;而同步执行是指:当通过连接发出命令后,不管后端是否完成并返回结果,客户端程序可以继续往下走。
超时间隔-空闲(分)。默认值是 0。设定一个非 0 值时,一个连接如果在这段时间内没被使用,Visual FoxPro 将使它休眠,再次使用该连接时 Visual FoxPro会激活它。注意使用该属性,因为它常常使您的应用程序产生不明不白的错误,如\error: unable to retrieve specific error information. Driver is probably out of resources.\实际上现在 ODBC 提供了 POOL 的功能,这些问题可由 ODBC 自行解决,我们不用操心。
超时间隔-连接(秒)。默认值是 0。设定一个非 0 值时,指在这个时间段内Visual FoxPro将试图连接到服务器,如果不成功,Visual FoxPro将产生一个错误。
显示 ODBC 登录提示。在应用程序中,我们总不希望有系统弹出登录窗口,这样于形象不利、于程序的安全性不利,特别是设计 COM 对象时,由于无人看护,更不希望登录窗口的弹出,Visual FoxPro 为我们提供了三种选择,笔者经常使用“从不显示”。
显示错误信息。如果设定为 .t.,任何 ODBC 错误将以 Messagebox 窗口显示出来;反之,ODBC 错误有用户自行处理。显然我们基本上使用默认设置 .f.。
提示:在Visual FoxPro中很多关于数据处理方面的设定,(如:set delete)都是限于数据工作期的,而连接被激活后可以在多个数据工作期内公用。
远程视图
弱水三千,取一瓢饮——条件视图
在前文我们已经建立了两个远程视图,您也许发现这种整个把远程表读取过来的做法执行效果很好,其实不然。我有两大理由:
设想如果后端表很大(Customers 只有 91 条记录),有几万、几十万条记录,结果会这样?
设想如果网络用户很多,网路很繁忙,结果会怎样?
Client\\Server设计的一个重要的课题就是使网络流量最小化,所以在设计远程视图时就要考虑这一解决问题。我们的想法是使远程视图仅下载有必要的信息,这就叫:弱水三千,取一瓢饮。例如:
CREATE SQL VIEW VCustomers
REMOTE CONNECTION Northwind SHARE ; AS SELECT * FROM Customers
WHERE customerid LIKE 'ALFKI'
我们在 WHERE子句中加入条件“customerid LIKE 'ALFKI'”,现在我们发现只有一条记录
从远端被下载。您也与已经发现这中间的不足:这个远程视图太死板了——只能为 Customerid 类似于'ALFKI'的记录提供服务,是不是可以把 'ALFKI'作为一个参数,供使这个远程视图更灵活,服务面更广。
可以,Visual FoxPro支持这样的远程视图:
CREATE SQL VIEW VCustomers ;
REMOTE CONNECTION Northwind SHARE ; AS SELECT * FROM Customers ; WHERE customerid LIKE cCustomerID
如果您打开远程视图或对已打开的远程视图执行REQUERY()、REFRESH()函数,Visual FoxPro会查找是由存在名为cCustomerID的变量。如果存在,Visual FoxPro 自动把变量值填入远程视图的SQL语句中;如果变量不存在,Visual FoxPro将弹出如下窗口询问变量值。
在实际开发应用程序中,我们会经常实现设定远程视图参数,在需要其它信息时,改变参数值,再用REQUERY()函数刷新客户端的数据;远程视图参数不仅可以填入普通数据,还可以使用统配符,如下:
cCustomerID='ALFKI' USE Vcustomers BROWSE
cCustomerID='%B%' REQUERY('Vcustomers') BROWSE
这里有以下问题点大家必须注意:
远程视图参数只能在WHERE字句中设定,其他的连接子句、分组字句、排序子句都不支持这一特性。比如不可以出现 Order by ?cOrder。(有传言说 Visual FoxPro 7 支持参数加入排序子句,但笔者还未及尝试)。
一个远程视图可以设定多个参数。实例见下面的代码。
远程视图的SELECT-SQL语句必须是以后端数据库的语法为标准。例如,SQL Server以单引号确认字符串,而Visual FoxPro可使用双引号、方括号、单引号确认字符串,那么如下语句是错误:
CREATE SQL VIEW VCustomers REMOTE CONNECTION Northwind SHARE ;
AS SELECT * FROM Customers WHERE customerid LIKE \
又如,Visual FoxPro中删除字符型字段的空格可使用ALLTR()函数,但远程视图的SELECT-SQL中不能用任何Visual FoxPro的函数,所以要实现上述功能,应如下使用SQL Server的函数:
CREATE SQL VIEW VCustomers REMOTE CONNECTION Northwind SHARE ;
AS SELECT customerid,LTRIM(RTRIM(CompanyName)) FROM Customers WHERE customerid LIKE 'ALFKI'
第三点中我们强调了远程视图构建时必须使用后端数据库认识的Select-SQL。当远程视图被打开,成为Visual FoxPro的光标时,就可以对它使用Visual FoxPro的命令与函数。
CREATE SQL VIEW VEmployees;
REMOTE CONNECTION Northwind SHARE AS SELECT EmployeeID,Birthdate FROM Employees
WHERE BirthDate >?dBirthdate1 AND BirthDate
BROWSE
*本例对日期型字段Birthdate使用了两个参数,并请注意日期型参数的传递。 CREATE SQL VIEW VEmployees1;
REMOTE CONNECTION Northwind SHARE;
AS SELECT EmployeeID, BirthDate, Country FROM Employees; WHERE birthDate > ?dBirthdate AND Country = ?cCountry dBirthdate='19601231' cCountry='uk'
USE VEmployees1 BROWSE
*本例对两个字段设定了参数
只下载有用的字段
上文中我们使用 SELECT * 从远端一古脑儿把所有列都下载到客户机,这样做不好。原因如下:
对远程数据操作时并不是所有的列(字段)都会被我们用到,特别是备注字段、大二进制字段。
在视图阶段就可以通过计算有关列得到更有用的信息,见下面的代码。
明确指定列信息有助于Visual FoxPro对远程数据表的版本控制。如果使用 SELECT *,那么只有在远程数据表增加字段时Visual FoxPro才知道,其他如减少、变更列的结构Visual FoxPro都不会知道。
例如:远程表中有FirstName,LastName列,我们在制作报表时只需要全名就可以了,那么我们解可以将它们相加成为一个新的列。
CREATE SQL VIEW VEmployees1 ;
REMOTE CONNECTION Northwind SHARE ;
AS SELECT EmployeeID, FirstName +' '+ LastName AS Name, Title ; FROM Employees
多表连接形成的远程视图
远程视图不仅支持远程一个表的操作,它还支持多表连接,如下:
CREATE SQL VIEW VEmployeeTerritories ; REMOTE CONNECTION Northwind SHARE ;
AS SELECT Territories.TerritoryID, Territories.TerritoryDescription, Employees.EmployeeID,; Employees.LastName, Employees.FirstName; FROM Territories INNER JOIN; EmployeeTerritories ON ;
Territories.TerritoryID = EmployeeTerritories.TerritoryID INNER JOIN; Employees ON EmployeeTerritories.EmployeeID = Employees.EmployeeID
远程视图还支持自连接,如下:
CREATE SQL VIEW VEmployeeReportTO ;
REMOTE CONNECTION Northwind SHARE ; AS SELECT Employees.EmployeeID as 领导工号,;
Employees.FirstName+' '+ Employees.LastName as 领导,;
Employees_a.FirstName +' '+ Employees_a.LastName as 下属,; Employees_a.ReportsTo as 上级领导工号; FROM Employees Employees INNER JOIN; Employees Employees_a ON ;
Employees.EmployeeID = Employees_a.ReportsTo; ORDER BY Employees_a.ReportsTo
从上面的语句大家可以发现:Visual FoxPro中的 SQL 与SQL Server 中的 SQL 十分相似,这方便了学习,但千万别陷入了“温柔陷阱”!
六、用vfp与sql server构建Client/Server应用程序(远程视图)(3)
-
可更新视图(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更新”选项。很多人在视图设计器中“千辛万苦”的设定了好多信息,但客户端数据变动就是不能发送到后端,原因就是他们忘记了这最最基本的选项。记住:要视图可更新,此项必须设定。
图 7. 视图设计器
我们可以通过DBSETPROP()函数设定以上三个选项:
DBSETPROP(\*设定键值栏
DBSETPROP(\*设定可更新字段,有很多字段要写,这里省略 DBSETPROP(\iew\
*打开“发送 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 子句包括——更新冲突的检测方式
“更新冲突”是指当某一位使用者在修改某一笔数据时,同一笔数据记录的内容已被其他使用者修改。下图就是一个更新冲突的示意图,红色代表数据源,黑色代表进程一,蓝色代表进程二。
图 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(\DBSETPROP(\DBSETPROP(\DBSETPROP(\iew\DBSETPROP(\iew\*设定更新冲突解决方案为“关键字和已更新字段” DBSETPROP(\iew\
*进程一
USE VCustomers BROWSE
*将指针停留在第一条记录上,即:CustomerID='ALFKI' REPLACE Phone with '123456'
*离开Visual FoxPro,千万别移动记录指针
进程二
使用 SQL Server 的 Enterprise Manager 打开 Customers表,把指针停在第一条记录上,修改Phone的值为'00000',移动指针到下一条记录。
回到Visual FoxPro,移动指针,您会看到图9:
图 9.更新冲突
按“还原”按钮。试验结束。
仔细想想,您就会明白什么是更新冲突了。
上例中,如果我们设定“关键字”方式检测更新冲突:
DBSETPROP(\iew\
其他均按原先步骤进行,您会发现没有更新冲突产生。因为Visual FoxPro仅检测关键字是否变化,这里进程一、二都没有修改关键字,当然不会有更新冲突。
上例中,如果我们设定“关键字和可更新字段”方式检测更新冲突:
DBSETPROP(\iew\
其他均按原先步骤进行,这时会有更新冲突产生。因为Visual FoxPro不仅检测关键字是否变化,还要检测所有的可更新字段字段(本例是所有字段)是否发生变化,这里进程二先进程一修改了可更新字段 Phone,进程一当然会有更新冲突发生。
如果使用 SQL Server 的 Profiler 程序您能更好的了解以上内容:
图 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 更新冲突发生了。
如果您还没有理解更新冲突——这很正常,请往下看。
七、用vfp与sql server构建Client/Server应用程序(远程视图)(4)
-
缓冲(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(\*设定VCustomers的缓冲模式为“开放式行缓冲”。由于这时Visual FoxPro的默认设置,这一句可省略。 USE VOrders
CURSORSETPROP(\ *设定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(\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(\REPLACE Phone with '9999' ?VCustomer.phone
*9999
?TABLEREVERT(.F.,'VCustomers') *返回1
?VCustomer.phone *123456
USE VCustomers
CURSORSETPROP(\?TABLEREVERT(.F.,'VCustomers') *返回0
在开放式表缓冲下使用TABLEREVERT()函数:
语法:TABLEUPDATE(lAllRows[,nWorkAear|cTableAlias])
返回值:放弃更新的记录数目
必选参数——lAllRows。默认值为.F.,表示对当前记录放弃更新;本参数设定为 .T.,放弃更新所有被修改过的记录。
可选参数——nWorkAear|cTableAlias。表示实行TABLEREVERT()的工作区,缺省表示对当前工作区有效。
USE VCustomers
CURSORSETPROP(\REPLACE Phone with '9999' next 4
*将第1、2、3、4、条记录的Phone改为9999 BROWSE
go 2
?TABLEREVERT(.F.,'VCustomers') *1
?TABLEREVERT(.T.,'VCustomers') *3
八、用vfp与sql server构建Client/Server应用程序(远程视图)(5)
-
刷新缓冲(refreshing buffers)
记得在“以缓冲理解更新冲突”一节中我们提到过什么情况下Visual FoxPro会刷新缓冲区。其中“远程视图光标被打开”是很好的理解,这里不再累述。
以 REQUERY()函数刷新远程视图光标
REQUERY()函数的作用就是重新执行远程视图的SELECT-SQL描述,也就是重新打开远程视图光标,所以对系统造成较大的负担。对于这个函数,我有几点建议:
执行成功,返回:1;反之,函数返回:0。
执行成功,记录指针将停在首记录,因为本函数就如同重新打开远程视图。
由于是重新下载数据,本函数对缓冲进行全部刷新。
行缓冲下,如果修改了一行记录,但未发送更新时,使用本函数,Visual FoxPro将先发送更
新,如果没有更新冲突,才重新下载数据光标;如果发生更新冲突,本函数不被执行,返回 0。
表缓冲下,如果修改了任何记录,但没有发送更新,本函数将不被执行,出现如图11的提示,返回0。
图 11。 表缓冲下更新没有被确认,不能使用REQUERY()函数
发送更新成功,只刷新被更新记录的相关字段的缓冲
也许这个标题很难理解,那么我们就分析一下:
无论是行缓冲还是表缓冲模式,发送更新成功,Visual FoxPro将已更新字段的新值填入缓冲。也就是说,没有被更新字段的缓冲区不被刷新。
行缓冲下发送更新成功,只刷新当前记录地被更新字段的缓冲。
表缓冲下发送更新成功,只刷新被更新的若干记录的各自被更新字段的缓冲。
我认为,Visual FoxPro所谓刷新缓冲,只不过是Visual FoxPro自己的行为而与远程数据源无关,也就是由Visual FoxPro生成UPDATE-SQL语句时,Visual FoxPro自动将新值填入缓冲,Visual FoxPro再发送此SQL描述。所以被刷新的就是被更新的字段的缓冲,并且无论更新是否成功!
远程视图的其他属性
远程视图的高级属性可以通过可视工具设定,下面我们就讲解一下:
FetchASNeed 和 FetchSize 即:取得远端所需的数据和每次提取的记录数
这两个属性是成对工作的,默认设定是:
DBSETPROP(\DBSETPROP(\
表示远程视图打开时,每批下载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(\
*表示一次下载所有记录,完成此任务才将控制权交回或继续执行程序。
如果你这样设置:
DBSETPROP('VOrders',\DBSETPROP(\
USE VOrders
*下在100条记录,并将控制权交还用户,由于FetchAsNeed=.T.,不像刚才——Visual FoxPro自动控制继续下载数据,连接被霸占
go 100
*第100条记录已经下载,所以VOrders不下载任何数据,连接被霸占
go 105
*第105条记录还没有下载到客户端,所以VOrder下载5条数据以满足用户的需要,连接被霸占
go bottom
*下载所有记录,连接被释放
MaxRecords 即:要提取的最大记录数
这个属性是指远程视图光标最多可以下载的记录数,并且它的优先级比上两个属性高。也就是说无论上两个属性怎样设置,客户端就只能拥有小于等于MaxRecords条从远端下载的记录,并且达到这个数量后,“霸占”连接就被释放、可以供其它视图使用。这个属性可以这样设定:
DBSETPROP(\