VFP的数据策略:高级篇
VFP的数据策略:高级篇
作者:Doug Hennig 翻译:老瓷
引语
在“VFP中的数据策略:基础篇”一文中,我们研究了VFP应用程序中访问非VFP数据(如SQL Server)的不同机制:远程视图、SQL Passthrough、ADO、XML和VFP 8中添加的CursorAdapter类。在本文中,我们将更详细地讨论CursorAdapter,并讨论可重用数据类的概念。此外,我们将简要介绍新的XMLAdapter基类,并了解它如何帮助与其他源(如ADO.NET)交换数据。
CursorAdapter
在我看来,CursorAdapter是VFP 8中最大的新特性之一。我觉得他们这么酷的原因是:
- 使得使用ODBC、ADO或XML变得容易,即使您不太熟悉这些技术。
- 为远程数据提供了一致的接口,而不管您选择何种机制。
- 使从一种机制切换到另一种机制变得容易。
最后是一个例子。假设您有一个应用程序使用带有CursorAdapter的ODBC来访问SQL Server数据,出于某种原因,您希望更改为使用ADO。您只需更改CursorAdapters的DataSourceType并更改到后端数据库的连接,就完成了。应用程序中的其他组件既不知道也不关心这一点;它们仍然看到同一个游标,而不管用于访问数据的机制如何。
让我们开始通过查看CursorAdapter的属性、事件和方法(PEMs)来检查它们。
PEMS
这里我们不讨论CursorAdapter类的所有属性、事件和方法,只讨论更重要些的属性、事件和方法。有关完整列表,请参阅VFP文档。
(PEMS:属性、事件、方法统称的缩写——译者注)
DataSourceType
这个属性很重要:它决定了类的行为,以及将什么类型的值放入其他一些属性中。有效的选项是“Native”,这表示您使用的是Native表,或者是选择“ODBC”、“ADO”或“XML”,这表示您使用了适当的机制来访问数据。您可能不会使用“Native”,因为您可能会使用Cursor对象而不是CursorAdapter,但此设置将使以后升迁应用程序更容易。
DataSource
这是访问数据的方法。当DataSourceType设置为“Native”或“XML”时,VFP忽略此属性。对于ODBC,将DataSource设置为有效的ODBC连接句柄(这意味着您必须自己管理连接)。对于ADO,数据源必须是一个ADO记录集,该记录集的ActiveConnection对象设置为打开的ADO连接对象(同样,您必须自己管理)。
UseDEDataSource
如果此属性设置为.T.(默认值为.F.),则可以不使用DataSourceType和DataSource属性,因为CursorAdapter将使用数据环境(DataEnvironment)的属性(VFP 8也将DataSourceType和DataSource添加到DataEnvironment类)。将此设置为.T.的一个示例是,希望数据环境中的所有CursorAdapter使用相同的ODBC连接。
SelectCmd
对于除了XML以外的所有内容,这是用于检索数据的SQL SELECT命令。对于XML,这可以是可以转换为游标的有效XML字符串(使用内部XMLToCursor()调用)或返回有效XML字符串的表达式(如UDF)。
CursorSchema
此属性保存游标的结构,其格式与您在CREATE Cursor命令中使用的格式相同(此类命令中括号之间的所有内容)。这里有一个例子:CUST_ID C(6),COMPANY C(30),CONTACT C(30),CITY C(25)。尽管可以将此项留空,并告诉CursorAdapter在创建游标时确定结构,但如果将CursorSchema填充进来,效果会更好。首先,如果CursorSchema为空或不正确,则在打开窗体的数据环境时可能会出错,或者无法将字段从CursorAdapter拖放到窗体以创建控件。幸运的是,VFP附带的CursorAdapter构建器可以自动为您填充这个内容。
AllowDelete, AllowInsert, AllowUpdate, and SendUpdates
这些属性(默认为.T.)决定是否可以执行删除、插入和更新,以及是否将更改发送到数据源。
KeyFieldList, Tables, UpdatableFieldList, and UpdateNameList
如果希望VFP使用游标中所做的更改自动更新数据源,则需要这些属性,这些属性的用途与视图的同名CursorSetProp()属性相同。KeyFieldList是一个逗号分隔的字段列表(不带别名),这些字段构成游标的主键。表是一个逗号分隔的表列表。UpdateableFieldList是一个逗号分隔的字段列表(没有别名),可以更新。UpdateNameList是一个逗号分隔的列表,它将游标中的字段名与表中的字段名相匹配。UpdateNameList的格式如下:CursorFieldName1 Table.FieldName1,CursorFieldName2 Table.FieldName2……请注意,即使UpdateableFieldList不包含表的主键的名称(因为您不希望更新该字段),它也必须仍然存在于UpdateNameList中,否则更新将不起作用。
*Cmd, *CmdDataSource, *CmdDataSourceType
如果要特别控制VFP如何删除、插入或更新数据源中的记录,可以为这些属性集指定适当的值(将上面的*替换为Delete、Insert和Update)。
CursorFill(UseCursorSchema, NoData, Options, Source)
此方法创建游标并用数据源中的数据填充它(尽管可以通过.T.使NoData参数创建空游标)。对于第一个使用CursorSchema或.F.中定义的模式的参数,传递.T.。以从数据源创建适当的结构(在我看来,这种行为是相反的)。必须设置多锁,否则此方法将失败。如果CursorFill由于任何原因失败,它将返回.F.,而不是引发错误;使用AERROR()来确定出了什么问题(尽管准备好进行一些深挖,因为您经常收到的错误消息不够具体,无法确切地告诉您问题是什么)。
CursorRefresh()
此方法类似于ReQuery()函数:它刷新游标的内容。
Before*() and After*()
几乎每个方法和事件都有前后“钩子”事件,允许您自定义CursorAdapter的行为。例如,在AfterCursorFill中,可以为游标创建索引,使其始终可用。对于Before事件,可以返回.F.以防止触发它的操作发生(这与数据库事件类似)。
下面是一个示例(CursorAdapterExample.prg),它从SQL Server附带的Northwind数据库的Customers表中获取巴西客户的某些字段。游标是可更新的,因此如果您在游标中进行了更改,请将其关闭,然后再次运行程序,您将看到您的更改已保存到后端。
local loCursor as CursorAdapter, ; laErrors[1] loCursor = createobject('CursorAdapter') with loCursor .Alias = 'Customers' .DataSourceType = 'ODBC' .DataSource = sqlstringconnect('driver=SQL Server;' + ; 'server=(local);database=Northwind;uid=sa;pwd=;trusted_connection=no') .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、Form类及其设计器进行了一些更改。
首先,如前所述,DataEnvironment类现在有DataSource和DataSourceType属性。它本身不使用这些属性,但已将UseDataSource设置为.T.的任何CursorAdapter成员都使用这些属性。其次,现在可以使用类设计器(woo-hoo!)可视化地创建DataEnvironment子类。
至于表单,现在可以通过设置新的DEClass和DEClassLibrary属性来指定要使用的DataEnvironment子类。如果您这样做,您对现有数据环境所做的任何事情(游标、代码等)都将丢失,但至少您会首先收到警告。表单的一个很酷的新特性是BindControls属性;在属性窗口中将其设置为.F. 意味着VFP不会在初始化时尝试对控件进行数据绑定,只有在将BindControls设置为.T.时才会这样做。这有什么好处?好吧,您诅咒参数传递给Init多少次了,Init在所有控件初始化并绑定到它们的ControlSource之后触发?如果要将参数传递给告诉它要打开哪个表的窗体或其他影响ControlSources的内容,该怎么办?这个新属性使这个问题很快解决。
其他变化
CursorGetProp('SourceType')返回一个新的值范围:如果游标是用CursorFill创建的,则该值为100加上旧值(例如,远程数据为102)。如果游标是用CursorAttach创建的(允许您将现有游标附加到CursorAdapter对象),则该值为200加上旧值。如果数据源是ADO记录集,则值为104(CursorFill)或204(CursorAttach)。
生成器
VFP包括DataEnvironment和CursorAdapter构造器(或称为生成器——译者注),使得使用这些类更加容易。
以正常方式启动DataEnvironment Builder:在类设计器中右键单击窗体的DataEnvironment或DataEnvironment子类,然后选择Builder。数据环境生成器的“数据源”页是设置数据源信息的位置。选择所需的数据源类型和数据源的来源。如果选择“使用现有连接句柄”(ODBC)或“使用现有ADO记录集”(ADO),请指定包含数据源的表达式(例如“goConnectionMgr.nHandle”)。您还可以选择使用系统上的任一个DSN或连接字符串。只有在为ADO选择“使用连接字符串”时才会启用“生成”按钮,该按钮将显示“数据链接属性”对话框,您可以使用该对话框直观地生成连接字符串。如果选择“使用DSN”或“使用连接字符串”,生成器将在数据环境的BeforeOpenTables方法中生成代码以创建所需的连接。如果选择“Native”,则可以选择VFP数据库容器作为数据源;在这种情况下,生成的代码将确保数据库是打开的(也可以使用自由表作为数据源)。
“Cursors”页面允许您维护DataEnvironment的CursorAdapter成员(游标对象不会在生成器中显示,也不能添加它们)。Add按钮允许您向DataEnvironment添加CursorAdapter子类,而New则创建一个新的基类CursorAdapter。Remove删除Select CursorAdapter,Builder为所选CursorAdapter调用CursorAdapter Builder。您可以更改CursorAdapter对象的名称,但对于任何其他属性,都需要CursorAdapter生成器。
从快捷菜单中选择Builder也可以调用CursorAdapter生成器。“Properties”页显示对象的类和名称(只有在从DataEnvironment中调出生成器时才能更改名称,因为它对CursorAdapter子类是只读的)、它将创建的游标的别名、是否应该使用DataEnvironment的数据源以及连接信息(如果没有)。与DataEnvironment生成器一样,如果选择“使用DSN”或“使用连接字符串”,CursorAdapter生成器将生成代码以创建所需的连接(在本例中是CursorFill方法)。
“数据访问”页允许您指定SelectCmd、CursorSchema和其他属性。如果您指定了连接信息,可以单击SelectCmd的Build按钮来显示Select Command Builder,这样就可以轻松地创建SelectCmd。
Select命令生成器简化了构建一个简单的Select语句的工作。从“表格”下拉列表中选择所需的表格,然后将相应的字段移到选定的一侧。对于本机数据源,可以向“表”组合框中添加表(例如,如果希望使用空闲表)。选择OK时,SelectCmd将填充适当的SQL SELECT语句。
单击游标模式的“生成”按钮,自动为您填写此属性。为了使其工作,生成器实际上创建了一个新的CursorAdapter对象,适当地设置了属性,并调用CursorFill来创建游标。如果您没有到数据源的实时连接,或者CursorFill由于某种原因(例如无效的SelectCmd)失败,那么这显然行不通。
使用“自动更新”页设置VFP自动为数据源生成更新语句所需的属性。Tables属性是从SelectCmd中指定的表自动填充的,fields网格是从CursorSchema中的字段填充的。与视图设计器一样,可以通过检查网格中的相应列来选择哪些是关键字段,哪些字段是可更新的。还可以设置其他属性,例如在将游标发送到数据源之前转换游标某些字段中的数据的函数。
更新、插入和删除页面的外观几乎相同。它们允许您为更新、删除和插入属性集指定值。对于VFP不能自动生成update语句的XML,这一点尤为重要。
使用本机数据
尽管很明显CursorAdapter的目的是为了标准化和简化对非VFP数据的访问,但是您可以通过将DataSourceType设置为“Native”来使用它来替代Cursor。你为何这样做?主要是倾向于将来应用程序升级;通过简单地将DataSourceType更改为其他选项之一(并可能更改其他一些属性,如设置连接信息),您可以轻松地切换到其他DBMS,如SQL Server。
当DataSourceType设置为“Native”时,VFP将忽略DataSource。SelectCmd必须是一个SQL SELECT语句,而不是USE命令或表达式,这意味着您总是使用相当于本地视图的语句,而不是直接使用表。您须确保VFP可找到SELECT语句中引用的任何表,因此如果这些表不在当前目录中,则需要设置路径或打开表所属的数据库。与往常一样,如果希望游标可更新,请确保设置更新属性(KeyFieldList、Tables、UpdateableFieldList和UpdateNameList)。
以下示例(NativeExample.prg)从TestData VFP示例数据库中的Customer表创建一个可更新的游标:
local loCursor as CursorAdapter, ; laErrors[1] open database (_samples + 'data\testdata') loCursor = createobject('CursorAdapter') 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、UpdateableFieldList和UpdateNameList,VFP将自动生成适当的UPDATE、INSERT和DELETE语句,以便用任何更改更新后端。如果要改用存储过程,请适当设置*Cmd、*CmdDataSource和*CmdDataSourceType属性。
下面是一个示例,取自ODBCExample.prg,它调用Northwind数据库中的CustOrderHist存储过程,以获取特定客户按产品销售的总单位:
local loCursor as CursorAdapter, ; laErrors[1] loCursor = createobject('CursorAdapter') with loCursor .Alias = 'CustomerHistory' .DataSourceType = 'ODBC' .DataSource = sqlstringconnect('driver=SQL Server;server=(local);' + ; 'database=Northwind;uid=sa;pwd=;trusted_connection=no') .SelectCmd = "exec CustOrderHist 'ALFKI'" if .CursorFill() browse else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill() endwith
使用ADO
使用ADO作为CursorAdapter的数据访问机制比使用ODBC有更多的问题:
- 必须将数据源设置为ADO记录集,该记录集的ActiveConnection属性设置为打开的ADO连接对象。
- 如果要使用参数化查询(这可能是常见情况,而不是检索所有记录),则必须将其ActiveConnection属性设置为open ADO Connection对象的ADO命令对象作为第四个参数传递给CursorFill。VFP将负责为您填充Command对象的参数集合(它解析SelectCmd以查找参数),但包含参数值的变量当然必须在作用域中。
- 在数据环境中将一个CursorAdapter与ADO一起使用很简单:可以将UseDEDataSource设置为.T.。如果愿意,可以像使用CursorAdapter一样设置数据环境的DataSource和DataSourceType属性。但是,如果数据环境中有多个CursorAdapter,则此操作不起作用。原因是DataEnvironment.DataSource引用的ADO记录集只能包含一个CursorAdapter的数据;当为第二个CursorAdapter调用CursorFill时,会出现“记录集已打开”错误。因此,如果您的数据环境有多个CursorAdapter,则必须将UseDEDataSource设置为.F.并自己管理每个CursorAdapter的DataSource和DataSourceType属性(或者可能使用为您管理这些属性的DataEnvironment子类)。
下面的示例代码取自ADOExample.prg,它展示了如何在ADO命令对象的帮助下使用参数化查询检索数据。这个例子还展示了VFP 8中新的结构化错误处理特性的使用;对ADO连接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=;trusted_connection=no' 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
将XML与CursorAdapter结合使用需要一些额外的东西。以下是问题:
- 数据源属性被忽略。
- 即使将.F.作为第一个参数传递给CursorFill方法,也必须填写CursorSchema属性,否则将出现错误。
- SelectCmd属性必须设置为返回游标XML的表达式,例如用户定义函数(UDF)或对象方法名称。
- 对游标所做的更改将转换为diffgram,diffgram是一种XML,它包含更改字段和记录的之前和之后的值,并在需要更新时放置在UpdateGram属性中。
- 为了将更改写回数据源,UpdateCmdDataSourceType必须设置为“XML”,UpdateCmd必须设置为处理更新的表达式(同样,可能是UDF或对象方法)。您可能需要将“This.UpdateGram”传递给UDF,以便它可以将更改发送到数据源。
游标的XML源可以来自不同的地方。例如,可以调用一个UDF,该UDF使用CURSORTOXML()将VFP游标转换为XML,并返回结果:
use CUSTOMERS cursortoxml('customers', 'lcXML', 1, 8, 0, '1') return lcXML
UDF可以调用返回结果集为XML的Web服务。下面是一个从我在自己的系统上创建和注册的Web服务中为我生成的自动感应示例(细节并不重要;它只是显示了一个Web服务的示例)。
local loWS as dataserver 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
它可以使用SQLXML 3.0执行存储在Web服务器模板文件中的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)来执行更新。
下面是一个示例(XMLExample.prg),它使用带有XML数据源的CursorAdapter。注意,SelectCmd和UpdateCmd都调用UDF。在SelectCmd的情况下,SQL Server 2000 XML模板的名称和要检索的客户ID被传递给一个名为GetNWXML的UDF,稍后我们将讨论这个UDF。对于UpdateCmd,VFP将UpdateGram属性传递给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 = 'GetNWXML([customersbyid.xml?customerid=ALFKI])' .Tables = 'CUSTOMERS' .UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, CONTACTNAME, ' + ; 'CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX' .UpdateCmdDataSourceType = 'XML' .UpdateCmd = 'SendNWXML(This.UpdateGram)' .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
此代码引用的XML模板CustomersByID.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数据库的虚拟目录中(有关配置IIS以使用SQL Server的详细信息,请参阅附录)。
这是GetNWXML的代码。它使用MSXML2.XMLHTTP对象访问Web服务器上的SQL Server 2000 XML模板并返回结果。模板的名称(以及可选的任何查询参数)作为参数传递给此代码。
lparameters tcURL local loXML as MSXML2.XMLHTTP loXML = createobject('MSXML2.XMLHTTP') loXML.open('POST', 'http://localhost/northwind/template/' + tcURL, .F.) loXML.setRequestHeader('Content-type', 'text/xml') loXML.send() return loXML.responseText
SendNWXML看起来很相似,只是它希望传递一个diffgram,将diffgram加载到MSXML2.DOMDocument对象中,并将该对象传递给Web服务器,然后Web服务器将通过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。您应该在浏览窗口中看到一条记录(ALFKI客户)。更改某个字段中的值,然后关闭窗口并再次运行PRG。您应该看到您的更改已写入后端。
CursorAdapter 和 DataEnvironment 子类
与VFP中通常的情况一样,我创建了CursorAdapter和DataEnvironment的子类,我将使用这些子类而不是基类。
SFCursorAdapter
SFCursorAdapter(在SFDataClasses.vcx中)是CursorAdapter的一个子类,它添加了一些附加功能:
- 它可以自动处理参数化查询;您可以将参数值定义为静态(常量值)或动态(表达式,例如“=Thisform.txtName.value”,在打开或刷新游标时计算)。
- 它可以在游标打开后自动创建索引。
- 它为ADO做了一些特殊的事情,例如将数据源设置为ADO记录集,将记录集的ActiveConnection属性设置为ADO连接对象,以及在使用参数化查询时创建ADO命令对象并将其传递给CursorFill。
- 它提供了一些简单的错误处理(cErrorMessage属性用错误消息填充)。
- 它有CursorAdapter中缺少的更新和发布方法。
让我们来看看这个类。
Init方法创建两个集合(使用新的集合基类,它维护事物的集合),一个用于SelectCmd属性可能需要的参数,另一个用于在游标打开后应自动创建的标记。它还设置了MULTILOCKS on,因为这是CursorAdapter游标所必需的。
with This * 创建参数和标记集合 .oParameters = createobject('Collection') .oTags = createobject('Collection') * 确保 MULTILOCKS 设置为 on. set multilocks on endwith
AddParameter方法向parameters集合添加一个参数。向此方法传递参数的名称(该名称应与SelectCmd属性中显示的名称匹配)和可选的参数值(如果现在不传递,可以稍后使用GetParameter方法进行设置)。这段代码展示了VFP 8中的两个新特性:新的Empty类(没有PEMs),使其成为轻量级对象的理想选择;ADDPROPERTY()函数(其作用类似于那些没有该方法的对象的ADDPROPERTY方法)。
lparameters tcName, ; tuValue local loParameter loParameter = createobject('Empty') addproperty(loParameter, 'Name', tcName) addproperty(loParameter, 'Value', tuValue) This.oParameters.Add(loParameter, tcName)
使用GetParameter方法返回一个特定的参数对象;当您想设置要用于参数的值时,通常会使用这个方法。
lparameters tcName local loParameter loParameter = This.oParameters.Item(tcName) return loParameter
SetConnection方法用于将DataSource属性设置为所需的连接。如果DataSourceType是“ODBC”,请传递连接句柄。如果是“ADO”,则数据源需要是一个ADO记录集,其ActiveConnection属性设置为打开的ADO连接对象,因此通过Connection对象,SetConnection将创建记录集并将其ActiveConnection设置为传递对象。
lparameters tuConnection with This do case case .DataSourceType = 'ODBC' .DataSource = tuConnection case .DataSourceType = 'ADO' .DataSource = createobject('ADODB.RecordSet') .DataSource.ActiveConnection = tuConnection endcase endwith
要创建游标,请调用GetData方法而不是CursorFill,因为它会自动处理参数和错误。如果要创建游标但不填充数据,请将.T.传递给GetData。此方法所做的第一件事是创建私有范围的变量,这些变量的名称和值与参数集合中定义的参数相同(从这里调用的GetParameterValue方法返回参数对象的值或以“=”开头的值的求值)。接下来,如果我们使用ADO并且有任何参数,代码将创建一个ADO Command对象并将其ActiveConnection设置为Connection对象,然后将Command对象传递给CursorFill方法;CursorAdapter要求在参数化ADO查询中使用该方法。如果我们没有使用ADO或者没有任何参数,代码只调用cursor fill来填充游标。注意.T.被传递给CursorFill,告诉它在CursorSchema被填充时使用CursorSchema(这是我希望基类具有的行为)。如果创建了游标,则代码调用CreateTags方法为游标创建所需的索引;如果没有,则调用HandleError方法来处理发生的任何错误。
lparameters tlNoData local loParameter, ; lcName, ; luValue, ; llUseSchema, ; loCommand, ; llReturn with This *如果我们要填充游标(而不是创建空游标),则创建变量来保存任何参数 *必须在这里而不是在方法中这样做,因为我们希望它们的作用域是私有的 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 *尝试填充游标 llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions) endif '?' $ .SelectCmd ... *如果我们创建了游标,请创建为其定义的任何标记。 *如果没有,请处理错误。 if llReturn .CreateTags() else .HandleError() endif llReturn endwith return llReturn
Update方法很简单:它只调用TABLEUPDATE()尝试更新原始数据源,如果失败则调用HandleError。
local llReturn llReturn = tableupdate(1, .F., This.Alias) if not llReturn This.HandleError() endif not llReturn return llReturn
有几种方法我们在这里不看,你可以自己检查一下。AddTag将游标创建后要创建的索引的信息添加到tags集合,而CreateTags(从GetData调用)在INDEX ON语句中使用该集合中的信息。HandleError使用AERROR()来确定出错的地方,并将错误数组的第二个元素放入cErrorMessage属性中。
让我们看几个使用这个类的例子。第一个(取自TestCursorAdapter.prg)从Northwind数据库的Customers表中获取所有记录。这段代码与用于基类CursorAdapter的代码没有太大的不同(由于没有填写CursorSchema,因此必须将.F.作为第一个参数传递给CursorFill)。
loCursor = newobject('SFCursorAdapter', 'SFDataClasses') with loCursor *连接到SQL Server Northwind数据库并获取客户记录 .DataSourceType = 'ODBC' .DataSource = sqlstringconnect('driver=SQL Server;server=(local);' + ; 'database=Northwind;uid=sa;pwd=;trusted_connection=no') .Alias = 'Customers' .SelectCmd = 'select * from customers' if .GetData() browse else messagebox('Could not get the data. The error message was:' + ; chr(13) + chr(13) + .cErrorMessage) endif .GetData() endwith
下一个示例(也取自TestCursorAdapter.prg)使用SFConnectionMgr的ODBC版本来管理连接,我们在“VFP中的数据策略:基础篇”一文中查看了该版本。它还为SelectCmd使用参数化语句,显示AddParameter方法如何允许您处理参数,并演示如何使用AddTag方法自动为游标创建标记。
loConnMgr = newobject('SFConnectionMgrODBC', 'SFRemote') with loConnMgr .cDriver = 'SQL Server' .cServer = '(local)' .cDatabase = 'Northwind' .cUserName = 'sa' .cPassword = '' endwith if loConnMgr.Connect() loCursor = newobject('SFCursorAdapter', 'SFDataClasses') with loCursor .DataSourceType = 'ODBC' .SetConnection(loConnMgr.GetConnection()) .Alias = 'Customers' .SelectCmd = 'select * from customers where country = ?pcountry' .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() endwith else messagebox(loConnMgr.cErrorMessage) endif loConnMgr.Connect()
SFDataEnvironment
SFDataEnvironment(也在SFDataClasses.vcx中)比SFCursorAdapter简单得多,但添加了一些有用的功能:
- GetData方法调用所有SFCursorAdapter成员的GetData方法,因此不必单独调用它们。
- 类似地,Requery和Update方法调用每个SFCursorAdapter成员的Requery和Update方法。
- 与SFCursorAdapter类似,SetConnection方法将数据源设置为ADO记录集,并将记录集的ActiveConnection属性设置为ADO连接对象。但是,它也调用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稍微复杂一点:它调用任何具有该方法且UseDEDataSource设置为.F.的成员对象的SetConnection方法,然后使用类似于SFCursorAdapter中的代码设置自己的数据源(如果任何CursorAdapter的UseDEDataSource设置为.T.)。
lparameters tuConnection local llSetOurs, ; loCursor, ; llReturn with This *调用任何不使用数据源的CursorAdapter的SetConnection方法 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 *如果发现使用数据源的CursorAdapter,需要设置数据源 if llSetOurs do case case .DataSourceType = 'ODBC' .DataSource = tuConnection case .DataSourceType = 'ADO' .DataSource = createobject('ADODB.RecordSet') .DataSource.ActiveConnection = tuConnection endcase endif llSetOurs endwith
Requery和Update几乎与GetData相同,所以我们不必费心去查看它们。
TestDE.prg显示了如何使用SFDataEnvironment作为两个SFCursorAdapter类的容器。由于此示例使用ADO,因此每个SFCursorAdapter都需要自己的数据源,故UseDEDataSource设置为.F.。请注意,对DataEnvironment SetConnection方法的单个调用负责为每个CursorAdapter设置数据源属性。
loConnMgr = newobject('SFConnectionMgrADO', 'SFRemote') with loConnMgr .cDriver = 'SQLOLEDB.1' .cServer = '(local)' .cDatabase = 'Northwind' .cUserName = 'sa' .cPassword = '' endwith if loConnMgr.Connect() loDE = newobject('SFDataEnvironment', 'SFDataClasses') with loDE .NewObject('CustomersCursor', 'SFCursorAdapter', 'SFDataClasses') with .CustomersCursor .Alias = 'Customers' .SelectCmd = 'select * from customers' .DataSourceType = 'ADO' endwith .NewObject('OrdersCursor', 'SFCursorAdapter', 'SFDataClasses') with .OrdersCursor .Alias = 'Orders' .SelectCmd = 'select * from orders' .DataSourceType = 'ADO' endwith .SetConnection(loConnMgr.GetConnection()) 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 else messagebox(loConnMgr.cErrorMessage) endif loConnMgr.Connect()
可重用数据类
现在我们有了CursorAdapter和DataEnvironment子类,让我们讨论一下可重用的数据类。
VFP开发人员要求微软在VFP中添加的一件事是可重用的数据环境。例如,您可能有一个表单和一个报表具有完全相同的数据设置,但是您必须手动为每个表单和报表填充数据环境,因为数据环境是不可重用的。一些开发人员(以及几乎所有的框架供应商)通过在代码中创建数据环境(它们不能可视化地被子类化)并在表单上使用“loader”对象来实例化数据环境子类,使得创建可重用的数据环境变得更加容易。然而,这是一种混乱,并没有帮助报告。
现在,在VFP 8中,我们能够创建两个可重用的数据类,它们可以提供从任何数据源到任何需要它们的数据源的游标,以及可重用的数据环境,后者可以托管数据类。在撰写本文时,您不能在报表中使用CursorAdapter或DataEnvironment子类,但可以通过编程添加CursorAdapter子类(例如在DataEnvironment的Init方法中)来利用那里的可重用性。
我们来为Northwind客户和订单表创建数据类。首先,创建SFCursorAdapter的一个子类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生成器完成大部分工作,特别是设置CursorSchema和更新属性。诀窍是打开“use connection settings in builder only”(仅在生成器中使用连接设置)选项,填写连接信息以建立实时连接,然后填写SelectCmd并使用生成器为您构建其余属性。
现在,只要您需要Northwind Customers表中的记录,就只需使用CustomersCursor类。当然,我们还没有定义任何连接信息,但这实际上是件好事,因为这个类不必担心如何获取数据(ODBC、ADO或XML),甚至不必担心要使用什么数据库引擎(用于SQL Server、Access和新版VFP8的Northwind数据库)。
但是请注意,这个游标涉及Customers表中的所有记录。有时候,你只想要一个特定的客户。所以,让我们创建一个CustomersCursor的子类CustomerByIDCursor。将SelectCmd更改为“select * from customers where customerid = ?pcustomerid”并将以下代码放入Init:
lparameters tcCustomerID dodefault() This.AddParameter('pCustomerID', tcCustomerID)
这将创建一个名为pCustomerID的参数(与SelectCmd中指定的名称相同),并将其设置为传递的任意值。如果未传递任意值,请使用GetParameter返回此参数的对象,并在调用GetData之前设置其Value属性。
创建一个类似于CustomersCursor的orderscorsor类,只是它从Orders表中检索所有记录。然后创建一个OrdersForCustomerCursor子类,该子类只检索特定客户的订单。将SelectCmd设置为“select * from orders where customerid = ?pcustomerid”,并将与CustomerByIDCursor相同的代码放入Init(因为它是相同的参数)。
要测试其效果,请运行TestCustomersCursor.prg。
示例:Form
现在我们有了一些可重用的数据类,来用一下它们。首先,让我们创建一个名为CustomersAndOrdersDataEnvironment的SFDataEnvironment子类,它包含CustomerByIDCursor和OrdersForCustomerCursor类。将AutoOpenTables设置为.F.(因为我们需要在打开表之前设置连接信息),并将CursorAdapter和UseDEDataSource设置为.T.。现在可以以某种形式使用此数据环境来显示有关特定客户的信息,包括其订单。
让我们创建这样一个表单。创建一个名为CustomerOrders.scx的表单(它包含在本文档附带的示例文件中),将DEClass和DEClassLibrary设置为CustomersAndOrdersDataEnvironment,以便我们使用可重用的数据环境。将以下代码放入Load方法中:
#define ccDATASOURCETYPE 'ADO' with This.CustomersAndOrdersDataEnvironment *设置数据环境数据源 .DataSourceType = ccDATASOURCETYPE *如果我们使用ODBC或ADO,请创建一个连接管理器 *并打开连接到Northwind数据库的连接 if .DataSourceType $ 'ADO,ODBC' This.AddProperty('oConnMgr') This.oConnMgr = newobject('SFConnectionMgr' + ccDATASOURCETYPE, ; 'SFRemote') with This.oConnMgr .cDriver = iif(ccDATASOURCETYPE = 'ADO', 'SQLOLEDB.1', ; 'SQL Server') .cServer = '(local)' .cDatabase = 'Northwind' .cUserName = 'sa' .cPassword = '' endwith if not This.oConnMgr.Connect() messagebox(oConnMgr.cErrorMessage) return .F. endif not This.oConnMgr.Connect() *如果我们使用ADO,每个游标都必须有自己的数据源 if .DataSourceType = 'ADO' .CustomerByIDCursor.UseDEDataSource = .F. .CustomerByIDCursor.DataSourceType = 'ADO' .OrdersForCustomerCursor.UseDEDataSource = .F. .OrdersForCustomerCursor.DataSourceType = 'ADO' endif .DataSourceType = 'ADO' *将数据源设置为连接 .SetConnection(This.oConnMgr.GetConnection()) *如果使用的是XML,请更改SelectCmd以调用GetNWXML函数 else .CustomerByIDCursor.SelectCmd = 'GetNWXML([customersbyid.xml?' + ; 'customerid=] + pCustomerID)' .CustomerByIDCursor.UpdateCmdDataSourceType = 'XML' .CustomerByIDCursor.UpdateCmd = 'SendNWXML(This.UpdateGram)' .OrdersForCustomerCursor.SelectCmd = 'GetNWXML([ordersforcustomer.' + ; 'xml?customerid=] + pCustomerID)' .OrdersForCustomerCursor.UpdateCmdDataSourceType = 'XML' .OrdersForCustomerCursor.UpdateCmd = 'SendNWXML(This.UpdateGram)' endif .DataSourceType $ 'ADO,ODBC' *指定将从CustomerID文本框中填充游标参数的值 loParameter = .CustomerByIDCursor.GetParameter('pCustomerID') loParameter.Value = '=Thisform.txtCustomerID.Value' loParameter = .OrdersForCustomerCursor.GetParameter('pCustomerID') loParameter.Value = '=Thisform.txtCustomerID.Value' *创建空游标并在失败时显示错误消息 if not .GetData(.T.) messagebox(.cErrorMessage) return .F. endif not .GetData(.T.) endwith
这看起来像很多代码,但其中大部分是为了演示目的,以允许切换到不同的数据访问机制。
此代码创建一个连接管理器来处理连接(ADO、ODBC或XML),具体取决于ccDATASOURCETYPE常量,您可以更改该常量以尝试每个机制。对于ADO,由于每个CursorAdapter都必须有自己的数据源,因此为每个CursorAdapter设置UseDEDataSource和DataSourceType属性。然后,代码调用SetConnection方法来设置连接信息。对于XML,SelectCmd、UpdateCmdDataSourceType和UpdateCmd属性必须如前所述进行更改。接下来,代码使用两个CursorAdapter对象的GetParameter方法将pCustomerID参数的值设置为表单中文本框的内容。注意在值中使用“=”;这意味着每次需要时都会对Value属性求值,因此我们基本上有一个动态参数(当用户在文本框中键入时,保存将参数不断更改为当前值的需要)。最后,调用GetData方法来创建空游标,以便控件的数据绑定可以工作。
在表单上放置一个文本框并将其命名为txtCustomer,将以下代码放入其Valid方法中:
with Thisform .CustomersAndOrdersDataEnvironment.Requery() .Refresh() endwith
这将导致在输入客户ID时重新查询游标和刷新控件。
在表单上放置一个标签,放在文本框旁边,并将其标题设置为“客户ID”。
将CompanyName、ContactName、Address、City、Region、PostalCode和Country字段从DataEnvironment中的Customers游标拖动到表单中,以创建这些字段的控件。然后在Orders游标中选择OrderID、EmployeeID、OrderDate、RequiredDate、ShippedDate、ShipVia和Freight字段,并将它们拖到表单中以创建网格(Grid--译者注)。
就这样子。运行表单并输入“ALFKI”作为客户ID。当您在文本框中选择选项卡时,您应该会看到客户地址信息和订单。尝试更改有关客户或订单的内容,然后关闭表单,再次运行它,然后再次输入“ALFKI”。您应该看到,您所做的更改已写入后端数据库,而无需您付出任何努力。
很酷吧?这比基于本地表或视图创建表单要简单得多。更好的方法是,尝试将ccDATASOURCETYPE常量更改为“ADO”或“XML”,并注意表单的外观和工作方式完全相同。这就是CursorAdapters的要点!
示例:Report
我们试一个Report。此处讨论的示例取自此文档附带的CustomerOrders.frx。这里最大的问题是,与表单不同,我们不能告诉报表使用DataEnvironment子类,也不能在DataEnvironment中删除CursorAdapter子类。因此,我们必须在报表中放入一些代码,以便将CursorAdapter子类添加到数据环境中。尽管将此代码放入报表数据环境的BeforeOpenTables事件中似乎是合乎逻辑的,但实际上这不会起作用,因为我不明白为什么,在预览报表时,BeforeOpenTables会在每个页面上激发。所以,我们将把代码放入Init方法中。
#define ccDATASOURCETYPE 'ODBC' with This set safety off *设置数据环境数据源 .DataSourceType = ccDATASOURCETYPE *为客户和订单创建CursorAdapter对象 .NewObject('CustomersCursor', 'CustomersCursor', 'NorthwindDataClasses') .CustomersCursor.AddTag('CustomerID', 'CustomerID') .NewObject('OrdersCursor', 'OrdersCursor', 'NorthwindDataClasses') .OrdersCursor.AddTag('CustomerID', 'CustomerID') *若使用ODBC或ADO,请创建一个连接管理器 *并打开连接到Northwind数据库的连接 if .DataSourceType $ 'ADO,ODBC' .AddProperty('oConnMgr') .oConnMgr = newobject('SFConnectionMgr' + ccDATASOURCETYPE, ; 'SFRemote') with .oConnMgr .cDriver = iif(ccDATASOURCETYPE = 'ADO', 'SQLOLEDB.1', ; 'SQL Server') .cServer = '(local)' .cDatabase = 'Northwind' .cUserName = 'sa' .cPassword = '' endwith if not .oConnMgr.Connect() messagebox(.oConnMgr.cErrorMessage) return .F. endif not .oConnMgr.Connect() *如果使用ADO,每个游标都必须有自己的数据源 if .DataSourceType = 'ADO' .CustomersCursor.UseDEDataSource = .F. .CustomersCursor.DataSourceType = 'ADO' .CustomersCursor.SetConnection(.oConnMgr.GetConnection()) .OrdersCursor.UseDEDataSource = .F. .OrdersCursor.DataSourceType = 'ADO' .OrdersCursor.SetConnection(.oConnMgr.GetConnection()) else .CustomersCursor.UseDEDataSource = .T. .OrdersCursor.UseDEDataSource = .T. .DataSource = .oConnMgr.GetConnection() endif .DataSourceType = 'ADO' .CustomersCursor.SetConnection(.oConnMgr.GetConnection()) .OrdersCursor.SetConnection(.oConnMgr.GetConnection()) *若使用XML,请更改SelectCmd以调用GetNWXML函数 else .CustomersCursor.SelectCmd = 'GetNWXML([getallcustomers.xml])' .CustomersCursor.DataSourceType = 'XML' .OrdersCursor.SelectCmd = 'GetNWXML([getallorders.xml])' .OrdersCursor.DataSourceType = 'XML' endif .DataSourceType $ 'ADO,ODBC' *获取数据并在失败时显示错误消息 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() *设置从客户到订单的关系 set relation to CustomerID into Customers endwith
此代码看起来与窗体的代码类似。同样,大多数代码是处理不同的数据访问机制。但是,还有一些额外的代码,因为我们不能使用DataEnvironment子类,必须自己编写行为代码。
现在,我们如何方便地把字段放在Report上?由于CursorAdapter在设计时不存在于数据环境中,因此我们不能将字段从它们拖到Report中。这里有一个提示:创建一个PRG来创建游标并将其留在作用域中(通过挂起或使CursorAdapter对象公开),然后使用Quick Report函数将具有适当大小的字段放在Report上。
在CUSTOMERS.CUSTOMERID上创建一个组并选中“在新页面上启动每个组”。然后将Report布局为类似于以下内容:
XMLAdapter
除了CursorAdapter之外,VFP 8还有三个新的基类来改进VFP对XML的支持:XMLAdapter、XMLTable和XMLField。XMLAdapter提供了一种在XML和VFP游标之间转换数据的方法。它的功能比CursorToXML()和XMLToCursor()函数多得多,包括支持分层XML和使用那些函数不支持的XML类型(如ADO.NET数据集)的功能。XMLTable和XMLField是子对象,它们提供微调XML数据的模式的能力。此外,XMLTable还有一个ApplyDiffgram方法,它允许VFP使用updategrams和diffgrams,这是VFP 7中缺少的。
为了让您了解它的功能,我创建了一个返回ADO.NET数据集的ASP.NET Web服务,然后使用VFP中的XMLAdapter对象来使用该数据集。现在我做到了。
首先,在Visual Studio.NET中,我将Northwind Customers表从服务器资源管理器拖到一个名为NWWebService的新ASP.NET Web服务项目中。这会自动创建两个对象,SQLConnection1和SQLDataAdapter1。然后,我将以下代码添加到现有生成的代码中:
<WebMethod()> Public Function GetAllCustomers() As DataSet Dim loDataSet As New DataSet() Me.SqlConnection1.Open() Me.SqlDataAdapter1.Fill(loDataSet) Return loDataSet End Function
我构建该项目是为了在NWWebService虚拟目录(VS.NET自动为我创建)中生成适当的Web服务文件。
为了在VFP中使用这个Web服务,我使用IntelliSense管理器注册了一个名为“Northwind.NET”的Web服务,指向“http://localhost/NWWebService/NWWebService.asmx?WSDL”作为WSDL文件的位置。然后我创建了以下代码(在XMLAdapterWebService.prg中)来调用Web服务并将ADO.NET数据集转换为VFP游标。
local loWS as Northwind.NET, ; loXMLAdapter as XMLAdapter, ; loTable as XMLTable *从.NET Web服务获取.NET数据集 loWS = NEWOBJECT("Wsclient",HOME()+"ffc\_webservices.vcx") loWS.cWSName = "Northwind.NET" loWS = loWS.SetupClient("http://localhost/NWWebService/NWWebService.asmx" + ; "?WSDL", "NWWebService", "NWWebServiceSoap") loXML = loWS.GetAllCustomers() *创建一个XMLAdapter并加载数据 loXMLAdapter = createobject('XMLAdapter') loXMLAdapter.XMLSchemaLocation = '1' loXMLAdapter.LoadXML(loXML.Item(0).parentnode.xml) *如果成功地加载了XML,那么从每个表对象创建并浏览一个游标 if loXMLAdapter.IsLoaded for each loTable in loXMLAdapter.Tables loTable.ToCursor() browse use next loTable endif loXMLAdapter.IsLoaded
注意,为了使用XMLAdapter,您需要在系统上安装MSXML 4.0服务包1或更高版本。您可以从MSDN网站下载(http://MSDN.microsoft.com并搜索MSXML)。
总结
我认为CursorAdapter是VFP 8中最大和最令人兴奋的增强之一,因为它提供了一个一致且易于使用的远程数据接口,而且它允许我们创建可重用的数据类。我相信一旦你用它来工作,你会发现他们和我一样令人兴奋。
作者介绍:
Doug Hennig是Stonefield Systems Group Inc.的合作伙伴。他是获奖的Stonefield数据库工具包(SDT)的作者和获奖的Stonefield查询的共同作者。他是《黑客视觉FoxPro 7.0指南》的合著者(与Tamar Granor、Ted Roche和Della Martin一起)和《视觉FoxPro 7.0的新特性》的合著者(与Tamar Granor和Kevin McNeish一起),均来自Hentzenwerke出版社,在Pinnacle Publishing的Pros Talk VisualFoxPro系列中,“VisualFoxPro数据字典”的作者。他在FoxTalk上写了每月的“可重用工具”专栏。他是《黑客指南》和《基础知识》的技术编辑,这两本书都来自亨森沃克出版社。自1997年以来,道格在每次微软FoxPro开发者大会(DevCon)以及北美各地的用户团体和开发者大会上都发表过演讲。他是微软最有价值的专业人士(MVP)和认证专业人士(MCP)。
附录:设置SQL Server 2000 XML访问存取
另文,本文略……