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”页面允许您维护DataEnvironmentCursorAdapter成员(游标对象不会在生成器中显示,也不能添加它们)。Add按钮允许您向DataEnvironment添加CursorAdapter子类,而New则创建一个新的基类CursorAdapterRemove删除Select CursorAdapterBuilder为所选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访问存取

另文,本文略……

posted @ 2020-02-24 19:04  老瓷  阅读(2594)  评论(2编辑  收藏  举报