VFP的数据策略:基础篇

时间:2022-07-14 05:13:53

VFP的数据策略:基础篇

作者:Doug Hennig  翻译:老瓷

概述

在VFP应用程序中,有很多方法可以访问非VFP数据(如SQL Server):
远程视图、SQL Passthrough(SPT--译者著)、ADO、XML……本文件将探讨不同机制的利弊,并讨论何时适合使用特定策略。我们还将研究VFP中一种令人兴奋的新技术CursorAdapter,它将使访问远程数据比早期版本更容易。

介绍

越来越多的VFP开发人员将数据存储在VFP表以外的其他地方,如SQL Server或Oracle。这有很多原因,包括VFP表的脆弱性(感知上和实际上)、安全性、数据库大小和公司标准。微软在每一个版本中都使非VFP数据的访问变得更加容易,甚至还鼓励它在VFP7 CD中加入MSDE(微软数据引擎,一个免费的、精简版的SQL Server)。
然而,访问后端数据库从未像使用VFP表那样容易。此外,还可使用多种机制来执行此操作:

  • 基于ODBC连接的远程视图。
  • SQL Passthrough(SPT)函数,如SQLCONNECT()、SQLEXEC()和SQLDISCONNECT(),它们也基于ODBC连接。
  • ActiveX数据对象(ADO),为数据库引擎的OLE DB提供程序提供面向对象的前端。
  • XML,这是一种轻量级、独立于平台的数据传输机制。

你应该选择哪种机制?答案(正如大多数VFP问题的答案)是“取决于”。它取决于许多因素,包括开发团队的经验和专业知识、基础设施、应用程序的需求、预期的未来需求等等。
让我们来看看这些机制中的每一种,包括它们的优点和缺点,需要注意的一些技术,以及我对每一种机制在整体方案中的位置的看法。我们还将了解我认为VFP 8中最大的新特性之一CursorAdapter类,以及它如何使通过ODBC、ADO或XML进行远程数据访问变得更加容易和一致。

远程视图

与本地视图一样,远程视图只是在数据库容器中定义的预定义SQL SELECT语句。不同之处在于,远程视图通过ODBC(即使它访问的数据是VFP)而不是本机访问数据。
可以使用create SQL view命令以编程方式创建远程视图,也可以使用视图设计器以可视化方式创建远程视图。在这两种情况下,都需要指定要使用的ODBC连接。连接可以是在系统上设置的ODBC数据源(DSN),也可以是已在同一数据库中定义的连接对象。下面是一个示例,它创建了SQL Server附带的示例Northwind数据库的Customers表的远程视图(我们将在示例中广泛使用该数据库)。这是从GENDBC生成的代码中摘录的;实际上还有很多代码可以设置连接、视图和字段的各种属性。

CREATE CONNECTION NORTHWINDCONNECTION ;
CONNSTRING "DSN=Northwind SQL; UID=sa; PWD=testdb; " + ;
"DATABASE=Northwind; TRUSTED_CONNECTION=No"
CREATE SQL VIEW "CUSTOMERSVIEW" ;
REMOTE CONNECT "NorthwindConnection" ;
AS SELECT * FROM dbo.Customers Customers
DBSetProp('CUSTOMERSVIEW', 'View', 'UpdateType', 1)
DBSetProp('CUSTOMERSVIEW', 'View', 'WhereType', 3)
DBSetProp('CUSTOMERSVIEW', 'View', 'FetchMemo', .T.)
DBSetProp('CUSTOMERSVIEW', 'View', 'SendUpdates', .T.)
DBSetProp('CUSTOMERSVIEW', 'View', 'Tables', 'dbo.Customers')
DBSetProp('CUSTOMERSVIEW.customerid', 'Field', 'KeyField', .T.)
DBSetProp('CUSTOMERSVIEW.customerid', 'Field', 'Updatable', .F.)
DBSetProp('CUSTOMERSVIEW.customerid', 'Field', 'UpdateName', ;
'dbo.Customers.CustomerID')
DBSetProp('CUSTOMERSVIEW.companyname', 'Field', 'Updatable', .T.)
DBSetProp('CUSTOMERSVIEW.companyname', 'Field', 'UpdateName', ;
'dbo.Customers.CompanyName')

可以升迁现有应用程序的最简单方法之一是使用升迁向导创建VFP表的SQL Server版本,然后创建新数据库(例如REMOTE.DBC),并在该数据库中创建与它们所基于的表同名的远程视图。这样,打开远程视图的代码将与打开本地表的代码完全相同,只是您将首先打开其他数据库。例如:

if oApp.lUseLocalData
open database Local
else
open database Remote
endif
use CUSTOMERS

如果在窗体和报表的数据环境中使用游标对象,则需要做一些额外的工作,因为这些对象引用了将视图拖放到数据环境中时选择的特定数据库。要处理此问题,请将类似于以下内容的代码放入DataEnvironment的BeforeOpenTables方法中:

