VFP CursorAdapter 起步二(作者:Doug Hennig 译者:fbilo)

用 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 属性设置成其它几个选项之一(当然可能还需要修改其它几个属性,例如设置连接信息等等),就能轻松的切换到另一种数据库引擎,例如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 模板的内容如下:
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。它的内容如下:

<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="urn:schemas-microsoft-com:xml-sql">
<Customers CustomerID="ALFKI" CompanyName="Alfreds
Futterkiste" ContactName="Maria Anders"
ContactTitle="Sales Representative"
Address="Obere Str. 57" City="Berlin" PostalCode="12209"
Country="Germany" Phone="030-0074321"
Fax="999-999-9999" />
现在可以运行本文中的SQLXML示例了。

posted @ 2020-02-20 23:27  老瓷  阅读(480)  评论(0编辑  收藏  举报