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[]
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[])
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[]
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()
else
aerror(laErrors)
messagebox(laErrors[])
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[]
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[])
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[]
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., , loCommand)
browse
else
aerror(laErrors)
messagebox(laErrors[])
endif .CursorFill(.F., .F., , 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[]
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[])
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="">
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(, .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() + chr() + .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() + chr() + .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() + chr() + .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().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访问存取
另文,本文略……
VFP的数据策略:高级篇的更多相关文章
- VFP的数据策略:基础篇
VFP的数据策略:基础篇 概述 在VFP应用程序中,有很多方法可以访问非VFP数据(如SQL Server):远程视图.SQ LPassthrough.ADO.XML……本文件将审查不同机制的利弊,并 ...
- 【mongoDB高级篇③】综合实战(1): 分析国家地震数据
数据准备 下载国家地震数据 http://data.earthquake.cn/data/ 通过navicat导入到数据库,方便和mysql语句做对比 shard分片集群配置 # step 1 mkd ...
- 大数据系列博客之 --- 深入简出 Shell 脚本语言(高级篇)
首先声明,此系列shell系列博客分为四篇发布,分别是: 基础篇:https://www.cnblogs.com/lsy131479/p/9914747.html 提升篇:https://www.cn ...
- Spring Cloud Alibaba | Sentinel: 服务限流高级篇
目录 Spring Cloud Alibaba | Sentinel: 服务限流高级篇 1. 熔断降级 1.1 降级策略 2. 热点参数限流 2.1 项目依赖 2.2 热点参数规则 3. 系统自适应限 ...
- 数据库MySQL学习笔记高级篇
数据库MySQL学习笔记高级篇 写在前面 学习链接:数据库 MySQL 视频教程全集 1. mysql的架构介绍 mysql简介 概述 高级Mysql 完整的mysql优化需要很深的功底,大公司甚至有 ...
- Influx Sql系列教程九:query数据查询基本篇二
前面一篇介绍了influxdb中基本的查询操作,在结尾处提到了如果我们希望对查询的结果进行分组,排序,分页时,应该怎么操作,接下来我们看一下上面几个场景的支持 在开始本文之前,建议先阅读上篇博文: 1 ...
- redis学习笔记(详细)——高级篇
redis学习笔记(详细)--初级篇 redis学习笔记(详细)--高级篇 redis配置文件介绍 linux环境下配置大于编程 redis 的配置文件位于 Redis 安装目录下,文件名为 redi ...
- 多线程高级篇1 — JUC — 只弄到处理高并发集合问题
1.线程池 1.1).什么是线程池? 池( pool ),就是一个容器,所以线程池就是把多个线程对象放到一个容器中 1.2).如何创建线程池? 先来了解几个常识 Executor -- 这是一个接口( ...
- 4 - 基于ELK的ElasticSearch 7.8.x技术整理 - 高级篇( 续 ) - 更新完毕
0.前言 这里面一些理论和前面的知识点挂钩的,所以:建议看一下另外3篇知识内容 基础篇:https://www.cnblogs.com/xiegongzi/p/15684307.html java操作 ...
随机推荐
- select的disabled形式的数据,使用表单序列化方式无法将数据传到后台
之前博客里有讲述到使用表单序列化的方式传递数据到后台,那里是将数据为disabled形式的内容剔除掉了,所以为disabled的select肯定也是传不过去的. 解决方式: 1.在序列化表单方法之前将 ...
- 9.JavaSE之运算符
Java语言支持如下运算符operator:优先级() 算数运算符 :+ ,- ,* ,/ ,% ,++ ,-- 赋值运算符 := 关系运算符 :> ,< ,>= ,<= ,= ...
- 初探ASP.NET Core 3.x (4) - 项目的重要组成
目录 O 前请提要 I 启动部分 I.1 Program类 I.2 Startup类 I.2.1 这个类干什么呢?? I.2.2 特征?? I.3 appsettings.json I.4 launc ...
- SEVERE: Unable to process Jar entry [avassist xxxx.class]
<bean id="sqlSessionTemplate2" class="org.mybatis.spring.SqlSessionTemplate" ...
- 记录 解决ubuntu16.04 ‘E: 无法获得锁 /var/lib/dpkg/lock-frontend - open (11: 资源暂时不可用) ’
当运行sudo apt-get install/update/其他命令时,会出现如下提示: E: 无法获得锁 /var/lib/dpkg/lock-frontend - open (11: 资源暂时不 ...
- 超越队西柚考勤系统——beta冲刺1
这个作业属于哪个课程 http://edu.cnblogs.com/campus/xnsy/GeographicInformationScience 这个作业的要求在哪里 https://www.cn ...
- Client API Object Model - Form Context
FormContext 提供界面或者界面上控件的的引用. 比如说 quick view control, row in an editable grid 等等. Xrm.Page 和 getFormC ...
- [HNOI2008]Cards(dp,Burnside引理)
Burnside引理: 参考自 某大佬对Burnside引理和Polya定理的讲解 相关概念 群:在数学中,群表示一个拥有满足封闭性.满足结合律.有单位元.有逆元的二元运算的代数结构. 置换群:由有限 ...
- win10系统下自由切换桌面
说明: win10系统下自由切换桌面,确认在win10系统下操作进行. 方法: 1.快捷键:ctrl+win键(开始键)+方向键(左/右) 2.桌面最下面的状态栏,点击红框
- Day3-Python3基础-函数
本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 1.函数的定义 定义: 函数是指将一组语句的集 ...