local loObject
for each loObject in This.Objects
if upper(loObject.BaseClass) = 'CURSOR' and not empty(loObject.Database)
loObject.Database = iif(oApp.lUseLocalData, 'local.dbc', 'remote.dbc')
endif upper(loObject.BaseClass) = 'CURSOR' ..
next loObject

需要注意的一点是:当您打开一个视图时,VFP会尝试在DBC中锁定该视图的记录,即使它只是短暂的。这可能会在繁忙的应用程序中引起争用,在这些应用程序中,多个用户可能会尝试同时打开窗体。尽管有一些解决方法(将DBC复制到本地工作站并使用该方法,或者在VFP 7和更高版本中,使用SET reproces SYSTEM来增加锁争用的超时),但这是需要计划的。
关于偏僻的景色是不是一件好事,有很大的争议。如果您有兴趣阅读有关论点各方面的内容,请查看以下链接:
http://fox.wikis.com/wc.dll?Wiki~RemoteViews~VFP
http://fox.wikis.com/wc.dll?Wiki~MoreOnRemoteViews~VFP
http://fox.wikis.com/wc.dll?Wiki~Client/ServerDataAccessTechniques~VFP
http://fox.wikis.com/wc.dll?Wiki~Client/ServerTechniquesPerformance~VFP

优势

远程视图的优点是:

  • 您可以使用视图设计器可视化地创建远程视图。好吧,在VFP 8解决了视图设计器的许多已知问题(尤其是涉及两个以上表的复杂视图)之前,这不一定是一个优势,但即使在早期版本中,也可以非常快速和轻松地创建简单视图。直观地看到底层表中的所有字段,使用友好的界面轻松地设置SQL SELECT语句的各个部分,并使用复选框或其他UI元素快速设置视图的属性,这是非常好的。
  • 从语言的角度来看,远程视图就像表一样。因此,它们可以在任何地方使用:您可以使用它们,将它们添加到表单或报表的数据环境中,将它们绑定到网格,在扫描循环中处理它们,等等。使用其他一些技术,特别是ADO和XML,必须将结果集转换为VFP游标,然后才能在VFP中的许多地方使用它。
  • 与使用任何其他技术相比,将现有应用程序转换为使用远程视图更容易,特别是在已经使用本地视图的情况下。
  • 由于可以将远程视图添加到窗体或报表的数据环境中,因此可以利用DE提供的可视化支持:拖放字段或整个游标来自动创建控件,通过从“属性”窗口的组合框中选择控件来轻松地将控件绑定到字段,等等。另外,根据AutoOpenTables和OpenViews属性的设置,VFP将自动为您打开远程视图。
  • 使用更改更新后端很容易:假设视图的属性设置正确,只需调用TABLEUPDATE()。事务处理和更新冲突检测是内置的。
  • 远程视图在开发环境中很容易使用:只需使用然后浏览。

缺点

远程视图的缺点是:

  • 远程视图位于DBC中,因此必须在客户端系统上维护和安装另一组文件。
  • 由于远程视图的SQL SELECT语句是预定义的,因此不能动态更改它。虽然这对于典型的数据输入表单来说是很好的,但是对于查询和报表来说可能是一个问题。您可能需要从同一组数据创建多个视图,每个视图在所选字段、WHERE子句的结构等方面都有所不同。
  • 不能从远程视图调用存储过程,因此远程视图需要直接访问底层表。这可能是应用程序的数据库管理员遇到的问题;有些DBA认为,出于安全和其他原因,只能通过存储过程进行数据访问。另外,由于它们是在后端预编译的,存储过程的执行速度通常比SQL SELECT语句快得多。
  • 当使用TABLEUPDATE()将视图中的更改写入后端数据库时,您几乎没有能力(通过设置一些属性除外)控制VFP如何进行更新。
  • 与本地视图一样,如果使用SELECT*视图从特定表检索所有字段,并且该表在后端的结构发生更改,则该视图无效,必须重新创建。他们只使用ODBC,所以他们被困在直接数据连接的“客户机-服务器”模型中。他们不能利用ADO或XML的优点。
  • 如前所述,DBC锁争用是一个需要处理的问题。
  • 与其他类型的VFP游标一样,您不能将结果集传递到当前数据会话之外,更不用说传递到另一个应用程序或层。仅此限制就使得它们在n层应用程序中几乎毫无用处。
  • 在VFP 8允许您指定使用USE语句打开远程视图时要使用的连接句柄之前,您几乎没有能力管理应用程序使用的连接。
  • 用于远程视图的连接信息在DBC中以纯文本硬编码(注意前面显示的代码中的用户名和密码)。这意味着黑客可以很容易地发现你后端王国的密钥(比如用户名和密码),只需使用记事本打开DBC就可以了。从VFP 7开始,这并不是什么大问题,因为它允许您在使用USE命令打开远程视图时指定连接字符串,这意味着您可以在打开视图之前动态组合服务器、用户名和密码(可能来自加密信息)。

