一 : 日本81道变态逻辑题,答出越多越变态31-40
31、井
有一天,因为觉得妹妹的哭声非常的吵耳所以把她杀了
然后把尸体丢到屋外的井里
第二天再去看的时候,尸体却消失了
5年后,因为一点小争执所以把朋友杀了
然后把尸体丢到屋外的井里
第二天再去看的时候,尸体却消失了
10年后,因为被一个在酒醉后不小心令她怀孕的女人缠上所以把她杀了
然后把尸体丢到屋外的井里
第二天再去看的时候,尸体却消失了
15年后,因为上司的责骂所以把他杀了
然后把尸体丢到屋外的井里
第二天再去看的时候,尸体却消失了
20年后,因为厌倦照顾那个行动不便的母亲所以把她杀了
然后把尸体丢到屋外的井里
第二天再去看的时候,尸体却没有消失
第三天、第四天,之后每一天都去看...
尸体都没有消失
瑜小七楼号: 75# 时间:2011-11-18 17:08
31、因为母亲疼爱儿子
所以把所有的尸体都处理掉了
可是... 却处理不了自己的尸体
瑜小七楼号: 76# 时间:2011-11-18 17:08
32、一家三口
有一家三口刚搬进他们的新家
虽然有些老旧,不过能够找到如此便宜的平房十分难得啊!
一直以来都住在空间狭小的公寓
如今甚至还有个小庭院,能搬到这里真的是太好了!
一家人就这样在新家开始生活
大约一个月后
有一天夜晚,妻子又开始说出
「我觉得这房子有问题!」这样的话
这不是第一次了!
「一定又是你想太多了吧?
虽然这间房子很便宜,但并不表示....」
丈夫还未说完,就发现妻子的脸色不太对劲
妻子脸色铁青望向窗外的庭院
只见男孩很开心的在庭院里玩着
丈夫也看了看窗外
「你到底怎麽啦!根本没什麽特别的吧?」丈夫说。
瑜小七楼号: 77# 时间:2011-11-18 17:08
32、文章开始只说是一家三口,但没有说明家庭成员是哪些人
事实上夫妇两人并没有孩子,妻子能看到的男孩丈夫却看不到
这表示....
二 : 81道变态逻辑题-转
太变态了,很刺激。[www.61k.com]三 : CA专题81
-- ★CA专题★
CursorAdapter 起步 1
CursorAdapter 类是 VFP 8/9 中最重要的新功能之一,因为它提供了一种简单易用、接口统一的访问远程数据源方式。在这个月的文章里,Dung Hennig 将向你展示 CursorAdapter 及它的工作方式。下个月,我们将再学习一些高级的用法。
正文:
越来越多的 VFP 程序员开始把他们的数据储存到象 SQL Server 或者 Oracle 这样的 VFP 表以外的数据仓库中去了。有许多原因导致了这种情况,包括 VFP 表的脆弱性(不管是想象中的还是确实如此)、安全性、数据库的容量、以及通用性的标准等等。MicroSoft 已经在每一个版本中都使得访问非VFP数据更加的简单,为了鼓励这种风气,它甚至在 VFP 7 光盘中自带了 MSDE(Microsoft Data Engine,SQL Server 的一个免费、简装版)。
不过,访问一个后台数据库从来就比使用 VFP 表要麻烦一些,而你可以使用的机制则多得吓人:
×× 远程视图,它基于 ODBC 连接;
×× SQL Passthrough (SPT) 函数,例如 SQLCONNECT()、SQLEXEC() 和 SQLDISCONNECT(),它们也基于 ODBC 连接;
×× ActiveX Data Objects ,简称 ADO,它提供了一个对各种数据库引擎的 OLE Provider 的一个面对对象访问方式;
×× XML,它是一个轻量级的、平台无关的数据传输机制。
如果你曾经用这些机制上工作过,有一件事情你可能已经注意到了:它们中的每一种都各不相同。这样的话,你就必须一个个的学过来,还要把一个已有的应用程序从一种机制转换到另一种机制,这可不是一件简单的工作。
由于有了一个新的基础类 CursorAdapter,在 VFP 8 中访问远程数据要比过去的版本中简单的多。以我之见,CursorAdapter 是 VFP 8 最重要的新功能之一。我认为它最棒的地方是: ×× 使用 ODBC、ADO、XML 变得非常容易,即时你不熟悉这些技术。
×× 不管你选择了哪种远程数据源机制,它都提供一种统一的访问接口。
×× 从一种机制转换到另一种机制变得非常的轻松。
这里是上面的最后一个观点的例子。假设你有一个使用 CursorAdapter 通过 ODBC 来访问 SQL Server 数据的应用程序,由于某些原因你想要改成使用 ADO 了。对于这种情况,你只需要改动 CursorAdapter 的 DataSourceType 属性、并改变对后台数据库的连接,就全部完成了。你的应用程序中的其它部分不需要知道也不需要关心这些事情;它们看到的只是同一个 Cursor 而不管使用了哪一种机制。
属性
我们先从查看 CursorAdapter 的属性、事件和方法开始来学习它。这里不会讨论所有的属性,只谈一下最重要的那些。
DataSourceType
**************
这个属性是最重要的:它决定了这个类的表现,以及要在其它一些属性中要怎么设置。可用的选项有“Native”——意思是使用 VFP 表——或者是 "ODBC"、"ADO" 或 "XML" ,表示你要选用的访问远程数据源的方式。
DataSource
***********
这是访问数据的手段。当 DataSourceType 被设置成“Native”或者“XML”的时候,VFP会忽略这个属性的设置。对于ODBC,请把这个属性设置为一个有效的 ODBC 连接句柄(这意味着你要自己管理连接了)。在ADO的情况下,DataSource 必须是一个 ADO RecordSet,而且它的 ActiveConnection 对象必须被设置为一个打开的 ADO Connection 对象(你又要自己管理这些了)。
UseDEDataSource
****************
如果这个属性被设置成了 .T.(默认是 .F.),你可以不管它的 DataSourceType 和 DataSource 属性,因为 CursorAdapter 将使用 DataEnvironment 的属性来代替( VFP 8 给 DataEnvironment 也增加了 DataSourceType 和 DataSource 属性)。举例来说,当你想让在一个数据环境中的所有 CursorAdapters 斗使用同一个 ODBC 连接的时候,就可以把它设置为 .T.。
SelectCmd
**********
除了 XML 的情况以外,这是一个用来取得数据的 SQL Select 命令。在 XML 的情况下,它可以或者是一个能够被转换为一个 Cursor 的有效 XML 字符串(使用内部的 XMLTOCURSOR() 调用),或者是一个能够返回一个有效的 XML 字符串的表达式。 CursorSchema
************
这个属性里保存的是 Cursor 的数据结构,格式就像你在用 CREATE CURSOR 命令的时候用的那样。这是一个例子:CUST_ID C(6), COMPANY C(30), CONTACT C(30), CITY C(25)。尽管不设置这个属性而让 CursorAdapter 在自己建立 Cursor 去决定这个结构也是可以的,不过如果你自己输入的话,它会工作的更好。如果 CursorSchema 是空的或者不正确,那么当你打开一个表单的数据环境的时候,就会要么弹出一个错误,要么就不能通过从 CursorAdapter 中拖放字段到表单上来建立控件。幸运的是,VFP 自带的 CursorAdapter 生成器可以为你填充这个属性。
AllowDelete、AllowInsert、AllowUpdate 和 SendUpdates
****************************************************
这些属性的默认值是 .T.,它们决定了是否可以删除、插入和更新和改动是否要被发送到数据源。
KeyFieldList、 Tables、 UpdatableFieldList、和 UpdateNameList
*************************************************************
这些属性的用途跟 CURSORSETPROP() 中用到的那些参数的用途是一样的,如果你想让 VFP 自动将对 Cursor 的改动提交到数据源,这些属性就是必须的。
×× KeyFieldList 是一个用逗号分隔的字段列表(不带别名),这些字段组成 Cursor 的主关键字。Tables 是一个用逗号分隔的表名列表。
×× UpdatableFieldList 是一个用逗号分隔的可以被更新的字段名列表(不带别名)。
×× UpdateNameList 是一个用逗号分隔的列表,它用来让 Cursor 中的字段名与在表中的字段名相匹配。UpdateNameList 的格式就像 这样:CURSORFIELDNAME1 TABLE.FIELDNAME1、CURSORFIELDNAME2 TABLE.FIELDNAME2 等等。注意:如果 UpdatableFieldList 不包含表的主键字段的名称(比如说你不想让用户可以更新这个字段),
在 UpdateNameList 还是必须要有这个字段,否则就不能更新。
Cmd、*CmdDataSource 和 *CmdDataSourceType
*****************************************
如果你想指定让 VFP 怎样去删除、插入和更新数据源中的记录,你可以给这些属性设置相应的值——注意,* 的位置是 Delete、Insert 或者 Update。
CursorFill(UseCursorSchema, NoData, Options, Source)
****************************************************
这个方法建立 Cursor,并用来自数据源的数据填充这个 Cursor(你也可以给 NoData 参数传递一个 .T.以建立一个空的 Cursor),给第一个参数传递 .T. 来使用定义在 CursorSchema 中的游标数据结构,或者传递 .F. 来根据数据源中的结构建立一个相应的结构。MULTILOCKS 必须被设置成 ON,否则这个方法将执行失败。如果 CursorFill 由于某些原因执行失败,它不会发生一个错误而是返回 .F.,不过你还是可以用 AERROR() 来检查发生了什么错误(准备苦苦挖掘吧!通常你得到的错误信息都不足以告诉你究竟问题在哪里)。 CursorRefresh()
***************
这个方法类似于 Requery() 函数:它刷新 Cursor 的内容。
Before*() 和 After*()
*********************
CursorAdapter 的几乎每个方法和事件都有一套 before 和 After 开头的“hook”事件(hook这个词中文没有很好的对应,勉强把它翻译成“挂钩”还不如不翻),这样你就可以自定义 CursorAdapter 的行为特性了。例如,你可以在 AfterCursorFill 中为 Cursor 建立索引。在 Before 系列事件中你可以返回一个 .F. 来防止触发被 hook 的事件发生(类似于数据库事件)。
示例
*****
这里是一个示例来自附带的示例文件 (CursorAdapterExample.prg),它用于从 SQL Server 自带的 Northwind 数据库的 Customers 表中取得巴西客户的某几个字段数据。产生的 Cursor 是可更新的,所以如果你对 Cursor 中的数据做了某些改动,然后再次运行程序,你会看到刚才所作的改动已经被保存在后台数据库中了。
local lcConnString, ;
loCursor as CursorAdapter, ;
laErrors[1] lcConnString = \'driver=SQL Server;server=(local);\' + ;
\'database=Northwind;uid=sa;pwd=;trusted_connection=no\'
* 把这里的密码改成你自己的数据库中密码
loCursor = createobject(\'CursorAdapter\')
with loCursor
.Alias = \'Customers\'
.DataSourceType = \'ODBC\'
.DataSource = sqlstringconnect(lcConnString)
.SelectCmd = "select CUSTOMERID, " + ;
"COMPANYNAME, CONTACTNAME from CUSTOMERS " + ;
"where COUNTRY = \'Brazil\'"
.KeyFieldList = \'CUSTOMERID\'
.Tables = \'CUSTOMERS\'
.UpdatableFieldList = \'CUSTOMERID, COMPANYNAME, \' + ;
\'CONTACTNAME\'
.UpdateNameList = ;
\'CUSTOMERID CUSTOMERS.CUSTOMERID, \' + ;
\'COMPANYNAME CUSTOMERS.COMPANYNAME, \' + ;
\'CONTACTNAME CUSTOMERS.CONTACTNAME\'
if .CursorFill()
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif
.CursorFill()
endwith
数据环境和表单的增强
********************
为了支持新的 CursorAdapter 类,对表单和数据环境类也做了一些增强。
首先,象我前面提到过的那样,DataEnvironment 类现在有了 DataSource 和 DataSourceType 属性。不过它自己并不使用这些属性,而是给那些在这个数据环境中的那些 UseDEDataSource 被设置成了 .T. 的 CursorAdapter 使用的。其次,现在你可以使用类设计器来可视化的建立 DataEnvironment 的子类了(哇!)。
而对于表单,你可以通过设置新的 DEClass 和 DEClassLibrary 属性来指定使用一个 DataEnvironment 的子类了。不过这么做一定要趁早,因为在这么干了以后,原来的数据环境中所有已经做好的东西(Cursor、代码等等)都会丢失,还好系统会先警告你。表单的一个很酷的新功能是它的 BindControls 属性——把这个属性设置为.F.就可以让表单在 INIT 的时候不对控件进行数据绑定,而只有当 BindControls 被设置为 .T. 的时候才会这样。这个功能好在哪里呢?你曾经多少次诅咒过这样的情况:参数必须被传递给表单的INIT事件,而INIT事件却要等到所有的控件已经初始化并已经绑定到它们的数据源了以后才会被触发?要是你想向该表单传递一个参数来告诉表单打开哪个表或者其它会影响 ControlSources 的事情的时候该怎么办?这个新的属性让这些事情变得象打个瞌睡那么容易。
优点
*****
有大量的理由支持我们使用 CursorAdapters 来代替远程视图、SPT、ADO 或者 XML: ×× 每一种机制都有一种不同的接口。对于远程视图,你用 USE 命令来打开它们。对于SPT,你要使用 SQLCONNECT() 和 SQLEXEC() 来建立一个 Cursor。对于 ADO,你必须先建立 ADO 的 Connection 对象和 Recordset 对象(根据你取得数据方式的不同,也许还可能需要一个 Command 对象)的实例,并调用它们的 Open 方法。对于 XML,你首先必须从什么地方获得一个 XML 字符串(例如在 N 层应用中通过一个 COM 部件,或者在 SQL Server 中通过 SQLXML),然后使用 XMLTOCURSOR() 来把这个字符串转换成一个 VFP 的 Cursor。编写对后台数据源进行更新的代码也各不相同。所以,当你从一种机
制转换到另一种机制的时候,就必须要去学一种新的技术,还有你可能需要重写的大量已有的代码。
不管你使用哪种机制来取得数据,CursorAdapters 都使用同一个统一的接口。通过设置该对象的一些属性,然后调用它的 CursorFill 方法来取得数据,对这个 Cursor 操作时就像在操作一个普通的 VFP Cursor 一样,然后调用 TABLEUPDATE() (或者让VFP自动去处理它)来将更新提交到后台数据源。
×× 在实际开发的时候,你经常会需要从命令窗口中打开一个 Cursor 来浏览它的内容。对于远程视图,这么做是很简单的,但是对于 SPT、ADO 和 XML,就可能要花上很多力气了。
而打开由一个 CursorAdapter 的子类产生的 Cursor 几乎就像打开一个远程视图那么容易:你只需要建立这个子类的实例,然后调用它的 CursorFill 方法就行了。你甚至可以直接在它的 INIT 方法中进行 CursorFill,这样只要一步就可以完成操作了。
×× 对于远程视图来说,升迁一个已有的应用程序会相对容易一些:你只要把数据环境中本地表或视图的东西换成同名的远程视图就行了。但是对于SPT、ADO 或 XML,你可能就必须要全部重写整个数据访问方案了。
而用 CursorAdapters 来升迁一个应用程序就会象用 远程视图来升迁一样轻松——只要简单的把数据环境中的 Cursor 对象替换成 CursorAdapters 对象就行了。
×× 在表单和报表设计器中使用远程视图来工作是很容易的。你可以给数据环境添加一个远程视图,然后就能利用到数据环境提供的可视化设计的优势了:拖放字段来自动建立控件、通过在属性窗口中的组合框中选择来轻松的将控件绑定到一个字段等等。SPT、ADO、XML 就不支持可视化设计方式了。
CursorAdapters 与远程视图一样能够享受到在数据环境中可视化设计的那些优点。
×× 用视图设计器可以很容易的建立远程视图。尽管过去它有着许多限制,尤其是在处理有两个以上的表相互连接的视图的时候,可现在,VFP 8 已经修正这些问题中的大多数,并且添加了许多新功能,例如双向编辑:你可以在 SQL 窗口中修改代码,然后就能看到这些改动被反映到视图设计器的可视化部分中了。而对于 SPT、ADO 和 XML,要做的工作就多的多,因为每样东西你都必须自己写代码:建立和关闭连接、要执行的 SQL Select 语句等等。
VFP 8 包含了一个 CursorAdapters 生成器,用了它,可以只需要很少的工作就可以设置好那些对于取得和更新数据来说相当重要的属性。它甚至还包含了一个 SelectCmd 生成器,这个生成器的可视化程度就像是视图设计器一样,它让你可以通过使用一个“mover”控件来选择应该从远程表中取得那些字段。
×× 将远程视图和ADO 记录集中的更新提交到后台数据库是相当简单的。假定视图的属性已经被设置正确了,那么你只需要调用 TABLEUPDATE() 就可以了。在 ADO 的情况下,则调用 RecordSet.Update() 或者 UpdateBatch()。对于 SPT 和 XML,你就必须手工的做大量工作来把更新提交到后台。
象我们前面看到的那样,用 CursorAdapter 来提交更新只需要设置几个属性,然后就可以全部交给 VFP 去做其它所有的工作,或者你也可以很方便的通过指定怎么删除、插入和更新来获得更大的灵活性。
×× 由于远程视图和 SPT 建立的结果集是 VFP 的 Cursor,所以你可以在VFP中的任何地方使用它们:表格、报表、用 Scan 来遍历等等。而另一方面的 ADO 和 XML ,在使用之前就必须先把它们转换成 Cursor,这会给你的应用程序增加额外的复杂性和处理它们的时间。
CursorAdapter 的结果集是一个 VFP Cursor,所以它有着与远程视图和SPT同样的优势。而
CA专题81_ca1981
更棒的是,即使数据源是 ADO 和 XML 你也能得到一个 VFP 的 Cursor,因为 CursorAdapter 会自动为你处理好转换的事情并为你形成一个 Cursor。
×× 由于一个远程视图的 SQL Select 语句是预先定义好的,所以你无法动态去修改它。尽管对于那些典型的数据输入表单来说这已经足够了,但是对于查询和报表来说则不然。可能你必须要建立好几个从同一个表中取得数据的视图,每一个会选择不同的字段、使用不同的 WHERE 子句结构等等。对于 SPT、ADO 或 XML 来说,这不是一个问题。
CursorAdapters 不会受这个问题的折磨——你可以很轻松的通过改动 SelectCmd 属性来改变取得什么数据以及怎么取得数据。
×× 你不能从一个远程视图中调用存储过程,所以远程视图需要直接访问后台的表。对于你的应用程序的数据库管理员来说,这可能是一个问题——某些数据库管理员认为,基于安全的或者什么其它原因,所有的数据访问应该只通过存储过程来进行。而且,由于存储过程是在后台预编译的,所以它执行起来通常要比 SQL Select 语句快得多。在 SPT、ADO 和 XML 的情况下,你可以根据需要来调用存储过程。
通过简单的设置 SelectCmd 属性,CursorAdapters 也可以使用存储过程。
×× 远程视图保存在一个数据库容器中,所以还有一套你必须维护和安装到客户系统上的文件。此外,当你打开一个视图的时候,VFP 会试图去锁定在 DBC 中的视图的记录,虽然这只需要很短的时间。在一个忙碌的系统中,当几个用户试图同时打开一个表单的时候,这可能会造成冲突。尽管也有一些变通的处理办法(把DBC拷贝到本地工作站上、或者使用在 VFP 7 以上版本中的 SET REPROCESS SYSTEM 来减少锁定的时间),这总是一件你要操心的事情。还有一个问题是:如果你使用一个 SELECT * 的视图来从一个指定的表取得数据、而那个表的结构又在后台被改动过了,那么这个视图就会变得无效而且必须要重建才能解决。对于 SPT、ADO 和 XML 来说,由于它们与 DBC 无关,因此它们都没有这些问题。
因为它们都不在 DBC 里面,所以 CursorAdapters 就也没这些问题了。
×× 由于远程视图和 SPT 是通过 ODBC 来工作的,因此它们只能用于直接数据连接的“客户—服务器”模式。而ADO和XML有着在一个 N-层应用程序中的多个层之间传递数据的机制可供选择。
由于 CursorAdapters 可以建立来自 ADO 或者 XML 数据的 VFP Cursor,因此它对于在一个 N-层应用程序中被用于用户界面层来说是理想的。
总结
××
我认为 CursorAdapters 是 VFP 8 中最重要、最令人兴奋的增强之一,因为它提供了一个对于远程数据源的统一而又容易使用的接口,此外,象我们将要在以后的文章中讲述的那样,它还允许我们建立可重用的数据类。下个月我们将探讨一下访问本地数据或者使用 ODBC、ADO和XML来访问远程数据的细节。再下个月,我们将探讨一下建立可重用数据类、以及怎样在报表中使用 CursorAdapters。
【译者注】
附带的示例文件是一个PRG,实在太简单了,我就直接把内容贴在这里了。
local lcConnString, ;
loCursor as CursorAdapter, ;
laErrors[1]
lcConnString = \'driver=SQL Server;server=(local);database=Northwind;\' + ;
\'uid=sa;pwd=;trusted_connection=no\'
* change password to appropriate value for your database
loCursor = createobject(\'CursorAdapter\')
with loCursor
.Alias = \'Customers\'
.DataSourceType = \'ODBC\'
.DataSource = sqlstringconnect(lcConnString)
.SelectCmd = "select CUSTOMERID, COMPANYNAME, CONTACTNAME " + ;
"from CUSTOMERS where COUNTRY = \'Brazil\'"
.KeyFieldList = \'CUSTOMERID\'
.Tables = \'CUSTOMERS\'
.UpdatableFieldList = \'CUSTOMERID, COMPANYNAME, CONTACTNAME\'
.UpdateNameList = \'CUSTOMERID CUSTOMERS.CUSTOMERID, \' + ;
\'COMPANYNAME CUSTOMERS.COMPANYNAME,
CUSTOMERS.CONTACTNAME\'
if .CursorFill()
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill()
endwith
CursorAdapter 起步 2
CursorAdapter 起步 之二:用 CursorAdapter 来取得和更新数据
在 VFP8 中新增的 CursorAdapter 基类提供一个统一、易用的数据接口。Doug Hennig 在这个月的文章中演示了怎样使用 CursorAdapter 来访问本地数据和 ODBC、ADO和XML这样的远程数据——讨论了使用各种数据源相应的特殊要求和实现途径。
正文:
如我在上一篇文章中所提到的那样,在VFP8中一个最重要的、也是最精彩的新功能是新的 CursorAdapter 基类。在那篇文章中,我们研究了一下 CursorAdapter 的属性、事件和方法,并讨论了它相对于远程视图、SQL PassThrough(SPT)、ADO和XML的优势。
在开始使用 CursorAdapter 之前,你需要根据要访问的是本地数据还是通过ODBC、ADO或者XML的远程数据源的不同,注意这个类所相应的不同的特殊要求。这个月的文章就讲述了使用各种数据源的细节。
使用本地数据源
×××××××
尽管我们很清楚 CursorAdapter 是试图用来标准化和简化对非VFP数据的访问方式的,不过你还是可以把它当作是 Cursor 的代替品用它来访问VFP数据:只要把它的 DataSourceType 属性设置成 "Native"。为什么要这么做呢?因为你的应用程序将来可能会需要升迁——那时候你就可以把 DataSourceType 属性设置成其它几个选项之一(当然可能还需要修改其它几个属性,例如设置连接信息等等),就能轻松的切换到另一种数据库引擎,CONTACTNAME
例如SQL Server。
当 DataSourceType 属性的设置为 "Native" 的时候,VFP会忽略它的 DataSource属性。SelectCmd属性必须是一个 SQL Select 语句(而不是一个 USE 命令或表达式),这就意味着你用 CursorAdapter 不是直接操作本地表而是操作一个类似于本地视图那样的东西。你还必须确保VFP能够找到出现在那个 Select 语句中的任何表,因此,如果这些表不在当前路径中,那么你就需要设置一下路径或者打开这些表所属的数据库。此外,就跟用视图一样,如果你想让这个 Cursor 是可更新的,你还必须设置好那些与更新相关的属性(KeyFieldList、Tables、UpdatableFieldlist和 UpdateNameList)。
下面的例子(文章附件 NativeExample.prg)会用 VFP 示例数据库中的 Customer 表建立一个可更新的 Cursor:
local loCursor as CursorAdapter, laErrors[1]
Open database (_samples + \'data\\testdata\')
with loCursor
.Alias = \'customercursor\'
.DataSourceType = \'Native\'
.SelectCmd = "Select CUST_ID, COMPANY, CONTACT FROM CUSTOMER " + ;
"WHERE COUNTRY = \'Brazil\'"
.KeyFieldList = \'CUST_ID\'
.Tables = \'CUSTOMER\'
.UpdatableFieldList = \'CUST_ID, COMPANY, CONTACT\'
.UpdateNamelist = \'CUST_ID CUSTOMER.CUST_ID, \'+ ;
\'COMPANY CUSTOMER.COMPANY, CONTACT CUSTOMER.CONTACT\'
if .CursorFill()
browse
tableupdate(1)
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill()
endwith
close databases all
使用 ODBC
×××××
ODBC 是 DataSourceType 属性四种设置中最简单的一种。把 DataSource 设置为一个打开了的 ODBC 连接句柄、设置一下常用的属性、然后调用 CursorFill 来取得数据。如果你设好了 KeyFieldList、Tables、UpdatableFieldList和 UpdateNameList属性,VFP 会自动把你对数据的任何改动转换成相应的 UPDATE、INSERT、和 DELETE 语句来把改动提交到后台数据源。如果你想用的是一个存储过程,那么要相应的设置 *Cmd、*CmdDataSource和 *CmdDataSourceType属性(* 代表 “Delete”、“Insert”或“Update”)。
这里是附件 ODBCExample.prg 中的一个例子,它调用 Sql Server 自带的 NorthWind 数据库中的 CustOrderHist 存储过程来取得销售给某个客户的单位产品总数。
local lcConnString, loCursor as CursorAdapter, laErrors[1]
lcConnString = \'driver=SQL Server;server=(local);database=Northwind;uid=sa;pwd=;"+ ; "trusted_connection=no\'
** 把上面连接字符串中的密码改成你的SQL Server 登录的密码
loCursor = createobject(\'CursorAdapter\')
with loCursor
.Alias = \'Customerhistory\'
.DataSourceType = \'ODBC\'
.DataSource = SQLStringConnect(lcConnString)
.SelectCmd = "exec CustOrderhist \'ALFKI\'"
if .CursorFill()
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill()
endwith
使用 ADO
××××
与使用 ODBC 相比,使用 ADO要多一些需要操心的事情:
×× DataSource 必须被设置成一个 ADO RecordSet,而且这个 RecordSet 的 ActiveConnection 属性需要被设置成一个打开了的 ADO Connection 对象。
××如果你想要使用一个参数化查询(与下载全部数据相比,这可能是更常用的方式),你必须把一个 ADO Command 对象作为第四个参数传递给 CursorFill 方法,而且这个 Command 对象的 ActiveConnection 属性需要被设置成一个打开了的 ADO Connection 对象。VFP会为你照顾好填充 Command对象的参数化集合的事情(它通过分析 SelectCmd 来找出参数),不过参数所包含的值当然还是必须在有效取值范围内的。
××在数据环境中只有一个使用了 ADO 的 CursorAdapter 这样的情况是比较简单的:如果需要的话,你可以把 UseDEDataSource 属性设置成 .T.,然后根据你的需要把数据环境的 DataSource 和 DataSourceType 属性设置成 CursorAdapter。不过,如果数据环境中有多个 CursorAdapter 的话,这种办法就无效了。原因是 DataEnvironment.DataSource 所引用的 ADO RecordSet 只能包含一个 CursorAdapter 的数据;当你为第二个 CursorAdapter 调用 CursorFill 方法的时候,会出现“RecordSet is already open (RecordSet 记录集已经打开)”的错误。所以,如果你的数据环境中有超过一个的 CursorAdapter,你必须要把 UseDEDataSource 设置成 .F.,并自行管理每个 CursorAdapter 的 DataSource 和 DataSourceType 属性(或者你可以使用一个能够管理这种情况的 DataEnvironment 的子类)。
附件 ADOExample.prg 中的示例代码演示了怎样借助一个 ADO Command 对象来取得数据。这个示例还演示了使用 VFP8 中新的结构化错误处理的功能。对 ADO Connection 对象的 Open 方法的调用被封装在一个 TRY...CATCH...ENDTRY 语句中,以捕捉调用这个方法失败的时候将会出现的 COM 错误。
local loConn as ADODB.Connection, ;
loCommand as ADODB.Command, ;
loException as Exception, ;
loCursor as CursorAdapter, ;
lcCountry, ;
laErrors[1]
loConn = createobject(\'ADODB.Connection\')
with loConn
.ConnectionString = \'provider=SQLOLEDB.1;data source=(local);\' + ;
\'initial catalog=Northwind;uid=sa;pwd=dhennig;trusted_connection=no\'
&& 把上面连接字符串中的密码改成你的SQL Server 登录的密码
try
.Open()
catch to loException
messagebox(loException.Message)
cancel
endtry
endwith
loCommand = createobject(\'ADODB.Command\')
loCursor = createobject(\'CursorAdapter\')
with loCursor
.Alias = \'Customers\'
.DataSourceType = \'ADO\'
.DataSource = createobject(\'ADODB.RecordSet\')
.SelectCmd = \'select * from customers where country=?lcCountry\'
lcCountry = \'Brazil\'
.DataSource.ActiveConnection = loConn
loCommand.ActiveConnection = loConn
if .CursorFill(.F., .F., 0, loCommand)
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill(.F., .F., 0, loCommand)
endwith
使用 XML
××××
用 CursorAdapter 来操作 XML 需要一些特殊的设置。下面是这些问题:
×× DataSource 属性被忽略;
×× CursorSchema 属性必须被填充好——即使你给 CursorFill 传递的第一个参数是 .F. 也一样——否则将会出错。
×× SelectCmd 必须被设置成一个表达式,例如一个用户自定义函数(UDF)或者对象方法名,该表达式能够为 Cursor 返回 XML。
××对 Cursor 的改动会被转换成一个 DiffGram,它是“包含着被改动了的字段或者记录,在被改动之前、被改动之后的值”的XML,当需要更新的时候,它被放在 DiffGram 属性中。
××为了把数据更动回写到数据源中去,UpdateCmdDataSourceType属性必须被设置为“XML”,并且 UpdateCmd 必须被设置成一个能够处理提交更新任务的表达式(象前面一样,这个表达式也是象一个 UDF 或者对象的方法)。你可能会需要把“This.DiffGram”传递给那个 UDF,这样它就可以把更新提交给后台数据源。
CA专题81_ca1981
这个 Cursor 所使用的 XML源文件可能来自各种不同的地方。例如,你可以调用这样一个UDF:它能用 CursorToXML()来把一个VFP Cursor 转换成 XML,并返回结果: use CUSTOMERS
cursortoxml(\'customers\', \'lcXML\', 1, 8, 0, \'1\')
Return lcXML
UDF 可以调用一个 Web Service,这个 Web Service 则返回一个 XML 结果集。这里是一个例子,我建立了一个 Web Service 并注册在我自己的系统上,而智能感知则为我生成了下面的代码(具体的细节并不重要,它只是演示了一个 Web Service 的例子):
loWS = newobject("WSclient\', home() + \'ffc\\_webservices.vcx\')
loWS.cWSName = \'dataserver web service\'
loWS = loWS.SetupClient(\'http://localhost/' + ;
\'SQDataServer/dataserver.WSDL\', \'dataserver\', ;
\'dataserverSoapPort\')
lcXML = loWS.GetCustomers()
Return lcXML
它能够在一个 Web Server 上使用 SQLXML 3.0 去执行一个存储在一个临时文件中的 SQL Server 2000 查询(要了解关于 SQLXML 更多的信息,请访问 http://msdn.microsoft.com并查找 SQLXML)。下面的代码使用一个 MSXML2.XMLHTTP 对象通过 HTTP 从 Northwind数据库的 Customers表来取得所有的记录,稍后我们将做更进一步的解释。
local loXML as MSXML2.XMLHTTP
loXML = createobject(\'MSXML2.XMLHTTP\')
loXML.open(\'POST\', \'http://localhost/northwind/' + ;
\'template/getallcustomers.xml, .F.)
loXML.setRequestHeader(\'Content-type\', \'text/xml\')
loXML.send()
return loXML.responseText
处理更新的事情要更复杂一点。数据源必须或者能够接受并处理一个 DiffGram (比如 SQL Server 2000 的情况),或者你必须自己去弄清楚所有的改动、执行一系列的 SQL 语句(UPDATE、INSERT和 DELETE)去提交更新。
这里是个使用了带 XML 数据源的 CursorAdapter 的例子(XMLExample.prg)。要注意的是:SelectCMD和 UpdateCMD都是要调用 UDF 的。在 SelectCMD 的情况中,要返回数据的客户编号被传递给一个叫做 GetNEWustomers 的 UDF,这个我们稍后再提。在 UpdateCmd 的情况中,VFP把 DiffGram 属性传递给 SendNWXML,这个我们也稍后再提。 local loCustomers as CursorAdapter, ;
laErrors[1]
loCustomers = createobject(\'CursorAdapter\')
with loCustomers
.Alias = \'Customers\'
.CursorSchema = \'CUSTOMERID C(5), COMPANYNAME C(40), \' + ;
\'CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60), \' + ;
\'CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), \' + ;
\'PHONE C(24), FAX C(24)\'
.DataSourceType = \'XML\'
.KeyFieldList = \'CUSTOMERID\'
.SelectCmd = \'GetNWCustomers([ALFKI])\'
.Tables = \'CUSTOMERS\'
.UpdatableFieldList = \'CUSTOMERID, COMPANYNAME, CONTACTNAME, \' + ;
\'CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX\' .UpdateCmdDataSourceType = \'XML\'
.UpdateCmd = \'SendNWXML(This.DiffGram)\'
.UpdateNameList = \'CUSTOMERID CUSTOMERS.CUSTOMERID, \' + ;
\'COMPANYNAME CUSTOMERS.COMPANYNAME, \' + ;
\'CONTACTNAME CUSTOMERS.CONTACTNAME, \' + ;
\'CONTACTTITLE CUSTOMERS.CONTACTTITLE, \' + ;
\'ADDRESS CUSTOMERS.ADDRESS, \' + ;
\'CITY CUSTOMERS.CITY, \' + ;
\'REGION CUSTOMERS.REGION, \' + ;
\'POSTALCODE CUSTOMERS.POSTALCODE, \' + ;
\'COUNTRY CUSTOMERS.COUNTRY, \' + ;
\'PHONE CUSTOMERS.PHONE, \' + ;
\'FAX CUSTOMERS.FAX\'
if .CursorFill(.T.)
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill(.T.)
endwith
这里是 GetNWCustomers 的代码。它使用了一个 MSXML2.XMLHTTP 对象来访问一个位于一个Web Server 上的名叫 CustomersByID.xml 的 SQL Server 2000 XML 模板,并返回结果。要获取数据的 Customer ID 被作为一个参数传递给这段代码:
lparameters tcCustID
local loXML as MSXML2.XMLHTTP
loXML = createobject(\'MSXML2.XMLHTTP\')
loXML.open(\'POST\', "http://localhost/northwind/template/customersbyid.xml?";; + ;
"customerid=" + tcCustID, .F.)
loXML.setRequestHeader(\'Content-type\', \'text/xml\')
loXML.send()
return loXML.responseText
这段代码里引用的名为 CustomersByID.XML 的 XML 模板的内容如下:
SELECT *
FROM Customers
WHERE CustomerID = @customerid
FOR XML AUTO
把这个文件放在用于 Northwind 数据库的一个虚拟目录中(参见补充文档《设置 SQL Server 2000 XML 访问》以了解更多关于为 SQL Server 2000 设置 IIS 的内容、以及这篇文章所需要的特殊细节。)
SendNWXML 的内容看起来与 GetNWCustomers类似,除了它接收的参数是一个 DiffGram,然后它把这个 DiffGram 加载到一个 MSXML2.DOMDocumnet 对象中,并把这个对象传递给 Web Server,该 Web Server 会通过 SQLXML把这个对象传递给 SQL Server 2000 去处理。
lparameters tcDiffGram
local loDOM as MSXML2.DOMDocument, ;
loXML as MSXML2.XMLHTTP
loDOM = createobject(\'MSXML2.DOMDocument\')
loDOM.async = .F.
loDOM.loadXML(tcDiffGram)
loXML = createobject(\'MSXML2.XMLHTTP\')
loXML.open(\'POST\', \'http://localhost/northwind/', .F.)
loXML.setRequestHeader(\'Content-type\', \'text/xml\')
loXML.send(loDOM)
运行 XMLExample.prg 来看看它是怎么工作的。你将会在 Browse 窗口中看到一台记录(客户 ALFKI)。试着改动几个字段的值,然后关闭这个窗口,再运行 PRG 一遍。你会看到你的改动已经被写入到后台数据源中了。
总结
××
尽管 CursorAdapter 基类提供了一种对远程数据源的统一的结构,而不管你使用的是 ODBC、ADO还是XML——但是,根据你选择的数据访问机制的不同,对 CursorAdapter 的设置也有一些区别。这些区别取决于数据访问机制的本身。
下个月,我将通过建立一些可重用的数据类、并讨论怎样在报表中使用 CursorAdapter 来结束这个系列的专题。
补充文档:
《设置 SQL Server 2000 XML 访问》
为了能够在一个浏览器或者其它 HTTP 客户端用一个 URL来访问 SQL Server 2000,你需要做一些工作。首先,你需要从 MSDN 网站(http://msdn.microsoft.com——查询一下“SQLXML”,然后选择下载)去下载和安装 SQLXML 3.0。
接着,你需要设置一个 IIS 虚拟目录。步骤如下:从开始菜单|程序|SQLXML 3.0文件夹中单击“Configure IIS Support(设置 IIS 支持)”。展开你的服务器节点,选择要使用的 Web 站点,然后单击鼠标右键,选择“新建|虚拟目录”,在出现的对话框的“常规”页中输入虚拟目录的名称和它的物理路径。在这里,我们使用“Northwind”作为虚拟目录名、“NorthwindTemplates”作为物理路径。使用 Windows 资源管理器在你的系统上的什么地方建立这个物理目录,然后给它建一个名为“Template”的子目录(稍后我们将会用到这个子目录)。把附件中的两个模板文件 GetAllCustomers.xml 和 CustomersByID.xml 拷贝到这个子目录中。
在“安全”页中,输入访问 SQL Server 的相应的信息,例如用户名和密码或者你想采用的特定的验证机制。在“数据源”页上,选择 SQL Server,如果需要的话,还要选择要使用的数据库。在这里我们选择 Northwind 数据库。在“设置”页上选择希望的设置,至少要选上“允许模板查询”和“允许 Post”。
在“虚拟名称”页中,从类型组合框中选择“模板”,并输入一个虚拟名称(在这里我们使用“template”)和物理路径(它应该是虚拟目录的一个子目录,在这里就是 "Template"子目录),这是使用模板的需要。好,单击“确定”。
现在我们测试一下是否每样东西都设置正确了,我们将通过使用你拷贝到 Template 子目录中去得 GetAllCustomers.xml来访问 SQL Server。它的内容如下:
SELECT *
FROM Customers
FOR XML AUTO
为了测试的目的,打开你的浏览器,并输入这个URL:http://localhost/northwind/template/getallcustomers.xml,你就会在浏览器中看到XML形式的 Northwind Customers 表的内容了。
--
CursorAdapter 起步 3
CursorAdapter 起步 之 三:可重用数据类
VFP 的程序员们想要一个可重用的数据类已经很久了。尽管在过去的版本中也有许多解决这个问题的办法,不过总是有点美中不足。现在在 VFP 8里,我们有了真正的可重用数据类。这个月,Doug 为我们演示了怎样通过建立 CursorAdapter 和 DataEnvironment 的子类来建立可重用的数据类、以及怎样在表单和报表中使用它们。
正文
××
在过去的两期杂志中,我们讨论了在 VFP 8 中新增的 CursorAdapter 基础类。我个人的观点是,这是 VFP 8 中最重要的改动之一,因为它向我们提供了一个对象SQL Server这样的非VFP数据源的简单易用、统一的接口。此外,如你本月所能见到的那样,它们还形成了可重用数据类的基础。
在讲述可重用数据类之前,让我们先来看一下我建立的一些 CursorAdapter 和 DataEnvironment 的子类,我给它们增加了一些额外的功能,它们将成为我们的可重用数据类的起点。
SFCursorAdapter
***************
SFCursorAdapter (在附件 SFDataClasses.vcx 中) 是 CursorAdapter 的一个子类,它拥有一些额外增加的功能,如下:
※ 它可以自动处理参数化查询:你可以静态(一个常量)也可以动态(一个表达式,例如“=Thisform.txtName.value”,当 Cursor 被打开或者刷新的时候,这个表达式会被运算)的定义一个参数值。
※ 它可以在 Cursor 被打开以后自动在该 Cursor 上建立索引。
※ 对于 ADO,它还会执行一些特殊的工作,例如把 DataSource 属性设置为一个 ADO RecordSet,把这个 RecordSet 的 ActiveConnection 属性设置为一个 ADO Connection 对象,当用到一个参数化查询的时候,它还会建立一个 ADO Command 对象并把这个对象传递给 CursorFill 方法。
※ 它提供了简单的错误处理(cErrorMessage 属性里会有错误的信息)。
※ 它还有 CursorAdapter 中缺少的 Update 和 Release 方法。
这个类的 INIT 方法建立两个集合(使用新的 Collection 基础类,它是维护某些东西的集合用的),一个是为 SelectCmd 属性可能会用到的参数而准备的,另一个是用于在 Cursor 被打开以后应该自动建立的标记。它还会 SET MULTILOCK ON,因为这是 CursorAdapter Cursor 的需求。
This.oParameters = CreateObject(\'Collection\')
This.oTags = CreateObject(\'Collection\')
Set multilocks on
AddParameter 方法象 parameters 集合添加一个参数。给这个方法传递参数的名称(这个名称应该与该参数出现在 SelectCmd 属性中的那个名称相一致),根据需要也可以付上参数的值(如果你现在不给它传递参数的值,也可以以后再调用 Getparameter 方法来传递)。这段代码演示了一对 VFP 8 中的新功能:新的 empty 基础类,它没有任何属性、事件或者方法,因此是建立一个轻量级的对象的理想选择;还有 AddProperty() 函数,它的作用跟 AddProperty 方法类似,区别是它用于那些没有这个方法的对象。
lparameters tcName, tuvalue
local loParameter
loParameter = createobject(\'Empty\')
addproperty(loParmeter, \'Name\', tcName)
addproperty(loParmeter, \'value\', tuvalue)
This.oParameters.Add(loParameter, tcName)
使用 GetParmeter 方法来返回一个特殊的 parameter 对象——通常是用在需要设置用于参数的值的时候。
lparameters tcName
local loParameter
loParameter = This.oParameters.Item(tcName)
return loParameter
SetConnection 方法用于将 DataSource 属性设置为希望的连接。如果 DataSourceType 是 “ODBC”,就给这个方法传递一个连接句柄;如果是“ADO”,DataSource 必须是一个ADO Recordset 对象,而且该对象的 ActiveConnection 属性必须要设置为一个活动 ADO Connection 对象,所以,我们需要向 SetConnection 方法传递这个 ADO Connection 对象,
CA专题81_ca1981
SetConnection 会建立一个 RecordSet,并且把这个 RecordSet 的 ActiveConnection 设置为被传递的 ADO Connection 对象。
lparameters tuConnection
with this
do case
case .DataSourceType = \'ODBC\'
.DataSource = tuConnection
case .DataSourceType = \'ADO\'
.DataSource = Createobject(\'ADODB.RecordSet\')
.DataSource.ActiveConnection = tuConnection
endcase
endwith
为了建立 Cursor,我们调用 GetData 方法而不是 CursorFill 方法,因为 GetData 能够自动处理参数和错误。如果你想要建立一个不带数据的 Cursor,那么就给 GetData 方法传递一个 .T.。这个方法建立的第一样东西,是与定义在 parameters 集合中的参数们同名的私有变量(在这里调用了 GetParametervalue 方法,该方法会返回参数对象的值,如果该对象的值是一个以“=”开头的表达式,那么返回的将是运算该表达式之后所获得的值。)下一步,如果我们是在使用 ADO 并且已经有了一些参数,这段代码会建立一个 ADO Command 对象,并把该对象的 ActiveConnection 属性设置为 Connection 对象,然后把这个 Connection 对象传递给 CursorFill 方法——这是 CursorAdapter 处理 ADO 参数化查询的需要。如果我们不是在用 ADO 、或者没有任何参数,那么代码会简单的调用 CursorFill 来填充 Cursor。注意,如果给 GetData 方法传递了 .T.,并且 CursorSchema 已经被填写好了,那么就是告诉 GetData 方法去使用 CursorSchema(这也是我想让 CursorAdapter 基类拥有的功能)。现在如果 Cursor 被建立起来了,代码会调用 GreateTags 方法来为该 Cursor 建立想要的索引;如果 Cursor 没有被建好,那么它会调用 HandleError 方法来处理任何发生了的错误。
lparameters tlNoData
local loParameter, ;
lcName, ;
luvalue, ;
llUseSchema, ;
loCommand, ;
llReturn
with This
* tlNoData参数指定是否要向 Cursor 中填充数据,如果要填充的话,
* 我们就要把建立存储所有参数的变量的活在这里就做掉而不是放到一个其它的方法中去。 * 因为我们希望这些变量的有效范围是私有的。
if not tlNoData
for each loParameter in .oParameters
lcName = loParameter.Name
luvalue = .GetParametervalue(loParameter)
store luvalue to (lcName)
next loParameter
endif not tlNoData
* 如果我们正在使用 ADO,并且有了一些参数,那么就需要有一个处理这些参数的 Command对象
llUseSchema = not empty(.CursorSchema)
if \'?\' $ .SelectCmd and (.DataSourceType = \'ADO\' or (.UseDEDataSource and ;
.Parent.DataSourceType = \'ADO\'))
loCommand = createobject(\'ADODB.Command\')
loCommand.ActiveConnection = iif(.UseDEDataSource, ;
.Parent.DataSource.ActiveConnection, .DataSource.ActiveConnection)
llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions, loCommand)
else
* 填充这个 cursor.
llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions)
endif \'?\' $ .SelectCmd ...
* 如果 Cursor 建立成功,则为之定义所有的 Tag,否则则处理发生的错误。
if llReturn
.CreateTags()
else
.HandleError()
endif llReturn
endwith
return llReturn
--
还有一些方法这里我们就不说了,你可以自己去研究它们。HandleError 方法使用 Aerror() 来判断发生了什么错误,并把错误数组的第二个元素放到 cErrorMessage 属性中去。Requery 方法与 GetData 类似,不过它是用来刷新 Cursor 中的数据。调用这个方法而不是 CursorRefresh 方法的原因就象 GetData 一样:它能够处理参数和错误。Update 方法很简单:它就是调用 TableUpdate() 来提交当前数据源的更新,如果提交更新失败,则调用 HandleError 方法来处理错误。AddTag 用于在 Cursor 被建立后将你想要建立的索引的信息添加到 Tags 集合中,而 GetData 方法会调用的 CreateTags 方法则会在自己的 Index ON 语句中用到这个集合中的信息。
这里是使用这个类的一个例子,是从附件中的 TestCursorAdapter.prg 中拿来的。它从 SQL Server 自带的 Northwind 数据库的 Customers 表中取得数据。它的 SelectCmd 属性里是一个参数化查询的 Select 语句,向你演示了怎样用 AddParameter 方法来处理参数,以及怎样用 AddTag 方法来自动地为 Cursor 建立索引标识。
local loCursor as SFCursorAdapter of SFCursorAdapter, ;
loConnMgr as SFConnectionMgrODBC of SFRemote, ;
loParameter as Empty
lnHandle = sqlstringconnect(\'driver=SQL Server;server=(local);\' + ;
\'database=Northwind;uid=sa;pwd=;trusted_connection=no\')
&& change password to appropriate value for your database
loCursor = newobject(\'SFCursorAdapter\', \'SFDataClasses\')
with loCursor
.DataSourceType = \'ODBC\'
.Alias = \'Customers\'
.SelectCmd = \'select * from customers where country = ?pcountry\'
.SetConnection(lnHandle)
.AddParameter(\'pcountry\', \'Brazil\')
.AddTag(\'CustomerID\', \'CustomerID\')
.AddTag(\'Company\', \'upper(CompanyName)\')
.AddTag(\'Contact\', \'upper(ContactName)\')
if .GetData()
messagebox(\'Brazilian customers in CustomerID order\')
set order to CustomerID
go top
browse
messagebox(\'Brazilian customers in Contact order\')
set order to Contact
go top
browse
messagebox(\'Canadian customers\')
loParameter = .GetParameter(\'pcountry\')
loParameter.value = \'Canada\'
.Requery()
browse
else
messagebox(\'Could not get the data. The error message was:\' + ;
chr(13) + chr(13) + .cErrorMessage)
endif .GetData()
* Now try to do an invalid SELECT statement.
.SelectCmd = \'select * from customersx\'
browse
else
messagebox(\'Could not get the data. The error message was:\' + ;
chr(13) + chr(13) + .cErrorMessage)
endif .GetData()
endwith
sqldisconnect(lnHandle)
SFDataEnvironment
*****************
在附件 SFDataClasses.vcx 中的这个数据和环境类要比 SFCursorAdapter 简单的多。但它增加了一些非常有用的功能:
×× GetData 方法会调用所有在这个数据环境类里面的 SFCursorAdapter 成员类的 GetData 方法,这样你就不需要自己去一个个的调用它们。与此类似的是,Requery 方法和 Update 方法也会调用每个 SFCursorAdapter 成员类的 Requery 和 Update 方法。
×× 象 SFCursorAdapter 一样,SetConnection 方法会把 DataSource 设置为一个 ADO Recordset,并把这个 Recordset 的 ActiveConnection 属性设置为一个 ADO Connection 对象。不过,它还会调用所有 UseDEDataSource 属性被设置为 .F. 的 SFCursorAdapter 成员类的 SetConnection 方法。
×× 它提供了一些简单的错误处理(cErrorMessage 属性会被填入错误信息)
×× 它有一个 Release 方法。
现在我们看看这个类的一对方法。GetData 非常简单:如果这个数据环境类的任何成员对象拥有 GetData 方法,则调用该方法:
lparameters tlNoData
local loCursor, ;
llReturn
for each loCursor in This.Objects
if pemstatus(loCursor, \'GetData\', 5)
llReturn = loCursor.GetData(tlNoData)
if not llReturn
This.cErrorMessage = loCursor.cErrorMessage
exit
endif not llReturn
endif pemstatus(loCursor, \'GetData\', 5)
next loCursor
SetConnection 方法稍微复杂一点:如果它的任何成员对象有 SetConnection 方法、并且该成员对象的 UseDEDataSource 属性被设置为 .F.,则调用该成员对象的 SetConnection 方法;然后,如果有任何一个 CursorAdapter 对象的 UseDEDataSource 属性被设置为了 .T.,则使用类似于 SFCusrorAdapter 中的那样的代码来设置自己的 DataSource:
lparameters tuConnection
local llSetOurs, ;
loCursor, ;
llReturn
with This
* Call the SetConnection method of any CursorAdapter that isn\'t using our
* DataSource.
llSetOurs = .F.
for each loCursor in .Objects
do case
case upper(loCursor.BaseClass) <> \'CURSORADAPTER\'
case loCursor.UseDEDataSource
llSetOurs = .T.
case pemstatus(loCursor, \'SetConnection\', 5)
loCursor.SetConnection(tuConnection)
endcase
next loCursor
* If we found any CursorAdapters that are using our DataSource, we\'ll need to
* set our own DataSource.
if llSetOurs
do case
case .DataSourceType = \'ODBC\'
.DataSource = tuConnection
case .DataSourceType = \'ADO\'
.DataSource = createobject(\'ADODB.RecordSet\')
.DataSource.ActiveConnection = tuConnection
endcase
endif llSetOurs
endwith
TestDE.prg 做了一个演示,它使用 SFDataEnvironment 作为容器,该容器中有一对 SFCursorAdapter 类。因为这个例子用的是 ADO,所以每个 SFCursorAdapter 都需要它自己的 DataSource,因此它们的 UseDEDataSource 属性都被设置成了 .F.(默认设置)。注意:
CA专题81_ca1981
只要简单的调用一下 SFDataEnvironment 的 SetConnection 方法就能搞定为每个 CursorAdapter 设置好 DataSource 属性的事情。
local loConn as ADODB.Connection
loConn = createobject(\'ADODB.Connection\')
loConn.ConnectionString = \'provider=SQLOLEDB.1;data source=(local);\' + ;
\'database=Northwind;uid=sa;pwd=\'
&& change password to appropriate value for your database
loConn.Open()
set classlib to SFDataClasses
loDE = createobject(\'SFDataEnvironment\')
with loDE
.AddObject(\'CustomersCursor\', \'SFCursorAdapter\')
with .CustomersCursor
.Alias = \'Customers\'
.SelectCmd = \'select * from customers\'
.DataSourceType = \'ADO\'
endwith
.AddObject(\'OrdersCursor\', \'SFCursorAdapter\')
with .OrdersCursor
.Alias = \'Orders\'
.SelectCmd = \'select * from orders\'
.DataSourceType = \'ADO\'
endwith
.SetConnection(loConn)
if .GetData()
select Customers
browse nowait
select Orders
browse
else
messagebox(\'Could not get the data. The error message was:\' + ;
chr(13) + chr(13) + .cErrorMessage)
endif .GetData()
endwith
loConn.Close()
可重用数据类
************
现在我们已经有了可用的 CursorAdapter 和 DataEnvironment 的子类,让我们来谈谈可重用数据类的事情。
一件 VFP 程序员们已经向 Microsoft 要求了很久的事情是可重用数据类。例如,你可能有
一个表单和一个报表,它们使用的是完全相同的一套数据,然而你却不得不重复的去手动向数据环境中添加一个个表或者视图——因为数据环境是不可重用的。某些程序员(以及几乎所有的应用程序框架提供商)采用了通过代码来建立可重用数据环境(那时候数据环境是不能可视化的建立子类的)的方法,并且在表单上使用一个 “loader”对象来建立 DataEnvironment 子类的实例。不管怎么说,这种方法总不是那么正规,并且无法用于报表。
现在,在 VFP8 里,我们不仅拥有了建立“能够向任何需要数据的对象提供来自任何数据源的数据”的可重用数据类的能力,还有了建立“能够寄宿数据类的”数据环境类的能力。在我写这篇文章的时候,你还不能在报表中使用 CursorAdapter 或者 DataEnvironment 的子类,不过你可以编程的添加 CursorAdapter 子类(例如把这些代码写在 DataEnvironment 的 INIT 方法中)来利用可重用类的优点。
让我们为 Northwind 数据库的 Customers 和 Orders 表建立一些数据类。CustomersCursor (在 NorthwindDataClasses.vcx 中)是 SFCursorAdapter 的一个子类,其属性如表1:
表 1. CustomersCursor 的属性
属性 值
Alias Customers
CursorSchema CUSTOMERID C(5), COMPANYNAME C(40), CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60),
CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), PHONE C(24), FAX C(24)
KeyFieldList CUSTOMERID
SelectCmd select * from customers
Tables CUSTOMERS
UpdatableFieldList CUSTOMERID, COMPANYNAME, CONTACTNAME, CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX
UpdateNameList CUSTOMERID CUSTOMERS.CUSTOMERID, COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME, CONTACTTITLE CUSTOMERS.CONTACTTITLE, ADDRESS CUSTOMERS.ADDRESS, CITY CUSTOMERS.CITY, REGION CUSTOMERS.REGION, POSTALCODE CUSTOMERS.POSTALCODE, COUNTRY
CUSTOMERS.PHONE, FAX, CUSTOMERS.FAX CUSTOMERS.COUNTRY, PHONE
你不会以为我会是手动在属性窗口中输入所有这些属性的值吧?当然不是!我是用 CursorAdapter 的生成器来干的。这里的技巧是打开“Use connection settings in builder only(只使用在生成器中的连接设置)”,填入连接信息以获得一个活动连接,再填好 SelectCMD 以后,最后再用生成器来生成其它的属性设置。
现在,任何时候你需要 Northwind 的 Customers 表中的数据,只要简单的放一个 CustomersCursor 类就够了。当然,我们还没有定义任何连接信息,不过做到这样就已经很不错了,有了这个类就不需要担心怎么获得数据(ODBC、XML、ADO)、使用哪种数据引擎(比如 SQL Server、Access 以及 VFP 8中都有 Northwind 数据库)之类的事情了。
不过,要注意的是这个 Cursor 对付的是 Customers 表中所有的记录。可有时候,你又只想要一个客户的数据。那么,CustomerByIDCursor 是 CustomersCursor 的一个子类,它的 SelectCmd 已经被改成 “Select * from customers where customerid = ?pcustomerid”,还有下面增加的 INIT 方法的代码:
lparameters tcCustomerID
dodefault()
This.AddParmeter(\'pCustomerID\', tcCustomerID)
这段代码会建立一个叫做 pCustomerID 的参数(它跟在 SelectCmd 中指定的是同一个),并且被设置成传递进来的任何值。如果没有值被传递进来的话,那么使用 GetParameter 方法来为这个参数返回一个对象,并在调用 GetData 之前设置它的 value 属性。
OrdersCursor 类类似于 CustomersCursor,只是它返回的是 Orders 表中的所有数据,而 OrdersForCustomerCursor 是它的一个子类,用于返回一个指定客户的所有订单。
要测试一下的话,请运行 TestCustomersCursor.prg,它会从 SQL Server 版本的 Northwind 数据库中 Customers 表的一个客户,然后做到 Access 版本的 Northwind 数据库所做的同样的事情。这个示例演示了怎样不为类指定连接信息,这个类自己就能灵活的完成任务,因此,它的可重用性是很强的。
示例:表单
**********
现在我们已经有了一些可重用类,让我们来用用它们。首先,我们来建立 SFDataEnvironment 的一个子类 CustomersAndOrdersDataEnvironment (哈哈,名字可有够长的,D.H牌冰糖葫芦!),它包含着一个 CustomerByIDCursor 类和一个 OrdersForCustomerCursor 类。由于我们希望在打开表之前设置连接信息,因此把它的 AutoOpenTables 属性设置为了 .F.,而且把前面两个 CursorAdapter 的 UseDEDataSource 属性都设置为了 .T.。现在,这个数据环境类已经可以被用来在某个表单中显示关于一个指定客户的信息以及他的订单了。
让我们来建立这么一个表单。附件中的 CustomerOrders.scx 表单的 DEClass 和 DEClassLibrary 属性已经被设置为了CustomersAndOrdersDataEnvironment 和 NorthwindDataClasses.vcx,这样就用上了我们的可重用数据环境类。这个表单的 Load 方法里面的代码有点多,不过这是因为它要支持 ADO、ODBC、以及 XML 数据源,并且它还要建立自己的连接,所以大多数代码都是处理这些问题的。如果它只支持一种数据源的话,比如只用 ODBC,再比如由另一个对象来管理连接(实际的应用程序开发中常常就是这样的),代码就会简单多了:
with This.CustomersAndOrdersDataEnvironment
* 获得连接
lnHandle = oApp.oConnectionMgr.GetConnectionHandle()
.SetConnection(lnHandle)
* 指定从 CustomerID 文本框中取得 cursor 参数的值
loParameter = ;
.CustomerByIDCursor.GetParameter(\'pCustomerID\')
loParameter.value = \'=Thisform.txtCustomerID.value\'
loParameter = ;
.OrdersForCustomerCursor.GetParameter(\'pCustomerID\')
loParameter.value = \'=Thisform.txtCustomerID.value\'
* 建立一个空的 cursor,如果失败的话则显示错误信息
if not .GetData(.T.)
messagebox(.cErrorMessage)
return .F.
endif not .GetData(.T.)
endwith
这段代码用上了那两个 CursorAdapter 对象的 GetParameter 方法来把 pCustomerID 参数设置为表单上一个文本框中的值。注意在值里面用到的\'=\',它表示在你需要 value 属性的时候再去运算它的值,所以我们实际上有了一个动态的参数(这样就顺应了当用户在文本框中输入了新的值以后要将改动反应到参数中去的需要)。调用 GetData 方法是为了建立一个空的 Cursor,这样才能安顿那些数据绑定的控件。
txtCustomerID 文本框没有绑定什么数据。它的 Valid 方法先调用数据环境的 Requery 方法,然后再调用表单的 Refresh 方法。这样,当用户输入一个新的客户ID的时候,就能够 Requery 那个 Cursor,接着表单上其它控件也会被刷新。表单上的其它文本框被绑定到由 CustomersByIDCursor 对象建立的 Customers cursor 的字段中。那个 Grid 被绑定到由 OrdersForCustomerCursor 对象建立的 Orders Cursor。
运行这个表单,并输入一个 Customer ID 为“ALFKI”(见图1)。当你按下 Tab 键跳出这个文本框的时候,你会看到该客户的地址信息以及他的订单就出现了。试着改动一些这个客户的数据或者订单数据,然后关闭表单再打开,再输入一次“ALFKI”,你会看到你没做什么工作这些改动就都已经被写到后台数据库中了。
此主题相关图片如下:
图1、
酷吧,嗯?跟建立一个基于本地表或者视图的表单相比,并没多多少工作。更棒的是:试试把定义在 Load 方法中的 ccDATASOURCETYPE 常量改变为 “ADO”或者“XML”,然后这个表单无论是看起来还是实际工作起来都跟没改过之前一摸一样(如果你想用 XML,你需要象上个月的文章中所说的那样为 Northwind 数据库设置一个 SQLXML 虚拟目录,
并把本月附件中的 XML 模板文件拷贝到那个目录里)。
示例:报表
**********
我们来试试报表。这里最大的问题是:与表单不同,我们既不能告诉报表去使用一个数据环境子类,也不能拖放一个 CursorAdapter 子类到数据环境中去。所以我们不得不向报表放入一些代码以将 CursorAdapter 添加到数据环境。尽管从逻辑上来看应该把这些代码放到报表数据环境的 BeforeOpernTables 事件中去,不过事实上这样做却是行不通的,因为——由于某些我不能理解的原因—— BeforeOpenTables 事件只会在你预览报表的每一页的时候才会触发。所以,我们只好把代码放在 Init 方法里。因为演示的需要,报表 CustomerOrders.frx 跟表单 CustomerOrders.scx 一样,要比实际开发的情况下会用到的代码更复杂一些。如果没有这些演示的需求的话,实际上可以简化到下面这样:
with This
set safety off
* 获得连接
.DataSource = oApp.oConnectionMgr.GetConnectionHandle()
* 建立客户和订单的 CursorAdapter 对象
.NewObject(\'CustomersCursor\', \'CustomersCursor\', ;
\'NorthwindDataClasses\')
.CustomersCursor.AddTag(\'CustomerID\', \'CustomerID\')
.CustomersCursor.UseDEDataSource = .T.
.NewObject(\'OrdersCursor\', \'OrdersCursor\', ;
\'NorthwindDataClasses\')
.OrdersCursor.AddTag(\'CustomerID\', \'CustomerID\')
.OrdersCursor.UseDEDataSource = .T.
* 取得数据,如果失败,则显示错误信息
if not .CustomersCursor.GetData()
messagebox(.CustomersCursor.cErrorMessage)
return .F.
endif not .CustomersCursor.GetData()
if not .OrdersCursor.GetData()
messagebox(.OrdersCursor.cErrorMessage)
return .F.
endif not .OrdersCursor.GetData()
CA专题81_ca1981
* 从 Customers 设置一个与 Orders 的关系
set relation to CustomerID into Customers
endwith
这里的代码比表单示例的要多一些,这是因为我们不能使用自定义的数据环境类导致不得不自己手动编码来代替。
现在,我们怎样才能尽可能简单的就把那些字段放到报表上去呢?由于 CursorAdapter 对象是我们用代码在运行时才添加到数据环境中去的,在设计时就没办法享受到拖放字段到报表上的方便了。这里有个小技巧:建立一个会建立这些 Cursor 的 PRG文件,并让这些 Cursor 处于有效范围内(可以采用挂起 PRG 的运行或者把 CursorAdapter 对象声明成 Public 的办法),然后使用快速报表功能来把那些字段放到报表上,这样报表控件的大小也设置好了。
图2展示了当你预览报表的时候该报表的情况。如果结合表单一起使用的话,你可以试试改动表单数据环境中的 #DEFINE 语句来换用其它类型的数据源。
总结
*****
我们对新的 CursorAdapter 基础类的研究就到这里了。我个人对 CursorAdapter 的出现感到非常的兴奋,并计划给我的应用程序框架中的数据处理部件升升级以更充分的利用它的优点。
--
vfp9 CA的新方法----DelayedMemoFetch
vfp8的CA有一个FetchMemo属性
如果设置FetchMemo= .F.
那Memo字段的内容会不读取,但如果需要里面的内容就会变的非常麻烦
VFP9开始有了DelayedMemoFetch方法
可以做到,cursorFill()的时候,Memo里面全空
但,当光标移动到Memo字段的时候才自动读取当前条记录的Memo字段内容
注意是:当前条记录的Memo字段内容
这个方法能极大的提高读取带有Memo字段表的效率
操作办法如下:
1.FetchMemo = .F.
2.FetchMemoDataSourceType = ca.DataSourceType
3.FetchMemoDataSource = ca.DataSource
4.FetchMemoCmdList
这个是最关键的,也是最麻烦的
oCA.FetchMemoCmdList= "f1 <SELECT f1 FROM testCAMemoFetch WHERE f0=?EVALUATE(this.RefreshAlias +\'.f0\')>, f2 <SELECT f2 FROM testCAMemoFetch WHERE f0=?EVALUATE(this.RefreshAlias +\'.f0\')>"
f1,f2这里是Memo字段名简称
f0是关键字段名
this.RefreshAlias可以是this.Alias
翻译一下意思如下:select Memo字段名 From 后台表名 whete 关键字段名=?前台的Alias名.关键字段内容
注意:
DelayedMemoFetch是个内置保护方法
在程序里是不能直接调用
--
Visual FoxPro 9 C/S方面极富人性化的增强
自从VFP 8 开始,在C/S方面提供了一个CursorAdapter 类, CursorAdapter 是一个基于松散耦合思想设计的对象化的 Cursor 处理模型。对 CursorAdapter 类很多人对其褒贬不一,特别是一些老的Foxer认为做C/S系统用SPT就足够了,何必再增加一个类呢?但我可以这么说,从VFP9 开始,几个CursorAdapter新增加的功能,相信足以使那些纯使用SPT 的Foxer 心动不已了。
下面我先谈谈VFP9 在 CursorAdapter 部分的几个增加和增强。
1. 属性值可以超过255 个字符
用过CursorAdapter 类的人都知道,VFP8 时其几个属性 SelectCmd、CursorSchema、UpdateNameList、UpdatableFieldList的长度不能超过255个字符,这在VFP8时让人感觉是一件非常滑稽的事情,当后台表的字段数一多,连使用它自身生成器生成的字符串都要报错,不能保存,这点造成CursorAdapter使用起来极为不便,也是全球Foxer要求解决的呼声最多的地方,现在这个问题在VFP9 中终于得到了彻底解决。
2. 生成器生成的CursorSchema 不再是按照字母次序来排列了
当我们设计表单上控件时,只要在表单数据环境里放入CursorAdapter,设置 CursorAdapter 的CursorSchema 属性就会可视化的出现一个Cursor, 此时只要拖动相应字段到表单,即可完成一个个控件的设计,这也是CursorAdapter的优点之一,VFP8 时利用生成器生成的CursorSchema 是按照字母次序排列的,这点造成设计时非常的不方便,大家习惯的是自己设计表时的字段次序,现在VFP9 在这方面也做了非常人性化的修改,现在我们可以完全非常舒服的利用其本身的生成器来生成 CursorSchema 供我们设计表单使用。
3. NoData 和 UseCursorSchema 属性
VFP8 时当把CursorAdapter 设计时放到表单的数据环境里,使用CursorFill()里的这两个参数极为不便,现在好了,把这两个属性单独列了出来。
4. TimeStamp 时间戳字段
VFP8 的CursorAdapter 虽然 WhereType 可以等于 4 ,可是其实时间戳字段真正在更新时却过滤掉了,根本不起任何作用。现在VFP9 的CursorAdapter 增加了 TimestampFieldList属性,如果你的后台表里有时间戳的话,只要设置一下这个属性,然后设置WhereType = 4即可。
5. RecordRefresh()
VFP8时CursorAdapter只能用 CursorRefresh() 来刷新前台,可很多时候,我们可能只需要刷新其中一条或者几条记录的数据,可CursorRefresh(),如果前台记录有 1000 条,也要全部重新读一遍,读取完毕以后,记录指针却始终定位在第一条记录,这样既巨大的浪费了网络资源,同时很多情况下还要花费很大的精力重新定位记录指针。
现在 VFP9 增加了这个极富人性化的方法-----RecordRefresh(),能做到任意刷新此Cursor里的任意一条或者连续几条记录,而且当前记录指针保持不变,看到这里相信做过C/S程序的朋友们是不是有一种跃跃欲试的感觉?这个可以期盼已久的功能啊。
RecordRefresh()里有2个参数,RecordRefresh(nRecords,nRecordOffset)
1) nRecords 表示要刷新几条记录
2) nRecordOffset记录偏移量,指是当前记录开始加几条记录
例子1,比如当前记录号是第5条,我要刷新第7、8两条记录,
oCa.RecordRefresh(2,-2)
例子2, 只刷新当前记录,
oCa.RecordRefresh(1)
6. CursorAdapter 的 Auto-Refresh
VFP9提供了记录的Auto-Refresh,其作用、功能、效果和前面介绍的RecordRefresh() 基本相似,有异曲同功之妙用。
让我们来看看具体是怎么使用的吧:
InsertCmdRefreshFieldList、InsertCmdRefreshKeyFieldList、InsertCmdRefreshCmd
这三个新增的属性是针对新增记录时使用,一般我们只需要设置InsertCmdRefreshFieldList、InsertCmdRefreshKeyFieldList即可,从字面已经非常好理解需要设置的内容,InsertCmdRefreshKeyFieldList可以和CursorAdapter的KeyFieldList相同。
UpdateCmdRefreshFieldList、UpdateCmdRefreshKeyFieldList、UpdateCmdRefreshCmd
这三个新增的属性是针对修改记录时使用的,一般我们也只需设置UpdateCmdRefreshFieldList和UpdateCmdRefreshKeyFieldList即
InsertCmdRefreshKeyFieldList完全可以和CursorAdapter的KeyFieldList相同。 可。
让我们设置好这些属性以后看看效果吧,当TableUpdate()提交后台成功以后,记录就自动刷新了,而且当前的记录指针保持不变,这难道不正是我们想要的吗?
请注意,RecordRefresh()和Auto-Refresh 的区别,虽然RecordRefresh() 也能刷新多条指定的记录,可只能是连续的几条记录,而Auto-Refresh却只刷新已修改过的记录。RecordRefresh()刷新的记录可以自己来指定,可Auto-Refresh 刷新的记录却是 CursorAdapter 来控制的。
7. DelayedMemoFetch()
VFP8 的CursorAdapter 里有一个属性:FetchMemo,当设置FetchMemo = .F. 时,本表里的所有Memo 字段都不读取到前台,这样来大大提高了读取数据时的效率,特别是当Memo
字段里内容多的时候,可是当我们需要Memo字段里的内容时怎么办呢?这点是我们一直以来不管用远程视图、SPT还是用CursorAdapter做 C/S 程序时的痛苦矛盾之处,现在我们终于在VFP9的CursorAdapter 里找到了完美的解决办法。
先用文字来说明一下 VFP9 的CursorAdapter 的解决方法,当 CursorFill() 时,所有Memo字段的内容全部为空,以提高读取记录时的效率,但当光标移动到这条记录的这个字段时,自动从后台读取这条记录的当前Memo字段的内容到前台,填充进CursorAdapter里,以我实际操作下来的感觉,几乎根本感觉不到这个读取过程,而数据确确实实读到了前台。 整个设置过程如下:
1)oCa.FetchMemo = .F.
2)oCa.FetchMemoDataSourceType = oCa.DataSourceType
3)oCa.FetchMemoDataSource = oCa.DataSource
4)oCa.FetchMemoCmdList 这个是最关键的,也是最麻烦的:
oCA.FetchMemoCmdList= "f1 <SELECT f1 FROM testCAMemoFetch WHERE f0=?EVALUATE(this.RefreshAlias +\'.f0\')>, f2 <SELECT f2 FROM testCAMemoFetch WHERE f0=?EVALUATE(this.RefreshAlias +\'.f0\')>"
f1,f2这里是Memo字段名简称
f0一般是关键字段名
this.RefreshAlias可以是this.Alias
我翻译一下,就应该比较好理解了,意思如下:” Memo字段名 <select Memo字段名 From 后台表名 where 关键字段名=?前台的Alias名.关键字段内容>,?..”
只要设置好了以上属性,当前台记录移动到Memo字段时 CursorAdapter 会自动调用DelayedMemoFetch(),来读取Memo字段内容。
注意:DelayedMemoFetch是个内置保护方法,在程序里不能直接调用。
RecordRefrsh()、Auto-Refresh和DelayedMemoFetch() 此三项功能是 SPT 无法做到的,合理使用好此三项功能,能极大的提高整个 C/S 系统的效率,降低网络和服务器开销。但使用RecordRefrsh()和Auto-Refresh功能时有点需要特别注意:大家读到这里应该可以看出,其实此三项功能,都是依靠关键字来定位记录的,可当我们用自增量型字段做关键字时,当新增记录时,其值是更新成功以后才能得到,所以当在前台使用RecordRefrsh()和Auto-Refresh时会因无法定位记录而要出错或得不到正确结果。
以上7点是我认为是 VFP9 CursorAdapter 类最具人性化和效率化的方面的功能增强,下面再说说其他几个C/S 方面的功能增强。
1. RecordsFetched 和 FetchIsComplete
以前我们读取大批量记录的时候,如果想要做一个人性化的进度条,是一件非常不容易的事情,一般只能采取异步方式,先读取记录总数,然后用一个循环,
lnResult = SQLEXEC(lnHandle,“SELECT * FROM ?. WHERE ?”,“Temp”)
DO WHILE lnResult = 0
liResult = CURSORGETPROP("FetchSize")
此处写入进度条代码
ENDDO
这样做,效果是出来了,可是也有缺陷,异步其实变成了同步。现在 VFP9 在CursorGetProp() 里增加了两个参数,RecordsFetch 和 FetchIsComplete,意思和使用方法如下:
RecordsFetched 返回读取远程表过程中目前所传回来的记录数目
例如:lnResult = CURSORGETPROP("RecordsFetched")
FetchIsComplete 表示读取过程是否已经完成
如果已完成 = .T.
例如:llResult = CURSORGETPROP("FetchIsComplete")
有了这两个属性,要做一个进度条就轻而易举了,而且能实现异步功能不丧失的效果。 RecordsFetch 和 FetchIsComplete应该还有其他用处,留待我们一起来慢慢挖掘吧。
2. SQLIDLEDISCONNECT() 这是我认为 VFP9 中极其有意义的 C/S 功能增强。
尤其是对CursorAdapter,当然对SPT和远程视图也同样适用。
SQLIDLEDISCONNECT() 从字面上理解是暂时中断连接。刚开始,我始终未能理解其意思,可当亲手做了实验以后,就不得不惊叹其功能之强大了。
以前在VFP8下我曾仔细做过 CursorAdapter 的断线重连的测试,得出结论,CursorAdapter断线重连以后,当前数据可以向后台更新,但已经无法CursorRefresh()了,只能重新CursorFill()以后才行,但重新CursorFill()以后,前台的Cursor 等于是重新生成了,这对前台如果是用GRID来绑定 Cursor 时尤为不便。
现在终于盼到了----- SQLIDLEDISCONNECT(),当CursorFill()、
CursorRefresh()或者RecordRefresh()以后 SQLIDLEDISCONNECT(lnHandle),此时检查服务器,确实此句柄已经不存在了,和服务器 完全脱离了关系,为了保险起见,把网线拔了,过一段时间再插上,再对CursoraAdapter 进行数据更新,提交后台,然后在前台执行CursorRefresh()或者RecordRefresh(),这个时候检查服务器,连接句柄自动建立成功了,而且数据确实更新成功并重新刷新。
再用SPT 做了测试,同样得到了完美的结果。
综上所述,一旦执行了SQLIDLEDISCONNECT(),这个句柄就处于休眠状态,此时完全和服务器脱离了关系,一旦发生前台向后台请求任何的任务,此时 VFP 将无需任何条件、无需任何人工干预的自动唤醒此连接,且句柄号不变。
此项功能在用户数较多或者网络环境较差的情况下尤其有用,特别是在降低服务器的资源消耗方面有着非常大的作用。
以上是我在探索 VFP9 的 C/S 新功能过程中总结的几点心得,从以上几点不难看出微软的VFP 开发小组在 C/S 人性化使用方面所做的努力,其实在VFP9里所有新增或者增强的功能里处处都能体会到这点。由于时间关系,先拿出这些来和大家共享,下笔匆匆,可能其中有不少不够深入或者认识错误之处,请读此篇的狐友谅解指正,谨以此文抛砖引玉,希望更多的狐友一起加入到探索VFP9 新功能的行列中来。
________________________________________
VFP9的CursorAdapter的行级刷新和Memo字段级刷新,使得CursorAdapter从VFP9开始又迈进了大大的一步
.
--
Visual FoxPro 8.0的CursorAdapter类及其在远程数据访问中的意义
摘要: 本文从 Visual FoxPro 应用程序的观点深入剖析Visual FoxPro 8.0中新增的CursorAdapter类及其在远程数据访问中的意义。CursorAdapter对各种远程数据源提供了一种统一的面向对象的访问接口,开发人员借此可以轻松实现基于Native(本地数据)、ODBC、
CA专题81_ca1981
ADO、XML的数据访问。
关键字:CursorAdapter 远程数据访问 ADO 视图 Cursor COM
1、引言
就在“Visual FoxPro已过时,并即将被淘汰??”的一片喧嚣声中,Microsoft于今年6月正式发布了Visual FoxPro 8.0 。据称,这是自Visual FoxPro 3.0发布以来最精彩的一个版本。新增众多如:结构化异常处理、事件绑定、数据环境子类、XML Web services 、自动递增字段、控件延迟绑定、WindowsXP风格支持等等令人目不暇接的功能,着实让全球Foxer们兴奋不已,然而其中最值得令人称道的当数CursorAdapter类,笔者以为这是Visual FoxPro 8.0最抢眼的一个亮点。
长期以来,Visual FoxPro因为其先天的弱点如:表的脆弱性、安全性、数据库的容量、以及通用性等等,一直成为人们攻击的目标。因而越来越多的 VFP 程序员开始抛弃DBF表,转而采用如SQL Server、Oracle 等一些大型数据库来存储数据,而将VFP作为C/S结构中的前端开发工具或者作为基于COM的三层结构应用程序的中间层开发工具。然而,传统的方式通常是使用远程视图或SQL Pass Through 然后经过 ODBC 来访问远程数据。随着新的基于组件技术的数据访问规范OLE DB和扩展标记语言XML的出现,对VFP开发人员来说无疑产生了极大的吸引力。然而,在以前各版本的Visual FoxPro中,这些新技术都无法得以很好地利用。Microsoft当然也看到了这一点,于是在刚刚发布的Visaul FoxPro 8.0中,一个全新的为用户提供统一的数据访问接口、被称为CursorAdapter的基类,展现在了全球Foxer们面前。借助 CursorAdapter,在 Visual FoxPro 8.0 中访问远程数据要比过去的任何一个版本都简单的多:
• ODBC、ADO、XML的使用变得非常容易,即时你不熟悉这些技术。
• 不管你选择了哪种远程数据源机制,它都提供一种统一的访问接口。
• 从一种数据访问机制转换到另一种数据访问机制变得非常轻松。
2、CursorAdapter的设计理念
要彻底理解CursorAdapter,必须先弄清Microsoft设计CursorAdapter的真正意图。
众所周知,基于OLE DB的ADO数据访问技术,正日渐成为Windows平台主流的数据存取解决方案,VC、VB、Delphi这些当今流行的开发工具,都无一不支持ADO技术。在需要编写组件的场合, ADO更是一种理想的选择。然而很久以来,在Visual FoxPro中,我们似乎看不到ADO的影子。是不是Visual FoxPro不支持ADO呢?当然不是。ADO实际上是一组实现数据存取的COM对象,Visual FoxPro支持COM,当然也支持ADO。
然而,在Visual FoxPro 8.0出现之前,几乎很少看到有VFP程序员采用ADO来访问如SQL Server、Oracle 等一些大型数据库,其原因何在?原来,在Visual FoxPro中,ADO产生的RecordSet对象,不能像VFP中的表或视图(本地或远程)那样实现与控件的绑定,也不能作为一个整体的数据对象来处理,必须使用循环语句来逐条处理RecordSet中的记录,因而也就无法发挥Visual FoxPro强大的数据处理功能。
现在,CursorAdapter的出现彻底改变了这一切。可以这么说,CursorAdapter为我们提供了一种全新的远程数据访问解决方案,无论你是使用本地表(Native)、ODBC、ADO还是XML,CursorAdapter均提供了一个统一的数据访问接口,让你轻松实现远程数据的存取。
在详细剖析CursorAdapter的运作机制之前,让我们先来看一下传统的Visual FoxPro中的数据处理模型。
Visual FoxPro中,一切数据处理其实都是围绕着游标(Cursor)进行的。如:DBC中的视图就是一个Cursor、表单数据环境中的表也是Cursor,Select-SQL产生的查询结果还是Cursor(只读)。可以说,Visual FoxPro灵活、强大的数据处理功能无一不归功于Cursor的参与。 然而,对于ADO所产生的RecordSet,Visual FoxPro却显得英雄无用武之地。所以,面对
ADO,Visual FoxPro急于解决的问题是:要么推倒原有的整个VFP数据处理架构,重建一个对象化的Cursor以实现与对象化的RecordSet相容;要么通过某种方式,将ADO的RecordSet转化为VFP能处理的Cursor,而原有的以Cursor为中心的数据处理架构保持不变。最终结果是,Mirosoft选择了后者,事实上这也是一种最合理的方式。
再来看一下Visual FoxPro 8.0中的CursorAdapter类。CursorAdapter就是一个为实现上述思想而设计的对象化的数据处理模型。无论是本地数据(DBF),还是基于ADO、ODBC、XML的远程数据,都可以通过这个CursorAdapter产生一个能为VFP处理的Cursor,而对该Cursor的所有处理结果,又可以根据需要决定是否返回给数据源。需要注意的是:CursorAdapter本身并不是Cursor,而只是一个用于产生Cursor的对象化的数据处理模型。
与VFP中其它非可视化类一样,CursorAdapter也不提供可视化界面,即它不是以控件的形式出现在VFP的表单控件工具箱中,但这并不妨碍我们对它的使用。CursorAdapter提供了许多属性、事件与方法。如常用的属性有:Alias、DataSourceType、Datasource、SelectCmd、UpdatableFieldList、UpdateNameList等;方法有:CursorFill、CursorRefresh、AutoOpen等;事件有:AfterUpdate、BeforeUpdate、AfterCursorFill、BeforeCursorFill、AfterCursorRefresh等。在此不一一列举,读者可以查阅Visual FoxPro 8.0中相关的帮助文件。通过这些属性、事件、方法,开发人员可以自由方便地操纵CursorAdapter对象,实现对远程数据的存取。
3、CursorAdapter实例演示
接下来,让我们通过两个示例来看一下CursorAdapter如何实现对远程数据的存取以及它与直接使用ADO组件来处理远程数据有何不同。
[示例一]:直接使用ADO访问SQL-Server:
connstr="Persist Security Info=False;User ID=sa;Initial Catalog=cjgl;Data Source=HXM" oConnection = CreateObject("adodb.connection") &&建立连接对象
With oConnection
.Provider = "SQLOLEDB.1"
.ConnectionString = connstr
.Open
EndWith
oRecordSet = CreateObject("adodb.recordset") &&建立记录集对象
oRecordSet.Open("Select * From xs",oConnection)
Do While !oRecordSet.Eof &&通过循环语句列举姓名字段 ? oRecordSet.fields("xm").value
oRecordset.MoveNext
EndDo
[示例二]:使用ADO+ CursorAdapter访问SQL-Server:
connstr="Persist Security Info=False;User ID=sa;Initial Catalog=cjgl;Data Source=HXM" oConnection = CreateObject("adodb.connection") &&建立连接对象
With oConnection
.Provider = "SQLOLEDB.1"
.ConnectionString = connstr
.Open
EndWith
loCursor = Createobject(\'CursorAdapter\') &&建立CursorAdapter对象
With loCursor
.Alias = \'student\'
.DataSourceType = \'ADO\' &&指定数据源类型
.Datasource = Createobject(\'ADODB.RecordSet\')
.SelectCmd="select * FROM xs" &&指定用于产生Cursor的Select-SQL语句
.DataSource.ActiveConnection = oConnection
ENDWITH
loCursor.CursorFill() &&将后台数据重载到前台并产生出一个Cursor Browse
[注]:以上代码用到了一个非常简单的SQL-Server数据库XSCJ,其中包含一张XS表。 从上面代码可以看到,CursorAdapter本质上是基于ADO的RecordSet,通过RecordSet产生出一个可更新的Cursor(游标),使得我们可以用所有VFP传统语句来处理该Cursor,如可以用Browse来浏览该Cursor。
读者可能会说,CursorAdapter的作用无非就是将RecordSet转换为一个适合VFP处理的Cursor,用户完全可以自己写个类,将此转换代码封装其中,不照样实现CursorAdapter的功能吗?
实际上,事情并非完全如此。CursorAdapter除了产生出一个Cursor外,更重要的在于引入了相关的对象事件模型,如:BeforeInsert、AfterInsert、BeforeUpdate、AfterUpdate等, 从而使得编程人员可以利用这些事件,来控制对Cursor的各种操作。而这一点,正是普通意义上的视图所无法实现的。DBC中的视图虽然也可更新,但它不具有对象事件模型,因而开发人员无法去控制它,一旦执行TableUpadte,数据便立即更新,没有商量的余地。 下面代码演示了Update-SQL语句如何触发CursorAdapter 的AfterUpdate事件: Connstr="Persist Security Info=False;User ID=sa;Initial Catalog=cjgl;Data Source=HXM" oConnection = Createobject("adodb.connection")
With oConnection
.Provider = "SQLOLEDB.1"
.ConnectionString = Connstr
.Open
Endwith
loCursor =Createobject("myCurAdp") &&创建CursorAdapter对象 With loCursor
.Alias = \'student\'
.DataSourceType = \'ADO\'
.Datasource =Createobject(\'ADODB.RecordSet\')
.DataSource.CursorLocation = 3 && adUseClient
.DataSource.LockType = 3 && adLockOptimistic
.SelectCmd="select * FROM xs"
.UpdatableFieldList="xh,xm,xb,zydh,xidh"
.UpdateNameList="XH xs.XH, XM xs.XM, XB xs.XB, ZYDH xs.ZYDH, XIDH xs.XIDH" .KeyFieldList="xh"
.AllowUpdate=.T.
.SendUpdates=.T.
.Datasource.ActiveConnection = oConnection
Endwith
IF loCursor.CursorFill()
Update student Set xm="王一平" WHERE xb="男" &&触发AfterUpdate事件
ENDIF
**定义一个基于CursorAdapter的子类
Define Class myCurAdp As CursorAdapter
Procedure AfterUpdate &&定义AfterUpdate事件
Lparameters FldState,lForce,nUpdateType,cUpdateInsertCmd,cDeleteCmd,cResult
Wait Window "触发AfterUpdate事件!"
Endproc
Enddefine
当执行Update-SQL命令时,就会触发myCurAdp类的AfterUpdate事件,用户可以在此事件中做一些自己想做的事。
4、数据环境中的CursorAdapter
如果你对PRG文件中的OOP编程不是很熟悉,那也不要紧,Visual FoxPro 8.0重新设计了表单的数据环境,在新的数据环境中同样提供了CursorAdapter类,你甚至可以用可视化的方式来设置CursorAdapter的各项属性,设置完毕,就会在数据环境设计器中产生一个Cursor,然后像处理本地表一样处理这个Cursor,例如,你可以将其拖放到表单上实现与表单控件的绑定、用TableUpdate()来更新数据源。
不过,从Microsoft对Visual FoxPro的产品定位策略来看,Microsoft更倾向于将Visual FoxPro作为一个多层结构的中间层开发工具,并且将其融合到.NET框架中来。而多层结构的中间层实际就是一些COM组件。一方面,COM组件的编写必须基于OOP,并且在VFP中,支持私有工作期的Session类只能在PRG文件中定义;另一方面,进程内的COM组件不允许带有界面元素,因此从组件开发的角度来看,熟悉PRG中的OOP编程更显重要,这也是一个真正的程序设计高手所必须具备的基本功。
5、结束语
曾经风靡一时的FoxPro,在.NET、XML、Web Service大行其道的今天,并没有完全被淘汰,至少目前或者今后几年内不会。在保持快速、灵活、强大的数据处理能力的同时,新的Visaul FoxPro 7.0、8.0对.NET、XML、Web Service等新技术的强有力的支持,使其依然充满生机和活力。据今年6月在美国加利福尼亚举行的“Visual FoxPro DevCon 2003“会议透露,Visaul FoxPro的下一个版本已在开发中,代码为Europa(木卫二),其Beta版预计将于明年下半年发布。Microsoft承诺,对Visaul FoxPro 8.0的技术支持将延续到2010年。
本文标题:81道变态逻辑题-日本81道变态逻辑题,答出越多越变态31-4061阅读| 精彩专题| 最新文章| 热门文章| 苏ICP备13036349号-1