VFP CursorAdapter 起步一(作者:Doug 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