回 帖 发 新 帖 刷新版面

主题:推荐几编CA的文章(转贴)

CursorAdapter 起步
CursorAdapter 起步

作者:Dung Hennig
译者:fbilo

CursorAdapter 类是 VFP 8 中最重要的新功能之一,因为它提供了一种简单易用、接口统一的访问远程数据源方式。在这个月的文章里,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同样的优势。而更棒的是,即使数据源是 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, CONTACTNAME CUSTOMERS.CONTACTNAME'
if .CursorFill()
  browse
else
  aerror(laErrors)
  messagebox(laErrors[2])
endif .CursorFill()
endwith


**********************************************************

回复列表 (共14个回复)

沙发

CursorAdapter 起步 之 二:用 CursorAdapter 来取得和更新数据

作者:Dung Hennig
译者:fbilo

在 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 属性设置成其它几个选项之一(当然可能还需要修改其它几个属性,例如设置连接信息等等),就能轻松的切换到另一种数据库引擎,例如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,这样它就可以把更新提交给后台数据源。

这个 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 模板的内容如下:

<root xmlns:sql="urn:schemas-microsoft-com:xml-sql">
  <sql:header>
    <sql:param name="customerid">
    </sql:param>
  </sql:header>
  <sql:query client-side-xml="0">
    SELECT *
    FROM   Customers
    WHERE CustomerID = @customerid
    FOR XML AUTO
  </sql:query>
</root>

把这个文件放在用于 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/&#39;, .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。它的内容如下:

<root xmlns:sql="urn:schemas-microsoft-com:xml-sql">
  <sql:query client-side-xml="0">
     SELECT *
     FROM   Customers
     FOR XML AUTO
  </sql:query>
</root>

为了测试的目的,打开你的浏览器,并输入这个URL:http://localhost/northwind/template/getallcustomers.xml,你就会在浏览器中看到XML形式的 Northwind Customers 表的内容了。

<root xmlns:sql='urns:schemas-micorsoft-com:xml-sql">
<Customers CustomerID="ALFKI" CompanyName="Alfreds Futterkiste" ContactName = "Maria Anders"
ContactTitle="Sales Represendative"
Address="Obere Str. 57" City="Berlin" PostalCode="12209"
Country="Germany" Phone="030-0074321"
Fax="999-999-9999" />

现在你就已经做好运行这篇文章的 SQLXML 示例的准备了。

板凳

CursorAdapter 起步 之 三:可重用数据类

作者:Doug Hennig
译者:fbilo

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 对象, 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'
if .GetData()
  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
return llReturn

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.(默认设置)。注意:只要简单的调用一下 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.COUNTRY, PHONE CUSTOMERS.PHONE, FAX, CUSTOMERS.FAX

你不会以为我会是手动在属性窗口中输入所有这些属性的值吧?当然不是!我是用 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()

  * 从 Customers 设置一个与 Orders 的关系
    set relation to CustomerID into Customers
endwith

这里的代码比表单示例的要多一些,这是因为我们不能使用自定义的数据环境类导致不得不自己手动编码来代替。

现在,我们怎样才能尽可能简单的就把那些字段放到报表上去呢?由于 CursorAdapter 对象是我们用代码在运行时才添加到数据环境中去的,在设计时就没办法享受到拖放字段到报表上的方便了。这里有个小技巧:建立一个会建立这些 Cursor 的 PRG文件,并让这些 Cursor 处于有效范围内(可以采用挂起 PRG 的运行或者把 CursorAdapter 对象声明成 Public 的办法),然后使用快速报表功能来把那些字段放到报表上,这样报表控件的大小也设置好了。

图2展示了当你预览报表的时候该报表的情况。如果结合表单一起使用的话,你可以试试改动表单数据环境中的 #DEFINE 语句来换用其它类型的数据源。

总结
*****

    我们对新的 CursorAdapter 基础类的研究就到这里了。我个人对 CursorAdapter 的出现感到非常的兴奋,并计划给我的应用程序框架中的数据处理部件升升级以更充分的利用它的优点。

3 楼

一个使用说明,关于cursoradapter类的

作者:四维

vfp8最激动人心的变化是  CursorAdapter 类,它为不同的数据源提供通用的数据接口。
下面介绍怎样用 CursorAdapter 改变在 VFP 8 中连接数据的方式,包括,native tables, ODBC, OLE DB, XML.  。   
CursorAdapter 类是 VFP 8 开发组的最给人印象深刻的成就之一。 它将会改变许多开发者连接各种不同的数据来源的方式。 开发小组在 VFP 存取数据的方式方面作了重要改变,将本地和远程数据连接方式进行了统一。另外,创建 CursorAdapter 类对那些已经熟练使用视图和SPT人来说并不费力。对使用ADO RecordSets 或XML可扩展标示语言文件的人也不费力。
CursorAdapter 类的独特之处在于,它是第一个提供本地游标、ODBC、ADO、XML数据源连接的基类,在一个类里面实现了所有连接功能。换句话说,要将ODBC数据源,ADO RecordSet 或XML可扩展标示语言文件翻译成一个 VFP 游标,CursorAdapter 类完全可以胜任。
你或许会说 CursorAdapter 是较早版本中本地视图和远程视图技术的替代 (注意: VFP 8中仍然保留这些功能). 但是在一些情形中,它也代替了SPT, 以及减少了使用ADO和XML时的代码量,可以直接使用ADO和XML。
CursorAdapter 的最大好处是,需要在同一个程序内连接到多个数据源的时候,它为你提供方便。举例说,如果你的程序大部分数据来自 SQL server,但是同时需要与XML可扩展标示语言连接,CursorAdapter 可以整合这两种情况,使程序取回的数据作为VFP的游标。
另外的一个例子是数据现在被储存在 VFP 表中 , 但是计划要移到一个数据库服务器 , 比如 SQL server或Oracle。 你需要先建立一组 VFP CursorAdapter 类,必要的时候以 SQL server以外的数据库代替这些类。
但是,就象我们能跑之前必须学会走一样,先概览一下 CursorAdapter 类和它的特性。 然后,使用 CursorAdapter 类来设计数据类是比较容易的。
建立第一个 CursorAdapter 类,象其他类一样,学习怎样使用它的最好办法是了解建立过程。第一次建立这些类的时候复杂程度低一些,我们开始用 CursorAdapter 类存取 VFP 本地的数据。 这很象使用本地视图取回 VFP 本地表的数据。 稍后在这一篇文章中,我们将会使用另一个 CursorAdapter 类连接到 SQL server数据库,ODBC和XML。
首先,你有二个方法建立 CursorAdapter 。 你能使用数据环境建立,也可以手动通过程序或类设计器来创建一个经过一个CursorAdapter类 。 这一个例子将会使用数据环境建立;较迟的例子将会手动建立.
如果你不熟悉 VFP 8 变化后的数据环境, 你可能认为在设计环境下创建的 CursorAdapter 只能在表单中使用,不能用于类。 然而,  VFP 8 中已经改善了设计环境,因此不需要在表单中就可以创建。
用creat class命令创建一个新的数据环境类。

此主题相关图片如下:

确定从下拉列表中选择based on数据环境类(dataenvironment) 。类名为Tests,所属类库名为tests.vcx.

此主题相关图片如下:

创建的类在类设计器中出现后,右键单击Data Environment ,选择builder,起动数据环境建立向导。

此主题相关图片如下:

在数据源类型项下,注意可选的选项。 因为第一个例子将会连接到本地的 VFP 表,选择native。 选择完以后, 使用‘省略号’按钮选择 Northwind 数据库(我这里是gzdata数据库)。 (默认位置是 c:\ program files\microsoft visual foxpro 8\ sample\northwind\ northwind.dbc)

此主题相关图片如下:

下一步,点cursors 页, 它初始值是空的。 在列表框中,选择new按钮用 CursorAdapter 设计器创建一个新的 CursorAdapter 类。

此主题相关图片如下:

首先,你应该看看Properties页,这里提供选项来选择类的名字和由类产生的游标的别名。
确定提供一个不同于表名字的别名,避免产生混乱。 在这里,使用 caCustomer 做类名, cCustomer 作为别名。 如果想让这个类用和数据环境一样的数据源,应该选择 "Use DataEnvironment data source" 。 注意你可以为 CursorAdapter 设置不同的数据源, 允许你在不同的类之间整合数据源.( 例如一个类使用ODBC数据源,另一个类使用XML数据源)

此主题相关图片如下:
要定义CursorAdapter 如何返回来自数据源的数据,使用设计器中的Data Access 页。 按build按钮激活一个对话框,可以选择游标包含的字段。

此主题相关图片如下:

在这个例子中,选择Customers表, 然后选择Customers.*。 点击向右的箭头移动选择项, 然后点ok。

此主题相关图片如下:

这为你建立下列SQL语句:
   select CUSTOMERS.* from CUSTOMERS