基本上,这归结为一个控制问题:远程视图使处理后端数据变得容易,但代价是限制了对它们的控制。

何时使用

远程视图实际上只适用于客户机-服务器、直接连接到数据的两层应用程序。我相信,从长远来看,如果使用n层设计开发,大多数应用程序将更加灵活、健壮,并且具有更长的保质期,因此,远程视图最适合于这样的情况:您正在对现有应用程序进行升迁,而不想重新设计/重新开发它,或者您的开发团队对其他技术没有太多经验。

SQL Passthrough(SPT)

VFP提供了许多函数,有时称为SQL passthrough(或SPT)函数,这些函数允许您访问后端数据库。SQLCONNECT()和SQLSTRINGCONNECT()连接到后端数据库引擎;这两个函数的区别在于,SQLCONNECT()需要现有的ODBC数据源(DSN)在用户系统上定义,而SQLSTRINGCONNECT()只是将必要的信息直接传递给ODBC,即无DSNless连接。如您所料,SQLDISCONNECT()从后端断开连接。SQLEXEC()向数据库引擎发送一个命令(如SQL SELECT语句),通常(但不一定,取决于命令)将返回的结果放入VFP游标中。
下面是一个打开到Northwind数据库的连接的示例(假设有一个名为“Northwind”的DSN定义了如何连接到此数据库),检索单个客户记录并将其显示在浏览窗口中,然后关闭连接。

lnHandle = sqlconnect('Northwind')
if lnHandle >
sqlexec(lnHandle, "select * from customers where customerid = 'ALFKI'")
browse
sqldisconnect(lnHandle)
else
aerror(laErrors)
messagebox('Could not connect: ' + laErrors[2])
endif lnHandle >

要改用无DSN连接,请将SQLCONNECT()语句替换为以下内容(将服务器名称、用户ID和密码替换为适当的值):

