VFP的数据策略:高级篇

VFP的数据策略:高级篇html

做者:Doug Hennig  翻译:老瓷node

引语

在“VFP中的数据策略:基础篇”一文中,咱们研究了VFP应用程序中访问非VFP数据(如SQL Server)的不一样机制:远程视图、SQL Passthrough、ADO、XML和VFP 8中添加的CursorAdapter类。在本文中,咱们将更详细地讨论CursorAdapter,并讨论可重用数据类的概念。此外,咱们将简要介绍新的XMLAdapter基类,并了解它如何帮助与其余源(如ADO.NET)交换数据。web

CursorAdapter

在我看来,CursorAdapter是VFP 8中最大的新特性之一。我以为他们这么酷的缘由是:sql

  • 使得使用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访问存取

另文,本文略……

相关文章
相关标签/搜索