如果你想添加过滤器,连接, 或其他条件到查询,你可以在列表框中直接键入。 如果你想建立带参数的查询,有一些选项,在本文后面介绍。 现在, 让我们添加WHERE子句:
   select CUSTOMERS.* from CUSTOMERS where
   companyname like 'C%'
此主题相关图片如下:

这里可以看出源表和游标的不同,因为只有少数记录符合WHERE子句。
在第二个编辑框(schema)中已经为为你创建了字段列表。通常在继续以前花几分钟看看字段顺序是否符合你的习惯是有好处的
在这一页的下半部分有数据包设置对话框(data fetching),用来设置怎样处理远程数据包,当用vfp作为数据源的时候,这里的设置不发挥作用。(如上图)这里我们保留默认设置,稍后再讲述具体细节。在本页的底部附近是缓冲模式设定, 允许你设置任何被关联的表单的缓冲模式。有两个选项:开放式行缓冲和开放式表缓冲。

此主题相关图片如下:

通常,你使用开放的表缓冲模式,除非你有特殊要求使用行缓冲模式。在这个例子中设置为开放式的表缓冲。最后,“break on error”控制CursorAdapter类怎样来处理错误。默认设置是类(class)自行捕获错误,并且允许你用aerror()函数捕获这些错误。选定这个设置,CursorAdapter类内部不管发生什么错误,vfp都会出现错误信息。也就是说,你需要使用ON ERROR命令或者‘类’的ERROR事件来排除不需要报错的情况。通常情况下不选这项设置,以便程序能处理任何发生的例外情况。
最后一页 (auto update) 配置如何更新源表。在通常情况下,选择“自动更新(auto-update)”和“更新所有字段(update all fields)”。

此主题相关图片如下:
这将使cursoradaper类对游标(cursor)中数据的任何改变自动建立适当的更新、插入、删除机制。然而,你必须选择游标中的主关键字段,以便这些机制(更新、删除、插入)唯一的识别源表中的记录。在本例中,CustomerID是关键字段。因此,需要在其前面大上‘对号’。其他的设置暂时保留默认值。具体设置办法在本文后面讲述。设置完cursoradaper后,点击“ok”按钮,回到数据环境设置。此时,你应该在左边列表框中能看到caCustomer类, 在右边看到细节。如果你想更改这个类,你可以随时用数据环境‘builder’更改,选择需要更改的CursorAdapter 类,然后点击builder按钮。
存取 VFP 数据
此时,你可以测试数据环境,看看是否能取回在 CursorAdapter 的指令中筛选的数据。 使用命令窗户,例示 DE 类而且唤起 OpenTables 方法:
lo = NewObject("deTest","Tests.vcx")
   ? lo.OpenTables()
   BROWSE  
一个特殊情况是,CursorAdapter的游标连接到其它对象,如果你毁坏了指向CursorAdapter类的对象,会丢失游标和其中的记录。这就是说,你必须确保CursorAdapter对象参数在你打算存取的关联游标的范围内
编辑 VFP 数据
现在, 让我们看看是否能够更新游标,并且把更新准确地发送到源表。在命令窗口测试以下命令:
REPLACE contactname WITH 'My Name Here'
   ?TABLEUPDATE()
   SELECT customers
   BROWSE
浏览customers别名,你会发现修改过的记录已经更新到源表中。 如果你在发送replace命令之前没有移动记录指针,客户ID中 'CACTU' 所在的记录被修改。 不管你修改哪条记录, 这证明 CursorAdapter 是能够被更新,而且更新能够准确地被发送到源表。
让我们打开你刚刚测试的数据环境类,这不只是一个练习—它是一个很棒的方法学习当你决定在数据环境以外建立自己的类的时候,该如何正确地配置一个 CursorAdapter 类。
虽然数据环境有一些属性改变和一个方法, 我们实际上对那些改变不感兴趣。看一下下拉列表框中的属性列表,选择 caCustomer 类,看看建立 CursorAdapter 类的时候需要进行的设置。 表 1 概述被builder和每个 PEM 所做改变。
所有的属性包含在 "see Init" ,通过INIT方法中的代码来设置这些属性。 那一段代码显示在list 1中。
在builder设置完以后,看看一些属性是怎样设置的,这是最好的学习方法。你可以在这里或者通过builder改变设置值。然而,如果在这里改变设置值,你需要冒破坏一些功能的风险,因为你在这里改变的属性有可能在builder里不会发生改变。
不管怎样,你能在 Init() 代码中看到 SelectCmd 属性是如何叙述的, 同样在 KeyFieldList , UpdatableFieldList 和 UpdateNameList中也可以看到。 特别注意 UpdateNameList 属性—这个属性列出游标的每一个字段以及源表中对应的字段(带表名称)。
当从头创建你自己的 CursorAdapter 类的时候,你可能想在这个列表中省略表名字。 然而,如果不使用精确的格式,你的更新将会失败, 但是没有错误提示。 在后面讲不通过builder建立一个类的时候我将再说这一点。
前面我说过 CursorAdapter使用本地的数据源的时候 , 本质上是一个替代了本地视图。如果你曾经用过本地视图,你可以发现类似的地方: 生成一个SQL select语句,定义哪些字段将被更新,设置关键字段,剩下的事情让 VFP 来做。 一旦游标中取回数据,可以使用 TableUpdate() 发送更新到源表,而且 VFP 自动地建立必要的update,insert,delete语句。
还是以上面的例子, 取消cCustomer 别名中contact字段被替换的数据。通过TableUpdate , VFP 自动地产生 (并提交)下列更新命令尝试更新:
UPDATE customers ;
     SET CONTACTNAME=ccustomer.contactname ;
     WHERE ;
      CUSTOMERID=OLDVAL('customerid','ccustomer');
     AND ;
      CONTACTNAME=OLDVAL('contactname','ccustomer')
VFP 能够根据CursorAdapter的KeyFieldList属性和部分UpdateNameList属性中的值产生where子句。它通过子句记录那些已经被改变或添加的记录,保证你不会更新那些别人已经更新过的记录。注意,这是因为我们在它的WhereType 属性中保留默认值 "key fields and any modified fields."
在本文中出现table1,list1等,可惜我当时没下载,现在已经找不到了,不过不会对理解本文有什么影响,下次发“cursoradapter类的错误处理"
错误处理
明显的,当用 CursorAdapter 更新数据时候 ,并不能完全如你所愿。你知道,多种原因可以导致TableUpdate 更新失败, 例如更新冲突或记录加锁失败。你必须对 CursorAdapter 类做特别处理来发现这些问题吗? 答案是,"视情况而定 ."
我们来建立一个简单的更新问题:给CursorAdapter尝试更新的一条记录加锁。如果类设计器仍然是开着的,关掉它。然后, 像前面做过的一样,用 NewObject 函数启动detest类,而且启动 OpenTables 方法。浏览游标以便你能看到数据,但是先不要改变任何东西。
现在打开 VFP 8 的第二个实例,这样就能够加锁记录。 在命令窗口中运行下列语句加锁你将尝试更新的记录:
OPEN DATABASE (HOME(2)+"Northwind\northwind.dbc")
   USE customers
   LOCATE FOR customerid = 'CACTU'
   ?RLOCK()
你应该能返回.T.,表明记录确实被 VFP 的这一个实例加锁。
回到 VFP 的第一个实例,在命令窗口中运行下面的代码:
REPLACE contactname WITH 'updated'
   SET REPROCESS TO 2 SECONDS
   ?TABLEUPDATE()
在这种情况, TableUpdate 返回.F.,表现记录锁阻止更新成功。 如果你用 AERROR() 捕获错误而且显示错误结果, 你会看到错误信息 "记录没加锁"。 这意味着如果你对设置缓冲的表直接操作而不是一个游标,你能用同样的办法处理错误。
不幸地,不是所有的预期错误都会这样表现出来。需要特别注意的是更新冲突,比如一个使用者企图覆盖其他人所作的修改。想看看这种行为的结果,在当前的 VFP 实例中(CursorAdapter 正在使用)运行下面的代码:
?TABLEREVERT(.T.)
   REPLACE contactname WITH 'client 1'
现在转变在到第二个例证并且发行下列指令:
CLOSE DATABASES all
   OPEN DATABASE (HOME(2) + "Northwind\northwind.dbc")
   USE customers
   LOCATE FOR customerid = 'CACTU'
   REPLACE contactname WITH 'client 2'
   BROWSE
返回第一个实例, 尝试用 TableUpdate 发送更新:
?TABLEUPDATE()
在这种情况, TableUpdate 错误地返回.T.,使你相信更新成功了! 然而, 事实上并没有成功,而且这可以被CursorAdapter的CursorRefresh() 方法证明, 用下列代码:
?TABLEREVERT(.T.)
   ?lo.caCustomer.CursorRefresh()