lnHandle = sqlstringconnect('Driver=SQL Server;Server=(local);' + ;
Database=Northwind;uid=sa;pwd=whatever')

DispLogin SQL设置控制ODBC是否显示登录对话框。尽管您可能认为让ODBC担心用户名和密码很方便,但您无法控制对话框的外观,也无法控制如果用户输入不正确的值会发生什么。相反,您最好在自己的VFP对话框中向用户请求适当的信息,然后将该信息传递给ODBC。如SQLCONNECT()函数的VFP帮助中所述,应该使用SQLSETPROP()将DispLogin设置为3。
与其让每个需要访问远程数据的组件管理自己的ODBC连接,不如使用一个对象,该对象的唯一职责是管理连接和访问数据。基于Custom的SFConnectionMgr就是一个例子。它有几个自定义属性,其中一些属性必须先设置,然后才能使用它连接到远程数据源。

属性 描述  
cDatabase 要连接到的数据库;如果填写了cDSN,则可以留空。
cDriver 要使用的ODBC驱动程序或OLE DB提供程序;如果填写了cDSN,则可以留空。
cDSN 要连接到的ODBC DSN;留空则使用无DSN连接。
cErrorMessage 发生任何错误的消息。
cPassword 数据源的密码;如果使用受信任的连接,则可以留空。
cServer 数据库所在的服务器;如果填写了cDSN,则可以留空。
cUserName 数据源的用户名;如果使用受信任的连接,则可以留空。
lConnected 如果已连接到数据源则为.T. 。

它有几个自定义方法:

方法 描述
Connect 连接到数据源。
Disconnect 断开数据源连接(Destroy调用,也可手动调用)。
Execute 对数据源执行语句。
GetConnection 返回连接句柄或ADO连接对象。
GetConnectionString 从各种连接属性(受保护)返回连接字符串。
HandleError 当发生错误时设置cErrorMessage属性(受保护)。

SFConnectionMgr不打算直接使用,但它是SFConnectionMgr ODBC和SFConnectionMgr ADO的子类,分别针对ODBC和ADO。这些子类重写大多数方法以提供所需的特定行为。让我们看看SFConnectionMgrODBC。

Init方法将当前的dislogin设置保存为自定义的ndislogin属性,并将其设置为3,这样就永远不会显示登录对话框。

This.nDispLogin = sqlgetprop(, 'DispLogin')
sqlsetprop(, 'DispLogin', 3)
dodefault()

Connect方法检查我们是否已经连接,然后如果在cDSN属性中指定了DSN,则使用SQLCONNECT()或生成连接字符串并使用SQLSTRINGCONNECT()函数。不管怎样,如果连接成功,nHandle属性都包含连接句柄,而lConnected是.T。如果连接失败,nHandle是0,lConnected是.F,而cErrorMessage包含有关出错的信息(在HandleError方法中设置,我们不会查看)。

* 如果尚未连接,请使用无DSN连接连接到指定的DSN或数据源。
local lcConnString
with This
if not .lConnected
if empty(.cDSN)
lcConnString = .GetConnectionString()
.nHandle = sqlstringconnect(lcConnString)
else
.nHandle = sqlconnect(.cDSN, .cUserName, .cPassword)
endif empty(.cDSN)
* 如果成功连接,则设置lConnected标志,否则获取错误信息。
.lConnected = .nHandle >
if not .lConnected
.nHandle =
.HandleError()
endif not .lConnected
endif not .lConnected
endwith
return This.lConnected

GetConnectionString从连接属性的值返回连接字符串。

local lcSpecifier, ;
lcConnString
with This
lcSpecifier = iif(upper(.cDriver) = 'SQL SERVER', 'database=', ;
'dbq=')
lcConnString = 'driver=' + .cDriver + ';' + ;
iif(empty(.cServer), '', 'server=' + .cServer + ';') + ;
iif(empty(.cUserName), '', 'uid=' + .cUserName + ';') + ;
iif(empty(.cPassword), '', 'pwd=' + .cPassword + ';') + ;
iif(empty(.cDatabase), '', lcSpecifier + .cDatabase + ';') + ;
'trusted_connection=' + iif(empty(.cUserName), 'yes', 'no')
endwith
return lcConnString

Execute方法对数据源执行一个语句(如SQL SELECT命令)。它首先调用Connect以确保我们有一个连接,然后使用SQLEXEC()函数将语句传递给数据源。

lparameters tcStatement, ;
tcCursor
local lcCursor, ;
llReturn
with This
.Connect()
if .lConnected
lcCursor = iif(vartype(tcCursor) = 'C' and not empty(tcCursor), ;
tcCursor, sys())
llReturn = sqlexec(.nHandle, tcStatement, lcCursor) >=
if not llReturn
.HandleError()
endif not llReturn
endif .lConnected
endwith
return llReturn

Disconnect方法断开与数据源的连接,并将nHandle设置为0,将lConnected设置为.F。

with This
if .lConnected
sqldisconnect(.nHandle)
.nHandle =
.lConnected = .F.
endif .lConnected
endwith

Destroy只需断开连接并恢复保存的discogin设置。

This.Disconnect()
sqlsetprop(, 'DispLogin', This.nDispLogin)
dodefault()

下面是一个示例(取自TestConnMgr.prg),它显示了如何使用SFConnectionMgrODBC。它首先连接到SQL Server Northwind数据库并获取所有客户记录,然后连接到Access Northwind数据库并再次检索所有客户记录。当然,本例使用硬编码连接信息;实际应用程序可能会将此信息存储在本地表、INI文件或Windows注册表中,以使其更加灵活。

loConnMgr = newobject('SFConnectionMgrODBC', 'SFRemote')
with loConnMgr
* 连接到SQL Server Northwind数据库并获取客户记录。
.cDriver = 'SQL Server'
.cServer = '(local)'
.cDatabase = 'Northwind'
.cUserName = 'sa'
.cPassword = ''
do case
case not .Connect()
messagebox(.cErrorMessage)
case .Execute('select * from customers')
browse
use
otherwise
messagebox(.cErrorMessage)
endcase
* 现在连接到Access Northwind数据库并获取客户记录。
.Disconnect()
.cDriver = 'Microsoft Access Driver (*.mdb)'
.cServer = ''
.cDatabase = 'd:\Program Files\Microsoft Visual Studio\VB98\Nwind.mdb'
.cUserName = ''
.cPassword = ''
do case
case not .Connect()
messagebox(.cErrorMessage)
case .Execute('select * from customers')
browse
use
otherwise
messagebox(.cErrorMessage)
endcase
endwith

优势

使用SPT的优点是:

  • 与远程视图相比,您在数据访问方面具有更大的灵活性,例如使用SQLEXEC()函数调用存储过程。
  • 您可以根据需要随时更改连接信息。例如,可以将用户名和密码存储为加密值,并且仅在将其用于SQLCONNECT()或SQLSTRINGCONNECT()函数之前对其进行解密。您还可以更改服务器甚至后端数据库引擎(例如,只需更改SQLSTRINGCONNECT()调用中指定的ODBC驱动程序,就可以在SQL Server和Access数据库之间切换)。如前所述,与以前的远程视图相比,这几乎没有优势,现在VFP 7允许您在USE命令上指定连接字符串。
  • 您可以根据需要更改SQL SELECT语句。例如,您可以轻松地更改字段列表、WHERE子句(例如更改涉及的字段或将其全部删除)、表等。
  • 您不需要DBC来使用SPT,因此不需要维护或安装任何东西,锁争用不是问题,而且您不必担心当后端表的结构更改时,SELECT*语句将变为无效。
  • 与远程视图一样,SPT调用的结果集是VFP游标,它可以在VFP中的任何地方使用,而不存在ADO和XML所具有的转换问题。
  • 尽管您必须自己编写代码(这将在“缺点”下进行更详细的讨论),但您可以更好地控制如何进行更新。例如,可以使用SQL SELECT语句来创建游标,但可以调用存储过程来更新后端表。
  • 你可以管理自己的关系。例如,您可能希望使用与前面讨论的类似的连接管理器来管理应用程序在一个位置使用的所有连接。

缺点

使用SPT的缺点是:

  • 这需要更多的工作,因为您必须对所有内容进行编码:创建和关闭连接、要执行的SQL SELECT语句等等。您没有像视图设计器这样好的可视化工具来显示哪些字段存在于后端的哪些表中。
  • 无法将SPT创建的游标可视化地添加到窗体或报表的数据环境中。相反,您必须对游标的打开进行编码(例如,在BeforeOpenTables方法中),您必须手动创建控件,并且您必须通过自己键入来填充绑定属性(例如,ControlSource)(输入别名和字段名时不要键入错误,否则表单将无法工作)。
  • 与开发环境中的远程视图相比,它们更难使用:您必须创建一个连接,然后使用SQLEXEC()调用来获取要查看的数据,而不是仅仅发出USE命令。如果您创建一组PRG来为自己完成工作(例如UseCustomers.prg,它打开并显示Customers表的内容),则可以使您的工作更轻松。您还可以使用SQL Server企业管理器(或类似的工具)检查表的结构和内容。您甚至可以创建一个DBC和一组只在开发环境中使用的远程视图,作为查看数据的快速方法。
  • 与远程视图和其他类型的VFP游标一样,不能在当前数据会话之外传递结果集。相反,您可能必须传递用于创建游标的信息(SQL SELECT语句或存储过程调用,甚至可能传递连接信息),并让另一个对象自己完成工作。
  • 使用SPT创建的游标可以更新,但您必须使用对SendUpdates、Tables、KeyFieldList、UpdateableFieldList和UpdateNameList属性的一系列CURSORSETPROP()调用来实现它们。另外,你必须自我管理事务处理和更新冲突检测。
  • 由于SPT游标不像远程视图那样定义,因此您不能像使用远程视图那样使用SPT轻松地在本地和远程数据之间切换(只需更改在窗体或报表中打开的视图)。
  • 与远程视图一样,SPT仅基于ODBC,因此不能利用ADO或XML的优点。

何时使用

与远程视图一样,SPT最适合于与数据直接连接的客户机-服务器两层应用程序。由于将现有应用程序转换为SPT要比远程视图或游标适配器(稍后我们将看到)做得更多,SPT最适合于实用程序、简单应用程序或窄焦点情况。

ADO

OLE DB和ADO是微软通用数据访问策略的一部分,在这种策略中,任何类型的数据都可以以任何格式存储在任何地方,而不仅仅是存储在本地服务器上的关系数据库中,可以供任何需要它的应用程序使用。OLE DB提供程序类似于ODBC驱动程序:它们提供了一种标准的、一致的访问数据源的方法。各种OLE DB提供程序可用于特定的DBMS(SQL Server、Oracle、Access/Jet等),而Microsoft为ODBC数据源提供OLE DB提供程序。

  • 连接:这是负责与数据源通信的对象。
  • 记录集:这相当于VFP游标:它具有定义的结构,包含数据集中的数据,并提供属性和方法来添加、删除或更新记录、从一个记录移动到另一个记录、筛选或排序数据以及更新数据源。
  • 命令:与简单的SELECT语句相比,该对象提供了执行更高级查询的方法,例如参数化查询和调用存储过程。

下面是一个示例(ADOExample.prg),它从Northwind数据库获取所有巴西客户,并显示客户ID和公司名称。注意,连接对象处理连接(记录集的ActiveConnection属性设置为连接对象),而记录集处理数据。

local loConn as ADODB.Connection, ;
loRS as ADODB.Recordset
loConn = createobject('ADODB.Connection')
loConn.ConnectionString = 'provider=SQLOLEDB.1;data source=(local);' + ;
'initial catalog=Northwind;uid=sa;pwd='
loConn.Open()
loRS = createobject('ADODB.Recordset')
loRS.ActiveConnection = loConn
loRS.LockType = && adLockOptimistic
loRS.CursorLocation = && adUseClient
loRS.CursorType = && adOpenStatic
loRS.Open("select * from customers where country='Brazil'")
lcCustomers = ''
do while not loRS.EOF
lcCustomers = lcCustomers + loRS.Fields('customerid').Value + chr(9) + ;
loRS.Fields('companyname').Value + chr(13)
loRS.MoveNext()
enddo while not loRS.EOF
messagebox(lcCustomers)
loRS.Close()
loConn.Close()

由于其面向对象的特性和功能,ADO一直是n层开发的首选数据访问机制(尽管随着XML变得越来越流行,这种情况正在迅速改变)。与ODBC不同,您不必直接连接到数据源。
有关ADO和在VFP中使用ADO的详细信息,请参阅John Petersen的“ADO Jumpstart For Visual FoxPro Developers”白皮书,该白皮书可从VFP主页(http://msdn.microsoft.com/vfoxpro;按照“技术资源”、“技术文章”和“com和ActiveX开发”链接访问文档)。

优势

使用ADO的优点是:

  • 与SPT一样,与调用存储过程等远程视图相比,在数据访问方面具有更大的灵活性。
  • 您可以根据需要动态更改连接信息,例如加密用户名和密码、更改服务器,甚至后端数据库引擎。
  • 您可以根据需要更改SQL SELECT语句。
  • 不涉及DBC。
  • 尽管在简单的场景中性能差异并不显著(事实上,在我的测试中,ODBC比ADO快),但ADO在Web服务器等大量使用的应用程序中更具可伸缩性。
  • 与VFP游标不同,ADO对象可以在当前数据会话之外传递给应用程序中的另一个组件、另一个应用程序或COM对象(例如Excel或中间层组件)或另一台计算机。您甚至可以使用远程数据服务(RDS)通过HTTP发送它们;有关更多信息,请参阅John Petersen的白皮书,尽管当涉及防火墙时这是一个问题。
  • ADO是面向对象的,因此您可以像处理对象一样处理数据。
  • 根据ADO记录集的设置方式,ADO记录集可以自动更新,而无需任何额外工作(调用Update或UpdateBatch方法除外)。事务处理和更新冲突检测是内置的。
  • 你可以管理自己的关系(?连接)。
  • 您可以轻松地将记录集持久化为本地文件,然后重新加载并继续工作,最后更新后端数据源。这使得它对于“road warrior”应用程序的选择要比远程视图或SPT好得多。

缺点

ADO的缺点是:

  • 这需要更多的工作,因为您必须对所有内容进行编码:创建和关闭连接、要执行的SQL SELECT语句等等。您没有像视图设计器这样好的可视化工具来显示哪些字段存在于后端的哪些表中。
  • ADO记录集不是VFP游标,因此不能在需要游标的地方使用,例如网格和报表。VFPCOM实用程序(可从VFP主页http://msdn.microsoft.com/vfoxpro下载)中有一些函数可以将记录集转换为游标,反之亦然,但使用这些函数可能会影响性能,特别是对于大型数据集,而且它们已知某些数据类型存在问题。
  • 没有对ADO记录集的可视化支持,因此必须对其创建和打开进行编码,必须手动创建控件,并且必须通过自己键入来填写绑定属性(如ControlSource)。这比SPT的工作还要多,因为语法不仅仅是CURSOR.FIELD,而是RecordSet.Fields('FieldName').Value。
  • 它们是开发环境中最难使用的技术,因为您必须对所有内容进行编码:建立连接、检索数据、在记录之间来回移动。您甚至无法通过浏览查看结果集的外观(除非使用VFPCOM或其他方法将记录集转换为游标)。
  • 与使用ODBC创建的游标相比,ADO的学习曲线更大。
  • 您可能需要确保客户端安装了最新版本的Microsoft数据访问组件(MDAC),以确保其OLEDB和ADO版本与您的应用程序所需的相匹配。
  • ADO是一种仅限Windows的技术。

何时使用

在其他组件之间来回传递数据时,ADO很容易使用。例如,VFP COM对象的方法可以很容易地将ADO记录集返回到Excel VBA代码,然后该代码可以处理和显示结果。
如果您正在使用n层体系结构设计应用程序,那么ADO可能是一个不错的选择,如果您已经熟悉它或者已经为它准备好了基础设施。然而,XML正迅速成为n层应用程序的首选机制,因此我希望ADO在这方面的应用越来越少。

XML

XML(可扩展标记语言)并不是一种真正的数据访问机制;它实际上是一种传输技术。数据被打包成具有结构化格式的文本,然后运到某个地方。然而,由于XML只是文本,所以它比其他技术有很多优势(我们稍后将讨论)。
几年前,微软“发现”了XML,并从那时起在几乎所有地方都实现了它。内置在.NET框架ADO.NET中的数据访问技术以XML为基础(事实上,一个简单的观点是ADO.NET实际上只是一组包装类,通过OOP接口公开XML数据)。基于SOAP(简单对象访问协议)的Web服务使用XML作为通信和数据传输的基础。XML甚至正迅速成为n层应用程序的首选数据传输机制,长期以来,n层应用程序更青睐ADO。
VFP 7添加了几个与XML一起工作的函数:XMLTOCURSOR(),它将XML转换为游标;CURSORTOXML(),它将执行相反的操作;XMLUPDATEGRAM(),它将updategram(以特定格式表示对数据的更改的XML)从对游标的更改中生成。下面是一些VFP代码(取自XMLExample1.prg),展示了VFP如何处理XML:

* 获取ALFKI客户的信息并显示原始XML。
close databases all
lcXML = GetCustomerByID('ALFKI')
strtofile(lcXML, 'ALFKI.XML')
modify file ALFKI.XML
erase ALFKI.XML
*将其放入游标中,浏览并进行更改,然后查看updategram。
xmltocursor(lcXML, 'CUSTOMERS')
set multilocks on
cursorsetprop('Buffering', 5)
browse
lcUpdate = xmlupdategram()
strtofile(lcUpdate, 'UPDATE.XML')
modify file UPDATE.XML
* 通过设置KeyFieldList属性,updategram将只包含key和changed字段。
cursorsetprop('KeyFieldList', 'cust_id')
lcUpdate = xmlupdategram()
strtofile(lcUpdate, 'UPDATE.XML')
modify file UPDATE.XML
erase UPDATE.XML
close databases all
* 此函数将指定的客户记录返回为XML。
function GetCustomerByID(tcCustomerID)
local lcXML
open database (_samples + 'data\testdata')
select * from customer where cust_id = tcCustomerID into cursor Temp
cursortoxml('Temp', 'lcXML', 1, 8, 0, '1')
use in Temp
use in Customer
return lcXML

注意,在版本8之前,虽然VFP可以创建一个XML updategram,但它没有一个简单的方法来使用它(即,更新VFP表)。Visual FoxPro MVP Alex Feldstein为此编写了一个例程(http://fox.wikis.com/wc.dll?Wiki~XMLUpdateGramParse),但在VFP 8中,可以使用新的XMLAdapter类的实例来完成此操作(我们将在“VFP:Advanced中的数据策略”文档中查看该类)。
下面是一个示例(XMLExample2.prg),它使用SQLXML通过Web服务器从SQL Server获取客户记录(我们将在高级篇中更详细地讨论SQLXML),然后将更改发送回。

* 获取ALFKI客户的信息并显示原始XML。
close databases all
lcXML = GetNWCustomerByID('ALFKI')
strtofile(lcXML, 'ALFKI.XML')
modify file ALFKI.XML
erase ALFKI.XML
* 将其放入游标中,浏览并进行更改。
xmltocursor(lcXML, 'CUSTOMERS')
cursorsetprop('KeyFieldList', 'customerid')
set multilocks on
cursorsetprop('Buffering', 5)
browse
* 获取updategram并将更改保存到SQL Server。
lcUpdate = xmlupdategram()
SaveNWCustomers(lcUpdate)
use
* 此函数使用SQLXML将指定的客户记录获取为XML。
function GetNWCustomerByID(tcCustomerID)
local loXML as MSXML2.XMLHTTP
loXML = createobject('MSXML2.XMLHTTP')
loXML.open('POST', 'http://localhost/northwind/template/ ' + ;
'customersbyid.xml?customerid=' + tcCustomerID, .F.)
loXML.setRequestHeader('Content-type', 'text/xml')
loXML.send()
return loXML.responseText
* 此函数使用SQLXML将指定的客户记录获取为XML。
function SaveNWCustomers(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)

优势

使用XML有很多好处:

  • 与远程视图相比,您具有相同的优势,例如没有DBC和易于更改的SQL SELECT语句,就像SPT和ADO一样。
  • 由于XML并不是一种真正的数据访问机制,因此在使用XML进行数据访问时具有最大的灵活性。例如,可以使用VFP CURSORTOXML()函数、Web服务、中间层组件、ADO.NET数据集、XMLHTTP和许多其他机制将数据从一个位置获取到另一个位置。
  • 因为XML只是文本,所以可以在任何地方传递,甚至可以通过防火墙(这是ADO的一个问题)。
  • XML DOM对象为XML数据提供了一个面向对象的接口。
  • XML很容易持久化到任何地方:文件、表中的备注字段等。
  • XML完全独立于平台/操作系统。
  • XML是大众可读的,与其他机制的二进制格式不同。

缺点

使用XML还有一些缺点:

  • 尽管XML的格式很简单,但与其他机制相比,它的学习曲线更大。XML DOM对象有自己的对象模型,有各种新技术(和缩写!)要学,像模式、XSLT、XPath、XDR和XQuery。
  • 同一组数据在XML中可能比在其他机制中大很多,因为每个元素都有开始和结束标记。例如,VFP的CUSTOMER示例表的DBF文件26257字节长而XML为40586字节长。
  • 虽然CURSORTOXML()速度很快,但使用XML DOM对象进行工作的XMLTOCURSOR()速度可能会很慢,尤其是在数据量很大的情况下。
  • XML标准仍在不断发展。

何时使用

XML有很多优点,包括存储配置设置、在应用程序组件之间传递少量数据、在备注字段中存储结构化数据等等,XML非常适合于n层应用程序,因为它易于传输(在组件之间或穿墙)和转换到数据集(如VFP游标)或从中转换。使用XML Updategrams(和更新的Diffgrams),可以限制传输的数据量。如果您正在启动新的n层项目,这显然是要使用的数据访问机制。

CursorAdapter

你可能注意到的一点是,我们所研究的每一种机制都与其他机制完全不同。这意味着每种机制都有一个新的学习曲线,将现有应用程序从一种机制转换为另一种机制是一项非常重要的任务。
在我看来,CursorAdapter是VFP 8中最大的新特性之一。我觉得他们这么酷的原因是:

  • 它们使使用ODBC、ADO或XML变得容易,即使您不太熟悉这些技术。
  • 它们为远程数据提供了一致的接口,而不管您选择何种机制。
  • 它们使从一种机制切换到另一种机制变得容易。

这是最后一点的例子。假设您有一个应用程序使用带有CursorAdapter的ODBC来访问SQL Server数据,出于某种原因,您希望改为使用ADO。您只需更改CursorAdapters的DataSourceType并更改到后端数据库的连接,就完成了。应用程序中的其他组件既不知道也不关心这一点;它们仍然看到同一个游标,而不管用于访问数据的机制如何。
我们将仔细查看高级文档中的CursorAdapter。不过,在此期间,这里有一个示例(CursorAdapterExample.prg),它从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的优点本质上是所有其他技术的结合。

  • 根据它的设置方式(例如,如果它是完全独立的),从CursorAdapter子类打开游标几乎和打开远程视图一样简单:只需实例化子类并调用CursorFill方法(甚至可以从Init调用它,使其成为一个单步操作)。
  • 将现有应用程序转换为使用CursorAdapter比使用SPT创建的游标更容易。
  • 与远程视图一样,您可以将CursorAdapter添加到窗体或报表的数据环境中,并利用DE提供的可视化支持:拖放字段以自动创建控件,通过从“属性”窗口的组合框中选择控件来轻松将控件绑定到字段,等等。
  • 更改后端的变化很容易:假设视图的属性设置正确,只需调用TABLEUPDATE()。
  • 因为CursorAdapter创建的结果集是一个VFP游标,所以它们可以在VFP中的任何位置使用:在网格中、报告中、在扫描循环中处理,等等。即使数据源来自ADO和XML,这也是正确的,因为CursorAdapter会自动为您处理与游标之间的转换。
  • 在数据访问方面有很大的灵活性,例如调用存储过程或中间层对象。
  • 您可以根据需要动态更改连接信息。
  • 您可以根据需要更改SQL SELECT语句。
  • 你不需要DBC。
  • 它们可以与ODBC、ADO、XML或本机表一起工作,允许您根据需要利用这些技术中的任何一种,甚至是交换技术的优点。
  • 尽管您必须自己编写代码(这将在“缺点”一节中进行更详细的讨论),但是您可以更好地控制如何进行更新。例如,可以使用SQL SELECT语句来创建游标,但可以调用存储过程来更新后端表。
  • 你可以管理自己的连接(?关系)。

缺点

CursorAdapter没有太多缺点:

  • 虽然CursorAdapter生成器是一个很好的辅助工具,但是您不能使用像视图设计器这样好的可视化工具来创建CursorAdapter。
  • 与其他类型的VFP游标一样,不能在当前数据会话之外传递结果集。但是,由于CursorAdapter实际上是用于UI层的,所以这不是什么大问题。
  • 在报告中使用它们很难;我们将在高级篇中更详细地讨论这一点。
  • 像所有新技术一样,必须掌握一条学习曲线。

何时使用

因为CursorAdapter创建VFP游标,所以您不太可能在n层应用程序的中间层使用它们。然而,在UI层中,我看到CursorAdapter替换了所有远程数据访问的其他技术,甚至在将来可能会被升级的新应用程序中替换了Cursor。

总结

本文讨论了ODBC(无论是远程视图还是SQL Passthrough)、ADO和XML作为访问非VFP数据(如SQL Server或Oracle)的方法的优缺点。通常,您应该为特定应用程序选择哪种机制取决于许多因素。然而,在VFP 8中使用新的CursorAdapter技术可以更容易地过渡到远程数据访问,并且在需要时更容易在机制之间切换。
在“VFP的数据策略:高级篇”文档中,我们将详细讨论CursorAdapter类,查看使用ODBC、ADO和XML访问本机数据或非VFP数据的细节。我们还将研究如何创建可重用的数据类,并讨论如何在报表中使用CursorAdapter。

作者介绍:

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)。