CursorRefresh 方法告诉 CursorAdapter 再运行 SelectCmd 里的语句,并且取回来自源表的最新数据。 对ContactName 字段的测试说明 CursorAdapter 根本没更新字段值!
解决这一个问题的最简单的方法要利用 CursorAdapter 的 AfterUpdate 方法。这一个方法在 TableUpdate 发送保存每条记录的请求后被执行。注意这个‘方法’只对当前记录有效。 如果记录是新的,或者记录已经被删除,会激活AfterInsert 或 AfterDelete 方法。
AfterUpdate 方法捕获一些参数, 包括最初的字段状态,是否被强制更改, 和作为更新的命令。 最后一个参数 , lResult,是我们这部分主题最重要的,因为它告诉我们更新过程是否成功。
使用的另一个重要角色解决更新冲突问题的是系统变量 _tally, 它告诉我们最后一次操作影响了多少记录。 因此,如果 lResult 放回成功的, 但是 _tally是零,那么说明没有记录被更新,那么你可以判定这种情况是一个更新冲突。
简单说,解决这一个问题的简单方法是把下列代码加入 CursorAdapter 类的 AfterUpdate 方法:
LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd, lResult
   IF lResult AND _TALLY = 0 THEN
     ERROR 1585 && update conflict
   ENDIF
有趣的是你在屏幕上看不到错误信息;错误信息被 TableUpdate “限制住了”,这就迫使你使用 AError 函数分析更新失败的原因。出现这样的现象是因为 BreakonError 属性保留了默认值 ,也就是错误不引起程序中断。如果你把这个属性设为“ture",那么"更新冲突" 错误信息就会出现,如果作了定义,你的ON ERROR句柄会被运行。
这一个更新冲突问题是为 CursorAdapter设计的,因为 VFP 8 没有自动发现这个问题方法。因此,当不用本地数据源的时候,这个代码( 或相似的代码)会用在你的CursorAdapter类设计中。
CursorAdapter with ODBC
现在你已经有了一定的基础, 让我们继续看看当后台数据库用 SQL server的时候怎样进行设置。我们将从使用 VFP 通过ODBC连接 SQL server上的 Northwind 数据库开始。同样, 让我们从头建立 CursorAdapter 以便可以看见类需要设置的各个方面。
首先,用下列指令在类设计器中建立一个新类:
CREATE CLASS caODBC OF tests as CursorAdapter
这里需要设置的最重要的属性是 DataSourceType 属性。 因为我们想通过ODBC连接 SQL server,将这一属性设置为ODBC。当使用这种连接方式的时候 , DataSource 属性需要有一个确切的连接句柄,可以用 SQLConnect 或 SQLConnectString 函数建立。 在任一情况,这些功能应该在 CursorAdapter 类的 Init 方法中使用下列代码:
LOCAL lcConnStr, lnConn
   ** string assumes trusted connection (integrated security)
   lcConnStr = "Driver=SQL Server;Server=(local);DATABASE=Northwind"
   lnConn = SQLSTRINGCONNECT(lcConnStr)
   IF lnConn > 0 THEN
     THIS.DATASOURCE = lnConn
   ELSE
     ** unable to connect
   ENDIF
连接字符串假定你使用可信赖的关联到 SQL server;如果你使用安全的连接到 SQL server,把 "uid"= 和 "pwd"= 串加入连接句柄指定使用者名称和密码。 任何一种情况下,返回的是连接柄 , 或错误发生时的否定值。 这一个连接柄被指定为 DataSource 属性以便 CursorAdapter 知道该到哪里声明。
下一个步骤是建立 SelectCmd 以便 CursorAdapter 知道需要从数据源取回什么数据。还有一个最好的放置地方是 Init 方法, 因为属性表限制字符串的长度。 把下列代码加入 Init 方法中,设定 DataSource 属性代码之后, 取回公司名字中以 "C" 开头的客户列表:
This.SelectCmd = "SELECT " + ;
     "CustomerID, CompanyName, ContactName, " + ;
     "Address, City, Region, Country " + ;
     "FROM Customers WHERE CompanyName LIKE 'C%'"
然后,你应该告诉 CursorAdapter 通过调用CursorFill 方法填充被关联的游标。 你可以省略这一个调用,并且手动从类之外调用, 或放在 Init 方法中,以便它能够自动填充游标。在Init 方法中设置 SelectCmd 之后,可以用 This.CursorFill()调用。
最后,你应该在类的Destroy方法中加上一些代码,以便在对象从内存中释放以后减少到服务器的连接。 没有这些代码的话,每一个新的实例将会产生一个到服务器的新连接, 并且不会释放它:
IF this.DataSource > 0 THEN
     SQLDISCONNECT(this.DataSource)
ENDIF
藉由这些变化,你拥有一个实用的 CursorAdapter 类,它能够生成一个只读的游标。在允许更新之前,测试一下类,确保可用,并能准确取回数据。用下列代码测试:
lo = NEWOBJECT("caODBC","tests")
   BROWSE
注意,你不需要像数据环境那样调用 OpenTables 方法。这是因为你把 CursorFill 方法直接加入了 Init 方法,使类在实例化之后自动地填充游标。
Updating ODBC Data(更新ODBC数据)
为了使这个类可更新,你必须正确地给Tables,  KeyFieldList , UpdatableFieldList 和 UpdateNameList 属性赋值。 并且设定 AllowInsert , AllowUpdate 和 AllowDelete 属性为true, 确定自动更新特征被激活。 再一次强调,最好的办法是在Init 方法中用代码设置这些属性。 Init 方法一些代码在list2 中。
在关闭类设计器之前,你可能想将 BufferModeOverride 属性换成 "5",“开放的表缓冲”以便移动记录指针的时候 , 自动更新不发生。
测试 CursorAdapter 的更新能力,把它作为一个实例,浏览游标,作一个更改, 然后提交 TableUpdate。 确定所作的更改被响应,调用 CursorAdapter 的 CursorRefresh 方法,再浏览一次。
处理ODBC错误(Handling ODBC Errors)象使用本地 CursorAdapter一样 ,大多数的错误处理是按照 "传统的"方法—测试 TableUpdate 返回的值,如果失败,使用 AError 来判断原因。 不幸的是,更新冲突的检测也是ODBC连接类型 CursorAdapter 的一个问题。
本地 CursorAdapter 错误的解决办法是在 AfterUpdate 方法中设置错误处理代码, 但这种方法对ODBC型 CursorAdapter 是无效的,因为更新失败的时候,我们不是要处理VFP错误,而是ODBC错误。 因此,最好答案是使用一个存储过程,或在更新句柄中增加一些代码发送到服务器。
回想一下,为本地的 CursorAdapter 更新错误的解决办法是检查 _TALLY,看看是否有记录被更新。为ODBC连接的解决办法与本地CursorAdapter是相似的,但是我们不能使用 _TALLY,因为在远程数据检测上它是不可靠的。 我们可以使用SQL server的@@Rowcount 系统函数来判定记录是否被更新。
如果你要写一个 T- SQL 批量更新语句来更新一笔记录,你应该象下面这样写:
--@custID and @oldContact set by earlier code or parameters
   UPDATE customers
     SET ContactName = @newContact
    WHERE CustomerID = @custID
       AND ContactName = @oldContact
   IF @@ROWCOUNT = 0
     RAISERROR('Update failed.',16,1)
RaisError T- SQL 函数使 VFP 接收一个ODBC错误 (1526 号),  在第一个参数里写错误信息.(另外二个参数指出严重和错误的状态) 在这种情况下, 当@@Rowcount=0的时候RaisError 被调用, 表明早先的 T- SQL 语句没影响任何的记录。
所有讨论的这一些表明你可以使用 CursorAdapter 的 BeforeUpdate 方法来描述发送到服务器用来更新的语句。 BeforeUpdate 方法接受五个参数, 有趣的是最后的二个 (cUpdateInsertCmd 和 cDeleteCmd) 建立了关联(注:原文the last two (cUpdateInsertCmd and cDeleteCmd) are interesting in that they are passed by reference. )。 在它们被送去数据源之前 , 这允许你修改指令。
在我们的例子中,我们使用这一个方法增加一个对@@Rowcount的测试然后调用 RaisError 。把下列代码写入 BeforeUpdate :
LPARAMETERS cFldState, lForce, nUpdateType, ;   
     cUpdateInsertCmd, cDeleteCmd
   IF nUpdateType = 1 THEN
     cUpdateInsertCmd = cUpdateInsertCmd + ;
       " if @@ROWCOUNT = 0 "+ ;
       "RAISERROR('Update Failed due to update " + ;    
         "conflict.',16,1)"
   ENDIF
现在,为发送到后台数据库的每一行记录,用这段代码测试行是否被更新。 如果没更新, VFP 将会接收到错误,TableUpdate 将会失败,而且 AError 将会显示1526号错误,并显示预设的错误信息。
存在二个的问题。 首先,这是特别为 SQL server设置的;对其他的ODBC数据源 ( 比如ORACLE) ,这一段代码无效。 其次,这个错误信息是指一类错误,总是产生相同的 VFP 错误号, 而且在 VFP 中建立正确的错误处理句柄有一点困难。这一个问题可以通过在SQL SERVER服务器上建立通用的错误信息,每条信息对应着自己的唯一错误号码。
另外一个更好的解决方法是使用存储过程来执行更新,代替用VFP建立一个查询。 当然,采用存储过程的不利因素是不能使用VFP 的自动更新句柄来更新数据。
参数化设置   Parameterization
通过学习,你已经知道如何为 CursorAdapter 类的方法、属性添加命令。 基本上,在类中使用的每个事件(event)都有一组before和after方法, 比如 BeforeUpdate 和 AfterUpdate 。然而,没有 BeforeSelect 或 AfterSelect-替代它们叫做 BeforeCursorFill 和 AfterCursorFill,因为游标是用 SelectCmd 的结果来填充的。
BeforeCursorFill 方法接受三个参数, 而且返回 Boolean 值。 第一个参数 , lUseCursorSchema, 叙述 CursorSchema 属性是否控制游标的结构。 第二个参数 , lNoDataOnLoad,与视图的 NODATA 子句类似,只取回表结构,但是没有从数据源取回数据。
对于现在的讨论 , 第三参数 , cSelectCmd,是最有意思的。 我们已经提到过它 (象 BeforeUpdate 的 cUpdateInsertCmd 参数) ,先使用 SelectCmd 的当前设置。然而,如果你改变这一个参数的值,它不改变 SelectCmd 属性的值;取而代之的是,它的值改为被传给数据源的语句 。
举例来说, 假如你已经把 CursorAdapter 对象的 SelectCmd 设定为下列代码:
SELECT CustomerID, CompanyName, ContactName, City,
   Region, Country FROM Customers
在呼叫 CursorAdapter 的 CursorFill 方法之前, BeforeCursorFill 方法的 cSelectCmd 参数会包含这个语句。 假如你在这一个方法中用下列代码:
cSelectCmd = cSelectCmd + ;
     " WHERE CompanyName LIKE '" + ;
     this.cCompanyName + "%'"
这就导致实际的select命令总是包含如上面代码描述的where子句和 this.cCompanyName(自定义属性) 的当前值.因为它不改变 SelectCmd 的初始值, 你不必用任何特别的代码来确定在select命令中是否包含两个where子句。
参数设置第二部分
如果你以前用过视图,或 SQL pass through,那么你可能熟悉在参数前使用 "?" 字符。 这一个用法也适用于 CursorAdapter 。 下面的代码例子告诉你如何在 CursorAdapter 的 SelectCmd 属性中使用参数:
This.SelectCmd = "SELECT * FROM Customers " + ;
     " WHERE CompanyName like ?lcMyVar "
   lcMyVar = 'C%'
   This.CursorFill()
最要紧的是确定变量 "lcMyVar" 在 CursorFill 方法被请求之前被定义。否则的话,VFP会提示找不到值,使用者会无所适从。
你可以使用 CursorAdapter 的属性作为参数,用来替代本地的变量。这样做有这样的优点:只要对象存在,属性也会存在,而且你可以通过使用access/assign方法来确定分配的值符合规则。
使用存储过程
在上面说过, 使用储存过程是一个突破错误处理限制的好办法。让我们逐步探究在ODBC CursorAdapter中使用存储过程的方法。这样我们就能感觉出手动处理 CursorAdapter 类的更新错误是多么复杂。
这一段是关于通过调用数据源的存储过程来代替自动执行的update、insert和delete命令。 这意味着你必须处理 UpdateCmd , InsertCmd 和 DeleteCmd 属性, 而且假设 SQL server上的 Northwind 数据库已经建立存储过程来提供这些功能。  
例子, 让我们看一看一个简单存储过程的完整代码,你可以用它更新 Northwind 数据库的customer表的 ContactName 字段:
--T-SQL code, not VFP
   CREATE PROCEDURE UpdateCustomerContact (
      @CustomerID nchar (5),
      @ContactName nvarchar (30),
         @oldContact nvarchar (30)
     )
   AS
     IF @CustomeriD IS NULL
       RAISERROR('CustomerID is a required parameter',16,1)
     ELSE
       UPDATE Customers
          SET ContactName = @contactName
        WHERE CustomerID  = @customerID
          AND ContactName = @oldContact
为了节约空间,这一个存储过程没有包含全部的错误处理代码。 不管这个,已经有足够的代码来说明怎样在 CursorAdapter 类中执行更新。
幸运地, 建立 UpdateCustomerContact 存储过程作为更新命令,可以取代 BeforeUpdate 方法,用下面的代码:
LPARAMETERS cFldState, lForce, nUpdateType, ;
     cUpdateInsertCmd, cDeleteCmd
   cUpdateInsertCmd = ;
     "EXECUTE UpdateCustomerContact '" + ;
     EVALUATE(this.Alias+".CustomerID") + "','" +;
     ALLTRIM(EVALUATE(this.Alias+'.ContactName'))+ ;
       "','" + ;
     OLDVAL('contactname',this.Alias)+"'"
这里,代码放在 cUpdateInsertCmd 参数里,覆盖了默认的update命令。我使用了evaluate函数,是为了cursor的名字是动态的,说明cursor的名字可以很容易被改变,但是代码不变。还有,我使用了OLDVAL 函数取回 ContactName 字段被修改前的值。如果在存储过程的where子句里需要旧的数据,那么这个函数是必需的。这有点像自动产生update的情况。
记住,在记录被实际更新之前 ,通过TableUpdate自动呼叫 BeforeUpdate 方法。 因此, 无论UpdateCmd当前的值是什么,这个方法程序(指UpdateCmd)被禁用,而且总是使用存储过程。
注意,你也可以使用已经讨论过的参数设置方法,使用 BeforeUpdate 方法。 这就要求你为 CursorAdapter 提供 UpdateCmd设置,但是, 不要在参数里用固定的代码,要用变量或者属性,并在它们前面加上“?”。
在这里需要注意的重要的一点是 cUpdateInsertCmd( 或对象的 UpdateCmd属性) 不能返回任何值。 进一步说,如果你从存储过程返回一个值,它就没有地方 "去" ,那么这个值就会永远丢失。因此,在存储过程中添加一个RaisError呼叫,以便在更新过程中发生任何错误( 例如不正确的参数或一个更新冲突) 的时候,你的代码能够作出反应,这样做是非常重要的。你可以通过测试 TableUpdate 返回值来捕获错误 ,或者用 AError 方法, 然后分析错误。
相似的代码也应该写进 BeforeInsert 和 BeforeDelete 方法,以便它们也调用存储过程,而不是调用设置好的“查询”。 为了节约空间,我将留下代码当做 "读者的练习."
CursorAdapter with OLE DB
我们的下一个任务是看看如何通过 CursorAdapter 类使用OLE DB(对象连接与嵌入), 并且把它同我们用过的native和ODBC作一比较。OLE DB技术比ODBC有更多的处理能力, 而且能够连接的数据源类型比ODBC多。 CursorAdapter 通过嵌入ADO对象使用OLE DB,这是OLE DB技术标准的 COM 封装。 VFP 会自动地把ADO记录集  转换成一个 VFP 游标, 以及处理更新, 正如在早先的例子中讲到的一样。
第一件要做的事,当然还是建立一个新的 CursorAdapter 类,我们用代码建立一个。
开始建立一个新的程序,取名字 caADO.prg, 把下列代码添加进去:
PUBLIC goCAADO as CursorAdapter
   goCAADO = CREATEOBJECT('caADO')
   BROWSE
   DEFINE CLASS caADO AS CursorAdapter
     oConn = NULL
     oRS = NULL
     Alias = "cCustADO"
     DataSourceType = "ADO"
     SelectCmd = "SELECT " + ;
       "CustomerID, CompanyName, ContactName, "+;
       "ContactTitle, Address, City, Country "+;
       "FROM Customers WHERE Customerid LIKE 'C%'"
     FUNCTION Init()    
       This.DataSource = this.oRS
       This.oRS.ActiveConnection = this.oConn
       This.CursorFill()
     ENDFUNC
   ENDDEFINE
在这段代码中,我们将 DataSourceType 设为ADO而且把我们对Customers的查询加入 SelectCmd 。 当 DataSourceType 是ADO的时候, DataSource 属性必须包含有效的 RecordSet 或命令对象, 这依赖于你如何使用 CursorAdapter。 如果你不使用参数化查询 (就象前面例子中讲到的用 "?")那么你可以用记录集( RecordSet );否则,你必须使用命令对象,因为ADO已经代替了参数选择。 你的查询中的任何参数在命令对象中自动地被处理。
在这种情况下,我们使用 RecordSet 对象 , 但是应该注意我们必须提供一个“连接”对象。 在这两种情形中,我使用Access方法建立了关于这些对象的参考。 list3 中是Access方法的代码。
两个Access方法首先检查对象是否已经被建立。 如果没有,那么就继续创建对象。在使用 RecordSet 的情形,你只需要建立对象,剩下的由 CursorAdapter 来做。 用“连接”对象的情况下, 你必须提供连接字符串(连接句柄)并且打开连接,因为CursorAdapter 不为你打开连接。这是因为“连接”不是 CursorAdapter 的一个属性, 而是 RecordSet 对象的一个属性。
下一次发:用OLE DB更新
用OLE  DB更新
如果不另外设定几个属性,这么简单的 CursorAdapter 是没有更新能力的。 把下面的代码放在定义类(define)的代码中,在init方法之前运行,将会允许自动更新:
KeyFieldList = "CustomerID"
   UpdatableFieldList = ;
     "CompanyName, ContactName, ContactTitle, "+ ;
     "Address, City, Country"
   UpdateNameList = ;
     "CustomerID Customers.CustomerID, " + ;
     "CompanyName Customers.CompanyName, " + ;
     "ContactName Customers.ContactName, "+;
     "ContactTitle Customers.ContactTitle, " + ;
     "Address Customers.Address, "+;
     "City Customers.City, Country Customers.Country"
   Tables = "Customers"
然而, RecordSet 会建立它的默认的 CursorLocation 和 CursorType 属性。 如果不改变这些属性设置, RecordSet 最初是只读的,因此,你需要按以下方法修正 oRS_Access 方法:
FUNCTION oRS_Access() as ADODB.RecordSet
     LOCAL loRS as ADODB.RecordSet
     IF VARTYPE(this.oRS)<>"O" THEN
       this.oRS = NULL
       loRS = NEWOBJECT("ADODB.Recordset")
       IF VARTYPE(loRS)="O" THEN
         loRS.CursorType= 3  && adOpenStatic
         loRS.CursorLocation = 3  && adUseClient
         loRS.LockType= 3  && adLockOptimistic
         this.oRS = loRS
       ENDIF
     ENDIF
     RETURN this.oRS
   ENDFUNC
为 RecordSet 加上这些代码以后, CursorAdapter 就能处理自动更新了。
下一次发cursoradapter with XML
CursorAdapter with XML
最后, 让我们建立使用XML作为数据源的 CursorAdapter。 这一节很有趣,因为XML文本不像通常的数据源。 同样,当数据源设置为XML的时候, CursorAdapter 不会自动建立 SQL 更新, 插入或删除命令。因此,这种类型的 CursorAdapter 需要大量的代码接收和更新数据。
在这一个例子中,我将使用 SQL server 2000 的 SQLXML 提供XML文本。 同时,因为SQLXML支持通过XML更新,我们将花一点时间写必要的代码来执行更新。 假设你已经配置了SQLXML,允许HTTP数据存取Northwind 数据库,而且允许用 UpdateGrams 对数据库进行更新。
在这里,我在 IIS 上面设置使用一个被称为 "nwind" 的虚拟目录存放 HTTP 数据。 因此,我的全部例子将会包含这个网址:
http://localhost/nwind,经由 IIS 存取 SQLXML。
让我们开始建立一个新的程序,程序名字为caXML.prg,用下列代码(创建一个类):
PUBLIC oCAXML as CursorAdapter
   SET MULTILOCKS ON && need for table buffering
   oCAXML = CREATEOBJECT('xcXML')
   BROWSE NOWAIT
   DEFINE CLASS xcXML AS CursorAdapter
     DataSourceType = "XML"
     Alias = "xmlCursor"
     UpdateCmdDataSourceType = "XML"
     InsertCmdDataSourceType = "XML"
     DeleteCmdDataSourceType = "XML"
     BufferModeOverride = 5
     *custom properties
     oXMLHTTP = NULL
     oXMLDOM = NULL
     cServer = "localhost"
     cVDir = "nwind"
   ENDDEFINE
不同于通常的 DataSourceType 和Alias(别名)属性设置,这是我们第一次看见 xxxCmdDataSourceType 属性。 因为这是一个XML类型的 CursorAdapter,如果你想让它用来更新源数据,这些属性就必须进行设置 。推荐在这个类中使用 oXMLHTTP 和 oXMLDOM 属性,将会在下面做详细说明。
接收XML数据
在考虑 CursorAdapter 的更新能力之前,我们先研究怎样从SQLXML 服务器接收文本。 首先,一个简单的select命令不能工作,我们必须建立通用的 SelectCmd 。 这在 Init 方法中很容易做到,同时,在INIT中调用 CursorFill 方法,如以下代码:
FUNCTION INIT() as Boolean
     LOCAL llRetVal, lcMsg, laErr[1]
     this.SelectCmd = "this.GetXml()"
     llRetVal = THIS.CursorFill()
     IF NOT llRetVal THEN
       AERROR(laErr)
       lcMsg = "Cursor was not filled!"
       IF NOT EMPTY(laErr[2]) THEN
         lcMsg = lcMsg + CHR(13) + laErr[2]
       ENDIF
       MESSAGEBOX(lcMsg,16,"XMLCursorAdapter Test")
     ENDIF
     RETURN llRetVal
   ENDFUNC  
这一段代码建立 SelectCmd 作为本地用的方法,代替 SQL 命令。 以前的例子中没有这样做过,对任何 CursorAdapter 类,不管什么类型,都是合法的。然而,当你使用一个本地的方法作为 SelectCmd 的时候,你必须也提供代码用来更新,插入和删除, 因为如果不是一条SQL语句的话, VFP 是不会自动处理的。
当我们在 Init() 中使用 CursorFill 的时候,GetXML 方法被调用。 数据源设定为XML的时候,GetXML 方法必须返回只包含一个表的最终文本。 如果它包含多个表,你会得到料想不到的结果。 GetXML 方法在list4 中列示。
GetXML开始于提交一个 MSXML2.XMLHTTP COM 对象。 这个对象处理所有 HTTP 通信,包括发送请求到服务器并且返回结果。 你可以看到,oXMLHTTP 实例对象是被设计好的Access方法控制的,这样设计是为了防止连续地创建和破坏 COM 服务器。
然后,你可以看到我们的典型的SELECT描述, 除了LIKE子句有点稍稍不同以外。 HTTP 需要我们当十六位百分比字符显示到达25%的时候退出显示(指进度显示), 在 SQL server接收查询之前 , 这个值将会变成单一的百分比符号。
然后,代码用具体的查询要求来设置URL,并且通过HTTP发送URL到SQL SERVER服务器。SQL SERVER接收到这个查询,并且处理它,以XML方式返回值。这是因为我们已经在“查询”中包含了FOR XML子句。在这个例子中,XML的根元素叫做 "results" 。你从查询字符串中可以看出。这是按照你的习惯配置的。
此时, lcRetXML 包含一个来自 SQL server的XML数据流。 既然 GetXML 方法被 VFP 作为 SelectCmd 调用,你可以从 GetXML 方法中返回这个变量的内容,而且 VFP 将会把数据流转换成一个 VFP 游标。 你可以通过执行 caXML 程序测试。 会出现一个包含返回的XML文本内容的浏览窗户。 使用调试工具一步一步查看  GetXML 方法,可以看到在被转换到一个 VFP 游标并释放之前 , lcRetXML 变量的值是XML文本。
更新XML数据
下一个步骤是决定该如何使这个游标可更新,以便所作的更改能被发送回 SQLXML 服务器。 SQLXML 能使用一个特别的XML文本,即 UpdateGram, 利用它直接把更新发送到数据库。 在 VFP7 中,可以用 XMLUpdateGram 函数建立这个文本。 用VFP 8 的 CursorAdapter , UpdateGram 属性会自动建立。
第一个步骤是设置 updatable 属性并且建立一个更新指令。 在类定义的开始设置这些属性,并在CursorAdapter的init事件中增加一行代码,提供调用更新命令的方法。
KeyFieldList = 'customerid'
   Tables = 'customers'
   UpdatableFieldList = ;
     "companyname, contactname, contacttitle, "+;
     "address, city, country "
   UpdateNameList= ;
     "customerid customers.customerid, " + ;
     "companyname customers.companyname, " + ;
     "contactname customers.contactname, " + ;
     "contacttitle customers.contacttitle, " + ;
     "address customers.address, " + ;
     "city customers.city, country customers.country"  
   FUNCTION INIT() as Boolean
     LOCAL llRetVal, lcMsg, laErr[1]
     this.UpdateCmd = "this.UpdateXML()"
     this.SelectCmd = "this.GetXML()"
     ** balance of code skipped...
注意,我们已经在init方法中替换了属性表中的 UpdateCmd 和 SelectCmd 设置,实际上最终结果是一样的。 不管怎样, 这一段代码的第一部份现在看起来很熟悉,在这里我们设置了 KeyFieldList ,table, UpdatableFieldList 和 UpdateNameList 属性。 没有这些属性设置,就不可能创建 UpdateGram 。
然后,我们建立 UpdateXML 方法作为 CursorAdapter 的 UpdateCmd 。 没有参数传递给 UpdateXML 方法,所以,所有决定更新的工作必须这个方法里处理。还有,因为XML 类型的 CursorAdapter 没有默认的更新机制, 你必须写代码把更新传递给XML数据源。
在这一段代码中,使用 XMLHTTP 对象传递更新到服务器。 通过LoadXML方法加载 UpdateGram 属性的内容到 XMLDOM( 在ACCESS方法中) 之内,打开到服务器的连接,设定请求的内容为XML, 然后发送 XMLDOM。所有的结果通过 XMLHTTP 对象的 ResponseText 属性返回,接着装载到 XMLDOM 中,并且分析错误信息。
如果没有提示错误,更新已经成功,而且过程结束。然而,如果有错误, 就会产生符合语法的错误信息文本,并且包含在一个自定义的ERROR函数中,这样 TableUpdate 函数就能找到更新失败的原因。 如果没有这些代码, 即使有问题的时候,TableUpdate 总是返回成功信息。
测试一下这段代码,运行 caXML 程序,在游标中更改一个字段, 然后在命令窗口中发出 TableUpdate。 如果 TableUpdate 成功, 你应该能在服务器上看到更新结果。如果 TableUpdate 失败,你需要用 AError 函数接收 SQL server产生的错误信息。
如果你对 UpdateGram 的内容感到好奇, 你可以一步步查看类的 UpdateXML 方法,并且查看 UpdateGram 属性的内容。 然而,如果你不到有关数据变化的方法中 (如 UpdateCmd , InsertCmd 或 DeleteCmd 属性内容) ,你也不能完全弄明白 UpdateGram 属性的内容。
list6显示当ContactName字段的客户ID被更改为‘CACTU' 后UpdateGram的内容。
正如你看到的,SQLXML 能够读取这个文本并且能够很容易地建立一个Update-SQL 语句, 然后发送到 SQL server。 updg:sync(同步更新)元素使更新立即发生;因此,如果你有多个表需要更新,你可以把它们关联起来放到一个 UpdateGram中, 确定把他们封装进这个元素,执行一次就可以全部更新。
结束语
在这一篇文章中,我们涉及了许多方面,讲了新的 CursorAdapter 类的四种表现形式。 你已经学到了如何通过 DataEnvironment 、 CursorAdapter buileder、类设计器、和PRG建立 CursorAdapter 。 你也已经了解了建立本地、ODBC、OLE DB或XML型 CursorAdapter 类的基本方法, 并且怎样使这些类可更新。
下一个步骤是考虑如何把这些类应用到你的程序中。 对我来说,觉得 CursorAdapter 应用到任何程序的 UI 层中效果很好,同时应用于需要大量过程代码的许多商业开发。CursorAdapter并不是一个最好的选择对象用于层来传递数据。因为它把所有的来自数据源的数据转化成一个不便于携带的游标。
然而,在一个使用 CursorAdapter 类进行商业开发的方案中,它能接收来自数据源的数据, 然后用标准的 VFP 命令和函数处理数据,这是因为它产生的是一个 VFP 游标。 数据可以被转换为更一个较适当的类型,例如XML。
CursorAdapter 的另一个有利条件是通用的 OOP 接口,不管需要存取的数据是什么类型。 甚至用XML,需要大量代码来使类可更新,我们仍然可以用使用 CursorFill取回数据,用 TableUpdate 更新数据, 而且用 AError 返回错误, 像使用其他类型的 CursorAdapter一样。
基于一个事先的考虑或者计划,你想建立一个可以重复使用的CursorAdapter类。 这些类可以在程序之间重复使用或者在同一程序内部混合使用,来形成一种你的程序处理数据的标准方法。
有网友问使用cursoradapter是否还需要tableupdate(),下面索性再发一篇文章:
如何通过 CursorAdapter 对象使用 TableUpdate 函数更新数据
这一篇文章适用于:vfp 8.0
摘要:
这一篇文章讨论下列各项主题:
  1、TableUpdate 交互地使用 CursorAdapter 类更新后端数据。
  2、使用vfp 8.0 的新 CursorAdapter 类通过TableUpdate 函数更新数据的时候如何处理更新冲突。
较多的信息:
可以通过vfp 8.0 的 CursorAdapter 类取回来自本地的或远程的数据源的数据。 默认情况下,CursorAdapter 产生的游标不更新后端数据。 为了要更新后端数据,必须设定 CursorAdapter 的下列各项属性值:
  InsertCmd
  UpdateCmd
  DeleteCmd
如果你不设定这些属性值,你能通过设定下列各项 CursorAdapter 属性值自动地产生后端 SQL 更新指令:
  tables
  KeyFieldList
  UpdatableFieldList
  UpdateNameList
  SendUpdates
处理更新冲突
当你使用 CursorAdapter更新后端数据的时候, TableUpdate 函数返回 CursorAdapter 游标的更新结果。 如果一个更新冲突发生,后端数据的更新不可能成功。 然而, TableUpdate 可能仍然返回更新成功,因为在 CursorAdapter 游标中的数据被更新。  
更新冲突是指,使用者试图编辑一条取回后已经发生改变的记录。 下列各项是更新冲突能发生的情况:
1. User1 打开由客户表建立的一个游标(cursor)。
2. User2 更新第一条记录而且提交更新。
3. User1 更新第一条记录(使用 TableUpdate() 函数).
此时, User1 有一个更新冲突:  User1 正在尝试更新的后端记录在被取回之后已经发生改变。
DataSourceType 代码示例:
下列示例代码使用vfp 8.0 的native DataSourceType 更新 SQL server Northwind 数据库的一笔记录。 查证是否记录被更新,在更新命令中附加下列代码:
CRLF+[EXECSCRIPT+(" if _tally=0"+CHR(10)"+error ('update conflit')"+CHR(10)+"ENDIF")]
在这种情况下,Tableupdate() 返回 (.F。 ) 而且允许你处理更新失败。
#DEFINE CRLF CHR(13)+CHR(10)
Local loCursor,ovfp
CLEAR

ON ERROR

Set Exclusive Off
Close Databases All
Set Multilocks On
loCursor = Createobject('CA')
* Load loCursor with the data specified in SelectCmd and display error message if error occurs.
loCursor.CursorFill()
GO top
* Display the value of the company name before update.
? "Before:",companyname
?
ovfp=Createobject("visualfoxpro.application.8")
ovfp.DoCmd("set exclusive off")
ovfp.DoCmd("update (_samples+'\northwind\customers') set companyname='Alfreds Futterkisted' where customerid='ALFKI'")
GO top
* Update the data in the cursor.
replace companyname WITH 'Alfreds Futterkiste'
* Update the back end.
retval=TABLEUPDATE(0,.F.,locursor.alias)
Messagebox("Tableupdate="+Transform(retval))

* If update conflict occurs, display the error.
if(retval=.F.)
LOCAL ARRAY errors(1)
AERROR(errors)
* Displays the errors.
IF "Update conflict"$errors[2]
  MESSAGEBOX("Update Conflict-reverting changes")
  =TABLEREVERT(.T.,locursor.alias)
ENDIF
endif
* Refresh the Cursor to get the updated data.
loCursor.CursorRefresh()  && Get the data again to be sure
GO top
* Display the value of the company name after update.
?
? "After:",companyname

Define Class CA As CursorAdapter
Alias = 'test1'
DataSourceType = 'NATIVE'
SelectCmd = 'select * from (_samples+"\northwind\customers")'
Tables = 'Customers'
KeyFieldList = "customerid"
UpdatableFieldList = "companyname"
UpdateNameList = "customerid customers.customerid,companyname customers.companyname"
WhereType= 3
* This is a custom property, that is added to handle update conflicts. It does not do
* anything by itself. It is added below to the automatically-generated UpdateInsertCmd to
* test whether anything was actually updated.
ConflictCheckCmd =CRLF+[EXECSCRIPT("IF _tally=0" + CHR(10) + "ERROR('Update conflict')" + CHR(10) + "ENDIF")]

Procedure AfterUpdate
  Lparameters cFldState, lForce, nUpdateType, UpdateInsertCmd, DeleteCmd, lResult
  * To see why it will fail on the back end, look at the UpdateInsertCmd that is used
  ? "Update Command sent="+UpdateInsertCmd
* Swap the actual values in the command to see what occurred.
  UpdateInsertCmd=Strtran(UpdateInsertCmd,[OLDVAL('customerid','test1')],Oldval('customerid','test1'))
  UpdateInsertCmd=Strtran(UpdateInsertCmd,[OLDVAL('companyname','test1')],Oldval('companyname','test1'))
  UpdateInsertCmd=Strtran(UpdateInsertCmd,[test1.companyname],test1.companyname)
  ? "With the OLDVAL() and test1.companyname evaluated the update statement is :"+UpdateInsertCmd
  * Check tally.
  ? "Tally="+Transform(_Tally)

Procedure BeforeUpdate
  Lparameters cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd
  cUpdateInsertCmd=cUpdateInsertCmd+this.ConflictCheckCmd

ENDDEFINE
使用SQL server DataSourceType的示例代码:
下列代码使用 SQL server DataSourceType 更新 SQL server Northwind 示例数据库的一笔记录。 查证记录是否被更新,把下列代码加入更新指令:
IF @@ROWCOUNT=0 RAISERROR (' Update
    conflict.', 16, 1)
在这种情况下,Tableupdate() 返回 (.F。 ) 而且允许你处理更新失败。
LOCAL loCursor,ovfp,nhnd,lsuccess
CLEAR
SET EXCLUSIVE OFF
CLOSE DATABASES ALL
SET MULTILOCKS ON
loCursor = CREATEOBJECT('CA')
* Load loCursor with the data specified in SelectCmd and display error message if error occurs.
IF !loCursor.CursorFill()
=AERROR(lar)
MESSAGEBOX(lar[2])
ENDIF

* Display the value of the company name before update.
? "Company Name Before Update:",companyname
?
* Create a connection handle for SQL Server so you can set up the update conflict.
nhnd=SQLSTRINGCONNECT([Driver=SQL Server; SERVER=<SQL SERVER NAME>; DATABASE=NORTHWIND])
=SQLEXEC(nhnd,[update customers set companyname='Alfreds Futterkiste' where customerid='ALFKI'])
=SQLDISCONNECT(nhnd)
* Now make a change to the local data, and then try to update it.
GO TOP
REPLACE companyname WITH 'Alfreds Futterkisted'
lsuccess=TABLEUPDATE(0,.F.,locursor.alias)
Messagebox("Tableupdate="+Transform(lsuccess))
* Error handling function. Displaying the error message if update conflict occurs.
IF !lsuccess
=AERROR(lar)
IF "Update conflict"$lar[2]
  MESSAGEBOX("Update conflict!-Reverting changes")
  =TABLEREVERT(.f.,locursor.alias)
ENDIF
ENDIF  
* Get the current data from the CursorAdapter.
loCursor.CursorRefresh()  
GO TOP
* Displaying the value of the company name after update.
?
?"Company Name After Update:", companyname
DEFINE CLASS CA AS CursorAdapter
Alias = 'test1'
SelectCmd = 'select * from customers'
Tables = 'Customers'
KeyFieldList = "Customerid"
UpdatableFieldList = "companyname"
UpdateNameList = "customerid customers.customerid,companyname customers.companyname"
WhereType= 3 && Key and modified

* This is a custom property that is added to handle update conflicts. It does not do
    * anything by itself. It is added below to the automatically-generated UpdateInsertCmd to
    * test whether anything was actually updated.
ConflictCheckCmd =";IF @@ROWCOUNT=0 RAISERROR (' Update conflict.', 16, 1)"


* Initializing the connectivity to Data source (SQL Server) by using ODBC driver.
PROCEDURE init
  WITH THIS
   .DataSourceType = 'ODBC'
   .DataSource=SQLSTRINGCONNECT([Driver=SQL Server; SERVER=<SQL SERVER NAME>; DATABASE=NORTHWIND])
  ENDWITH
ENDPROC

PROCEDURE BeforeUpdate
  LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd
  
  ? "Entering BeforeUpdate()"
  
  cUpdateInsertCmd=cUpdateInsertCmd + THIS.ConflictCheckCmd
ENDPROC


PROCEDURE AfterUpdate
  LPARAMETERS cFldState, lForce, nUpdateType, UpdateInsertCmd, DeleteCmd, lResult
  * To see why it will fail on the back-end, look at the UpdateInsertCmd that is used.
  ? "Update Command sent="+UpdateInsertCmd
  * Swap the actual values in the command to see what occurred.
  lcActualCmd =Strtran(UpdateInsertCmd,[OLDVAL('customerid','test1')],Oldval('customerid','test1'))
  lcActualCmd =Strtran(UpdateInsertCmd,[OLDVAL('companyname','test1')],Oldval('companyname','test1'))
  lcActualCmd =Strtran(UpdateInsertCmd,[test1.companyname],test1.companyname)
  ? "With the OLDVAL() and test1.companyname evaluated the update statement is :"+UpdateInsertCmd
  ?
  ? "Leaving AfterUpdate()"
ENDPROC

* Destroying the connection.
PROCEDURE destroy
  =SQLDISCONNECT(THIS.DataSource)
ENDDEFINE
注意:这种更新方式(如这一段代码所示的)不支持批量更新。 (例如,cursoradapter.batchupdatcount>1) 。使用批量更新的时候,下列各项事件不动作:
  BeforeInsert
  AfterInsert
  BeforeUpdate
  AfterUpdate
  BeforeDelete
  AfterDelete
叁考
更多的信息,vfp8帮助中的下列各项主题。
“CursorAdapter Object Properties, Methods, and Events"
"Data Access Management Using CursorAdapters"
"Detecting and Resolving Conflicts"
"Locking Data"
"Management of Updates with Views"
"Programming for Shared Access"
vfp8自带的cursoradapter帮助:
用 Visual FoxPro,可以用cursor adapters接收支持以下数据源类型的本地或远程数据:
&#8226; 本地的
&#8226; 开放式数据源连接(ODBC)
&#8226; ActiveX 数据对象 (ADO)
&#8226; 扩展标记语言 (XML)
Cursoradapter 类为不同的数据源类型作为本地临时表使用提供支持. Cursoradapter 对象具有以下功能:
&#8226; 灵活地使用不同的数据源.
&#8226; 使用 Cursoradapter对象数据源或者数据环境 .
&#8226; 在数据源限制范围内分享数据.
&#8226; 随意定义Cursoradapter 对象生成的临时表的表结构.
&#8226; 控制 Cursoradapter 对象生成的临时表的数据装载.
&#8226; 从不同数据源生成基于数据源类型的Visual FoxPro 临时表.
&#8226; 用 Cursoradapter 属性和方法控制怎样添加、更新、删除数据.
&#8226; 除数据环境以外,添加 Cursoradapter 对象到表单、表单集和其他容器.
&#8226; 用 Cursoradapter 类作为单独的、无数据环境的类.
对 Cursoradapter 对象来说,数据源只是一个翻译层,它从数据源获取数据生成 Visual FoxPro 临时表.
注意:   Visual FoxPro 不支持Cursoradapter 对象建立连接关系. 但是, 可以在生成的临时表之间建立连接关系.
想了解更多 Cursoradapter, 数据环境, 和游标类的信息,请看: Cursoradapter Class, DataEnvironment Object, 和 Cursor Object.
用TABLEUPDATE()和TABLEREVERT()函数实现数据互动
TABLEUPDATE( ) 函数能够识别并且用 Cursoradapter 对象工作. TABLEUPDATE( ) delegates its operations to the cursor adapter associated with the cursor. TABLEREVERT( ) operates on Cursoradapter objects in the same way as other buffered cursors.
想了解更多的 Cursoradapter 对象怎样影响TABLEUPDATE( ) 和 TABLEREVERT( ) 函数的行为, 请查看 TABLEUPDATE( ) Function 和
TABLEREVERT( ) Function.
Automatic Updating and Cursoradapters
Visual FoxPro在本地和远程视图中自动产生 SQL INSERT, UPDATE, 和 DELETE 命令. 用 Cursoradapter 对象,可以指定、控制Visual FoxPro 怎样操作SQL INSERT, UPDATE, 和 DELETE 命令.
当 Cursoradapter InsertCmd, UpdateCmd, 和 DeleteCmd 属性为空的时候, Visual FoxPro 自动产生标准的SQL命令. 你必须确定那些自动产生的命令是否适合使用的数据源.要自动产生SQL INSERT, UPDATE, 和 DELETE 命令,你必须设定下面的 Cursoradapter特定属性:
&#8226; Tables
&#8226; KeyFieldList
&#8226; UpdatableFieldList
&#8226; UpdateNameList
Tables 和 UpdateNameList 属性要符合以下规则:
&#8226; Tables
如果允许自动更新,必须按顺序列出希望在 SQL INSERT, UPDATE, 和 DELETE 命令中显示的表的名称.
&#8226; UpdateNameList
&#8226; 必须指定一个包含成对出现的、本地和完整的远程数据字段的列表,用分号分隔。 没一对字段名中,本地的在前,远程的在后。完整的远程字段名应该这样写:<远程表名>, <远程字段名>, <远程表名> 必须和Tables 属性中的名字一致.
如果你设置以下Cursoradapter 的属性为 True (.T.),必须指定关键字段:
&#8226; AllowInsert
&#8226; AllowUpdate
&#8226; AllowDelete
你想了解更多关于SQL 的自动更新命令,请看 INSERT - SQL Command, UPDATE - SQL Command, 和 DELETE - SQL Command.
批量更新(Batch Updates)
如果 Cursoradapter BatchUpdateCount 属性的值大于1, Cursoradapter 对象使用批量更新 。 同时必须具备以下条件:
&#8226; Cursoradapter 对象设置为所有允许的操作使用相同的ODBC连接,包括AllowInsert, AllowUpdate, 和 AllowDelete属性中设置的INSERT,UPDATE,和DELETE操作。.
&#8226; Cursoradapter 对象设置为所有允许的操作使用相同的ADODB 命令对象。
&#8226; Cursoradapter 对象所有被允许的操作使用 "XML" 作为数据源。
如果使用批量更新, 下列 Cursoradapter 事件不发生:
&#8226; BeforeInsert
&#8226; AfterInsert
&#8226; BeforeUpdate
&#8226; AfterUpdate
&#8226; BeforeDelete
&#8226; AfterDelete
如果批量更新失败, Visual FoxPro 尝试在该批数据中的每一行发送一个分散的更新,然而,列举的事件不会发生。
更多的相关信息, 参见 BatchUpdateCount Property.
自动更新(Automatic Updating)和 ActiveX Data Objects (ADO)
用 ADO工作的时候, 可以用两种办法发送更新:
&#8226; 使用 Cursoradapter 对象,通过在Cursoradapter CursorFill 方法中使用 ADO 记录集对象发送更新
&#8226; 当使用 ADO记录集执行自动更新的时候, 设置ursorAdapter InsertCmdDataSource, UpdateCmdDataSource, 和 DeleteCmdDataSource 属性的时候,必须遵循下列规则:
&#8226; ADO记录集必须是可读写的并且标记为可用。
&#8226; 当使用客户端记录集对象的时候,书签必须总是可用。
&#8226; Bookmarks might be available when using Keyset or Static server-side cursors when supported by the OLE DB Provider.
&#8226; 按照一个字段一个字段的规则进行更新.更新完一条记录以后, Cursoradapter 对象在ADO记录集中寻找原始记录t, 用来改变可更新字段的值, 并且调用ADO记录集的update方法。 Cursoradapter 对象不调用ado记录集的updatebatch方法, 因此, 你的程序需要在恰当的时候明确地调用 UpdateBatch方法。
当使用这个方法程序的时候, 需要按以下方法设置 Cursoradapter 对象:
THIS.DataSourceType="ADO"
THIS.UpdateCmdDataSourceType=""
可以用数据环境(DataEnvironment)和 Cursoradapter builders 来建立一个可更新的表单,或者使用数据环境 DataEnvironment 和 Cursoradapter 类库,或者在Cursoradapter Builder中使用 AutoUpdate tab 制定一个主关键字段,更新字段,或者其他要求的信息。
&#8226; 通过一个自定义的或者自动产生的命令,通过 Cursoradapter 对象向数据库直接发送一个更新。
&#8226; 当执行自动直接更新, Cursoradapter 对象需要一个 ADO对象,其 ActiveConnection 属性设置成开放的ADO连接模型。
&#8226; 用这种方法的时候, 需要用以下方法设置Cursoradapter对象 :
THIS.UpdateCmdDataSourceType="ADO"
THIS.UpdateCmdDataSource=NewADODBCommandObject
在这个方法里不推荐使用 THIS.DataSourceType="ADO" 。
自动更新和 XML
Cursoradapter 对象使用XML数据源时,不自动产生SQL INSERT, UPDATE, 和 DELETE命令,因为已经存在好多产生XML的手段。 然而,可以使用Cursoradapter 对象产生一个 XML UpdateGram ,放在 Cursoradapter UpdateGram 属性.
尽管Cursoradapter 对象能产生XML UpdateGram, 你必须使用恰当的协议来实现最终更新。例如,SQL XML(HTTP), SQL XML OLE DB, 或者 XML Web service to .NET.
使用 XML 数据源和产生 XML UpdateGrams的时候必须遵循以下规则:
&#8226; 必须在 Cursoradapter InsertCmd, UpdateCmd, and DeleteCmd 属性中指定具体的命令,Visual FoxPro能够据以进行正确的插入、更新、和删除操作。 如果使用批量更新, Cursoradapter BatchUpdateCount 属性值必须大于1, UpdateCmd 在批量中只执行一次。
&#8226; 下列 Cursoradapter 属性必须设置为 "XML":
&#8226; InsertCmdDataSourceType
&#8226; UpdateCmdDataSourceType
&#8226; DeleteCmdDataSourceType
如果这些属性不设置为"XML", Visual FoxPro 执行 UpdateCmd, InsertCmd, DeleteCmd 属性中的命令而且不产生XML UpdateGram.
&#8226; 要想正确的建立XML 更新(UpdateGram), Cursoradapter 需要 Tables, UpdatableFieldList, UpdateNameList 属性中包含确定的值。 Cursoradapter 可以通过在Tables, UpdatableFieldList,和 UpdateNameList 属性中指定恰当的名称来产生一个多表更新。因此, 可以更新用XMLUPDATEGRAM( )函数产生的连接多个表的游标。更多的信息,请看 XMLUPDATEGRAM( ) Function.
&#8226; Cursoradapter 使用 WhereType 属性产生XML UpdateGram 的 before 事件。因此, 当你执行一个更新或删除的操作时,   KeyFieldList 和 UpdatableFieldList 属性必须包含一个主关键字段.更多的信息,参见 WhereType Property.
&#8226; 如果设置了行缓冲, 或者 BatchUpdateCount 的值是1, Visual FoxPro 为每个update, insert, delete 操作建立一个XML UpdateGram 更新。
如果设置为表缓冲, 并且使用批量更新, 即atchUpdateCount 的值大于1, Visual FoxPro 为全部的批量创建一个 XML更新。 此种方式下, 必须使用TABLEUPDATE( ) 函数执行XML更新。
See Also
Working with Tables | Cursoradapter Class | Cursoradapter Object Properties, Methods, and Events

4 楼

谢谢,我一定好好的学习你发表的文章!![em2][em2]

5 楼

关于使用后台数据的问题,俺是这样想的,VFP最大的优点就是自带一个数据解决方案以及其语句的简洁、易懂。如果向后台发展,其效力不及其他的工具如DELPH、VB等。

    所以,我的观点是不提倡用VFP时过多使用后台(也不是不用),这不是说不让人进步,真的想步入C/S或B/S时代,用VFP有点误人子弟,想想看,VFP发展到现在,才开始在数据库开发上有所成长,而其他的工具则己轻车熟路的向更多的数据库及网络技术发展了。
可能很多用VFP的人有这样的感觉,用VFP除了做本地数据操作不比其他工具差外,做其他的工作包括网络、数据库、网站等技术,有一种使不上力的感觉(因为也不是不行)。这取决于VFP是一个纯数据表工具。
欢迎批评!

6 楼

楼上的朋友可能有点误会了,我想你肯定没用过VFP作前台,其它大型数据库作后台开发C/S系统.我感觉VFP+SQL SERVER配合性能不比DELPHI差,尽管我对DELPHI不算内行(后来想转为C#,所以没深入研究),但我用过,老王也发表过一些观点,认为VFP+SQL SERVER 比 DELPHI+SQL SERVER 差

7 楼

是这样的,楼主,用VFP连接SQL的机制及存储过程的使用及后台的使用,并非不行,差的是VFP本身的开发环境先天优于本地表的使用,对于DELPHI及VB、PB来说,其差别没有一点的参数说明,无法精确的说出其他工具强到什么程度,只是,包括发展到现在的VFP9在内(我不太了解),在于后台数据库、网络技术(包括并发控制)、系统控制等等的开发环境上,都有些牵强,但其它的工具先天在这些方面就是强势,所以,感觉上来说,用VFP开发本地数据应用是比较理想的,我也用过用VFP做的商业MRP ,不知是技术还是成本问题,仍是用本地表做的,但其多用户机制控制的相当好,没有出现冲突这种只有VFP才有的要命的问题。
    其实,也不是讨论VFP好不好的问题,而是说VFP到底怎么用才会更有效,以及怎样避免其缺点。这样对于同行和初学者,是终生受益的。不知楼主意下如何。

8 楼

vfp 访问 SQL SERVER 速度似乎比DELPHI 更快些

9 楼

两位的观点各有其词。
楼主的这篇文章早已拜读,并仿此改写几个小东东,收益匪浅。
用VFP自身的数据库有整体化一的好处,不需要在另外做个什么数据仓库了。
但确有其败病,比如构造WEB。虽说目前对VFP+WEB的研究有了质的突破,但绝大多数还是会以SQL为后台的,那么我们如何把处理好了的数据提交到WEB就成了我们所研究的一个问题。基于此,用VFP+SQL有其长。
另一则说,VFP的C/S或B/S的构造由于DBF自身的保密性差好象总让人不放心似的。
加之VFP8中CA的出台,VFP+SQL或许是今后程序员的偏爱,让VFP这个后娘生的(对MS来说)在数据库领域重放异彩。

10 楼

用VFP自身的数据库有整体化一的好处,不需要在另外做个什么数据仓库了。
----数据仓库的概念好象不是如此--查查SQL SERVER的数据仓库看看

我来回复

您尚未登录,请登录后再回复。点此登录或注册