一个使用说明,关于cursoradapter类的
作者:四维
vfp8最激动人心的变化是 CursorAdapter 类,它为不同的数据源提供通用的数据接口。
下面介绍怎样用 CursorAdapter 改变在 VFP 8 中连接数据的方式,包括,native tables, ODBC, OLE DB, XML. 。
CursorAdapter 类是 VFP 8 开发组的最给人印象深刻的成就之一。 它将会改变许多开发者连接各种不同的数据来源的方式。 开发小组在 VFP 存取数据的方式方面作了重要改变,将本地和远程数据连接方式进行了统一。另外,创建 CursorAdapter 类对那些已经熟练使用视图和SPT人来说并不费力。对使用ADO RecordSets 或XML可扩展标示语言文件的人也不费力。
CursorAdapter 类的独特之处在于,它是第一个提供本地游标、ODBC、ADO、XML数据源连接的基类,在一个类里面实现了所有连接功能。换句话说,要将ODBC数据源,ADO RecordSet 或XML可扩展标示语言文件翻译成一个 VFP 游标,CursorAdapter 类完全可以胜任。
你或许会说 CursorAdapter 是较早版本中本地视图和远程视图技术的替代 (注意: VFP 8中仍然保留这些功能). 但是在一些情形中,它也代替了SPT, 以及减少了使用ADO和XML时的代码量,可以直接使用ADO和XML。
CursorAdapter 的最大好处是,需要在同一个程序内连接到多个数据源的时候,它为你提供方便。举例说,如果你的程序大部分数据来自 SQL server,但是同时需要与XML可扩展标示语言连接,CursorAdapter 可以整合这两种情况,使程序取回的数据作为VFP的游标。
另外的一个例子是数据现在被储存在 VFP 表中 , 但是计划要移到一个数据库服务器 , 比如 SQL server或Oracle。 你需要先建立一组 VFP CursorAdapter 类,必要的时候以 SQL server以外的数据库代替这些类。
但是,就象我们能跑之前必须学会走一样,先概览一下 CursorAdapter 类和它的特性。 然后,使用 CursorAdapter 类来设计数据类是比较容易的。
建立第一个 CursorAdapter 类,象其他类一样,学习怎样使用它的最好办法是了解建立过程。第一次建立这些类的时候复杂程度低一些,我们开始用 CursorAdapter 类存取 VFP 本地的数据。 这很象使用本地视图取回 VFP 本地表的数据。 稍后在这一篇文章中,我们将会使用另一个 CursorAdapter 类连接到 SQL server数据库,ODBC和XML。
首先,你有二个方法建立 CursorAdapter 。 你能使用数据环境建立,也可以手动通过程序或类设计器来创建一个经过一个CursorAdapter类 。 这一个例子将会使用数据环境建立;较迟的例子将会手动建立.
如果你不熟悉 VFP 8 变化后的数据环境, 你可能认为在设计环境下创建的 CursorAdapter 只能在表单中使用,不能用于类。 然而, VFP 8 中已经改善了设计环境,因此不需要在表单中就可以创建。
用creat class命令创建一个新的数据环境类。
此主题相关图片如下:
确定从下拉列表中选择based on数据环境类(dataenvironment) 。类名为Tests,所属类库名为tests.vcx.
此主题相关图片如下:
创建的类在类设计器中出现后,右键单击Data Environment ,选择builder,起动数据环境建立向导。
此主题相关图片如下:
在数据源类型项下,注意可选的选项。 因为第一个例子将会连接到本地的 VFP 表,选择native。 选择完以后, 使用‘省略号’按钮选择 Northwind 数据库(我这里是gzdata数据库)。 (默认位置是 c:\ program files\microsoft visual foxpro 8\ sample\northwind\ northwind.dbc)
此主题相关图片如下:
下一步,点cursors 页, 它初始值是空的。 在列表框中,选择new按钮用 CursorAdapter 设计器创建一个新的 CursorAdapter 类。
此主题相关图片如下:
首先,你应该看看Properties页,这里提供选项来选择类的名字和由类产生的游标的别名。
确定提供一个不同于表名字的别名,避免产生混乱。 在这里,使用 caCustomer 做类名, cCustomer 作为别名。 如果想让这个类用和数据环境一样的数据源,应该选择 "Use DataEnvironment data source" 。 注意你可以为 CursorAdapter 设置不同的数据源, 允许你在不同的类之间整合数据源.( 例如一个类使用ODBC数据源,另一个类使用XML数据源)
此主题相关图片如下:
要定义CursorAdapter 如何返回来自数据源的数据,使用设计器中的Data Access 页。 按build按钮激活一个对话框,可以选择游标包含的字段。
此主题相关图片如下:
在这个例子中,选择Customers表, 然后选择Customers.*。 点击向右的箭头移动选择项, 然后点ok。
此主题相关图片如下:
这为你建立下列SQL语句:
select CUSTOMERS.* from CUSTOMERS
如果你想添加过滤器,连接, 或其他条件到查询,你可以在列表框中直接键入。 如果你想建立带参数的查询,有一些选项,在本文后面介绍。 现在, 让我们添加WHERE子句:
select CUSTOMERS.* from CUSTOMERS where
companyname like 'C%'
此主题相关图片如下:
这里可以看出源表和游标的不同,因为只有少数记录符合WHERE子句。
在第二个编辑框(schema)中已经为为你创建了字段列表。通常在继续以前花几分钟看看字段顺序是否符合你的习惯是有好处的
在这一页的下半部分有数据包设置对话框(data fetching),用来设置怎样处理远程数据包,当用vfp作为数据源的时候,这里的设置不发挥作用。(如上图)这里我们保留默认设置,稍后再讲述具体细节。在本页的底部附近是缓冲模式设定, 允许你设置任何被关联的表单的缓冲模式。有两个选项:开放式行缓冲和开放式表缓冲。
此主题相关图片如下:
通常,你使用开放的表缓冲模式,除非你有特殊要求使用行缓冲模式。在这个例子中设置为开放式的表缓冲。最后,“break on error”控制CursorAdapter类怎样来处理错误。默认设置是类(class)自行捕获错误,并且允许你用aerror()函数捕获这些错误。选定这个设置,CursorAdapter类内部不管发生什么错误,vfp都会出现错误信息。也就是说,你需要使用ON ERROR命令或者‘类’的ERROR事件来排除不需要报错的情况。通常情况下不选这项设置,以便程序能处理任何发生的例外情况。
最后一页 (auto update) 配置如何更新源表。在通常情况下,选择“自动更新(auto-update)”和“更新所有字段(update all fields)”。
此主题相关图片如下:
这将使cursoradaper类对游标(cursor)中数据的任何改变自动建立适当的更新、插入、删除机制。然而,你必须选择游标中的主关键字段,以便这些机制(更新、删除、插入)唯一的识别源表中的记录。在本例中,CustomerID是关键字段。因此,需要在其前面大上‘对号’。其他的设置暂时保留默认值。具体设置办法在本文后面讲述。设置完cursoradaper后,点击“ok”按钮,回到数据环境设置。此时,你应该在左边列表框中能看到caCustomer类, 在右边看到细节。如果你想更改这个类,你可以随时用数据环境‘builder’更改,选择需要更改的CursorAdapter 类,然后点击builder按钮。
存取 VFP 数据
此时,你可以测试数据环境,看看是否能取回在 CursorAdapter 的指令中筛选的数据。 使用命令窗户,例示 DE 类而且唤起 OpenTables 方法:
lo = NewObject("deTest","Tests.vcx")
? lo.OpenTables()
BROWSE
一个特殊情况是,CursorAdapter的游标连接到其它对象,如果你毁坏了指向CursorAdapter类的对象,会丢失游标和其中的记录。这就是说,你必须确保CursorAdapter对象参数在你打算存取的关联游标的范围内
编辑 VFP 数据
现在, 让我们看看是否能够更新游标,并且把更新准确地发送到源表。在命令窗口测试以下命令:
REPLACE contactname WITH 'My Name Here'
?TABLEUPDATE()
SELECT customers
BROWSE
浏览customers别名,你会发现修改过的记录已经更新到源表中。 如果你在发送replace命令之前没有移动记录指针,客户ID中 'CACTU' 所在的记录被修改。 不管你修改哪条记录, 这证明 CursorAdapter 是能够被更新,而且更新能够准确地被发送到源表。
让我们打开你刚刚测试的数据环境类,这不只是一个练习—它是一个很棒的方法学习当你决定在数据环境以外建立自己的类的时候,该如何正确地配置一个 CursorAdapter 类。
虽然数据环境有一些属性改变和一个方法, 我们实际上对那些改变不感兴趣。看一下下拉列表框中的属性列表,选择 caCustomer 类,看看建立 CursorAdapter 类的时候需要进行的设置。 表 1 概述被builder和每个 PEM 所做改变。
所有的属性包含在 "see Init" ,通过INIT方法中的代码来设置这些属性。 那一段代码显示在list 1中。
在builder设置完以后,看看一些属性是怎样设置的,这是最好的学习方法。你可以在这里或者通过builder改变设置值。然而,如果在这里改变设置值,你需要冒破坏一些功能的风险,因为你在这里改变的属性有可能在builder里不会发生改变。
不管怎样,你能在 Init() 代码中看到 SelectCmd 属性是如何叙述的, 同样在 KeyFieldList , UpdatableFieldList 和 UpdateNameList中也可以看到。 特别注意 UpdateNameList 属性—这个属性列出游标的每一个字段以及源表中对应的字段(带表名称)。
当从头创建你自己的 CursorAdapter 类的时候,你可能想在这个列表中省略表名字。 然而,如果不使用精确的格式,你的更新将会失败, 但是没有错误提示。 在后面讲不通过builder建立一个类的时候我将再说这一点。
前面我说过 CursorAdapter使用本地的数据源的时候 , 本质上是一个替代了本地视图。如果你曾经用过本地视图,你可以发现类似的地方: 生成一个SQL select语句,定义哪些字段将被更新,设置关键字段,剩下的事情让 VFP 来做。 一旦游标中取回数据,可以使用 TableUpdate() 发送更新到源表,而且 VFP 自动地建立必要的update,insert,delete语句。
还是以上面的例子, 取消cCustomer 别名中contact字段被替换的数据。通过TableUpdate , VFP 自动地产生 (并提交)下列更新命令尝试更新:
UPDATE customers ;
SET CONTACTNAME=ccustomer.contactname ;
WHERE ;
CUSTOMERID=OLDVAL('customerid','ccustomer');
AND ;
CONTACTNAME=OLDVAL('contactname','ccustomer')
VFP 能够根据CursorAdapter的KeyFieldList属性和部分UpdateNameList属性中的值产生where子句。它通过子句记录那些已经被改变或添加的记录,保证你不会更新那些别人已经更新过的记录。注意,这是因为我们在它的WhereType 属性中保留默认值 "key fields and any modified fields."
在本文中出现table1,list1等,可惜我当时没下载,现在已经找不到了,不过不会对理解本文有什么影响,下次发“cursoradapter类的错误处理"
错误处理
明显的,当用 CursorAdapter 更新数据时候 ,并不能完全如你所愿。你知道,多种原因可以导致TableUpdate 更新失败, 例如更新冲突或记录加锁失败。你必须对 CursorAdapter 类做特别处理来发现这些问题吗? 答案是,"视情况而定 ."
我们来建立一个简单的更新问题:给CursorAdapter尝试更新的一条记录加锁。如果类设计器仍然是开着的,关掉它。然后, 像前面做过的一样,用 NewObject 函数启动detest类,而且启动 OpenTables 方法。浏览游标以便你能看到数据,但是先不要改变任何东西。
现在打开 VFP 8 的第二个实例,这样就能够加锁记录。 在命令窗口中运行下列语句加锁你将尝试更新的记录:
OPEN DATABASE (HOME(2)+"Northwind\northwind.dbc")
USE customers
LOCATE FOR customerid = 'CACTU'
?RLOCK()
你应该能返回.T.,表明记录确实被 VFP 的这一个实例加锁。
回到 VFP 的第一个实例,在命令窗口中运行下面的代码:
REPLACE contactname WITH 'updated'
SET REPROCESS TO 2 SECONDS
?TABLEUPDATE()
在这种情况, TableUpdate 返回.F.,表现记录锁阻止更新成功。 如果你用 AERROR() 捕获错误而且显示错误结果, 你会看到错误信息 "记录没加锁"。 这意味着如果你对设置缓冲的表直接操作而不是一个游标,你能用同样的办法处理错误。
不幸地,不是所有的预期错误都会这样表现出来。需要特别注意的是更新冲突,比如一个使用者企图覆盖其他人所作的修改。想看看这种行为的结果,在当前的 VFP 实例中(CursorAdapter 正在使用)运行下面的代码:
?TABLEREVERT(.T.)
REPLACE contactname WITH 'client 1'
现在转变在到第二个例证并且发行下列指令:
CLOSE DATABASES all
OPEN DATABASE (HOME(2) + "Northwind\northwind.dbc")
USE customers
LOCATE FOR customerid = 'CACTU'
REPLACE contactname WITH 'client 2'
BROWSE
返回第一个实例, 尝试用 TableUpdate 发送更新:
?TABLEUPDATE()
在这种情况, TableUpdate 错误地返回.T.,使你相信更新成功了! 然而, 事实上并没有成功,而且这可以被CursorAdapter的CursorRefresh() 方法证明, 用下列代码:
?TABLEREVERT(.T.)
?lo.caCustomer.CursorRefresh()
CursorRefresh 方法告诉 CursorAdapter 再运行 SelectCmd 里的语句,并且取回来自源表的最新数据。 对ContactName 字段的测试说明 CursorAdapter 根本没更新字段值!
解决这一个问题的最简单的方法要利用 CursorAdapter 的 AfterUpdate 方法。这一个方法在 TableUpdate 发送保存每条记录的请求后被执行。注意这个‘方法’只对当前记录有效。 如果记录是新的,或者记录已经被删除,会激活AfterInsert 或 AfterDelete 方法。
AfterUpdate 方法捕获一些参数, 包括最初的字段状态,是否被强制更改, 和作为更新的命令。 最后一个参数 , lResult,是我们这部分主题最重要的,因为它告诉我们更新过程是否成功。
使用的另一个重要角色解决更新冲突问题的是系统变量 _tally, 它告诉我们最后一次操作影响了多少记录。 因此,如果 lResult 放回成功的, 但是 _tally是零,那么说明没有记录被更新,那么你可以判定这种情况是一个更新冲突。
简单说,解决这一个问题的简单方法是把下列代码加入 CursorAdapter 类的 AfterUpdate 方法:
LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd, lResult
IF lResult AND _TALLY = 0 THEN
ERROR 1585 && update conflict
ENDIF
有趣的是你在屏幕上看不到错误信息;错误信息被 TableUpdate “限制住了”,这就迫使你使用 AError 函数分析更新失败的原因。出现这样的现象是因为 BreakonError 属性保留了默认值 ,也就是错误不引起程序中断。如果你把这个属性设为“ture",那么"更新冲突" 错误信息就会出现,如果作了定义,你的ON ERROR句柄会被运行。
这一个更新冲突问题是为 CursorAdapter设计的,因为 VFP 8 没有自动发现这个问题方法。因此,当不用本地数据源的时候,这个代码( 或相似的代码)会用在你的CursorAdapter类设计中。
CursorAdapter with ODBC
现在你已经有了一定的基础, 让我们继续看看当后台数据库用 SQL server的时候怎样进行设置。我们将从使用 VFP 通过ODBC连接 SQL server上的 Northwind 数据库开始。同样, 让我们从头建立 CursorAdapter 以便可以看见类需要设置的各个方面。
首先,用下列指令在类设计器中建立一个新类:
CREATE CLASS caODBC OF tests as CursorAdapter
这里需要设置的最重要的属性是 DataSourceType 属性。 因为我们想通过ODBC连接 SQL server,将这一属性设置为ODBC。当使用这种连接方式的时候 , DataSource 属性需要有一个确切的连接句柄,可以用 SQLConnect 或 SQLConnectString 函数建立。 在任一情况,这些功能应该在 CursorAdapter 类的 Init 方法中使用下列代码:
LOCAL lcConnStr, lnConn
** string assumes trusted connection (integrated security)
lcConnStr = "Driver=SQL Server;Server=(local);DATABASE=Northwind"
lnConn = SQLSTRINGCONNECT(lcConnStr)
IF lnConn > 0 THEN
THIS.DATASOURCE = lnConn
ELSE
** unable to connect
ENDIF
连接字符串假定你使用可信赖的关联到 SQL server;如果你使用安全的连接到 SQL server,把 "uid"= 和 "pwd"= 串加入连接句柄指定使用者名称和密码。 任何一种情况下,返回的是连接柄 , 或错误发生时的否定值。 这一个连接柄被指定为 DataSource 属性以便 CursorAdapter 知道该到哪里声明。
下一个步骤是建立 SelectCmd 以便 CursorAdapter 知道需要从数据源取回什么数据。还有一个最好的放置地方是 Init 方法, 因为属性表限制字符串的长度。 把下列代码加入 Init 方法中,设定 DataSource 属性代码之后, 取回公司名字中以 "C" 开头的客户列表:
This.SelectCmd = "SELECT " + ;
"CustomerID, CompanyName, ContactName, " + ;
"Address, City, Region, Country " + ;
"FROM Customers WHERE CompanyName LIKE 'C%'"
然后,你应该告诉 CursorAdapter 通过调用CursorFill 方法填充被关联的游标。 你可以省略这一个调用,并且手动从类之外调用, 或放在 Init 方法中,以便它能够自动填充游标。在Init 方法中设置 SelectCmd 之后,可以用 This.CursorFill()调用。
最后,你应该在类的Destroy方法中加上一些代码,以便在对象从内存中释放以后减少到服务器的连接。 没有这些代码的话,每一个新的实例将会产生一个到服务器的新连接, 并且不会释放它:
IF this.DataSource > 0 THEN
SQLDISCONNECT(this.DataSource)
ENDIF
藉由这些变化,你拥有一个实用的 CursorAdapter 类,它能够生成一个只读的游标。在允许更新之前,测试一下类,确保可用,并能准确取回数据。用下列代码测试:
lo = NEWOBJECT("caODBC","tests")
BROWSE
注意,你不需要像数据环境那样调用 OpenTables 方法。这是因为你把 CursorFill 方法直接加入了 Init 方法,使类在实例化之后自动地填充游标。
Updating ODBC Data(更新ODBC数据)
为了使这个类可更新,你必须正确地给Tables, KeyFieldList , UpdatableFieldList 和 UpdateNameList 属性赋值。 并且设定 AllowInsert , AllowUpdate 和 AllowDelete 属性为true, 确定自动更新特征被激活。 再一次强调,最好的办法是在Init 方法中用代码设置这些属性。 Init 方法一些代码在list2 中。
在关闭类设计器之前,你可能想将 BufferModeOverride 属性换成 "5",“开放的表缓冲”以便移动记录指针的时候 , 自动更新不发生。
测试 CursorAdapter 的更新能力,把它作为一个实例,浏览游标,作一个更改, 然后提交 TableUpdate。 确定所作的更改被响应,调用 CursorAdapter 的 CursorRefresh 方法,再浏览一次。
处理ODBC错误(Handling ODBC Errors)象使用本地 CursorAdapter一样 ,大多数的错误处理是按照 "传统的"方法—测试 TableUpdate 返回的值,如果失败,使用 AError 来判断原因。 不幸的是,更新冲突的检测也是ODBC连接类型 CursorAdapter 的一个问题。
本地 CursorAdapter 错误的解决办法是在 AfterUpdate 方法中设置错误处理代码, 但这种方法对ODBC型 CursorAdapter 是无效的,因为更新失败的时候,我们不是要处理VFP错误,而是ODBC错误。 因此,最好答案是使用一个存储过程,或在更新句柄中增加一些代码发送到服务器。
回想一下,为本地的 CursorAdapter 更新错误的解决办法是检查 _TALLY,看看是否有记录被更新。为ODBC连接的解决办法与本地CursorAdapter是相似的,但是我们不能使用 _TALLY,因为在远程数据检测上它是不可靠的。 我们可以使用SQL server的@@Rowcount 系统函数来判定记录是否被更新。
如果你要写一个 T- SQL 批量更新语句来更新一笔记录,你应该象下面这样写:
--@custID and @oldContact set by earlier code or parameters
UPDATE customers
SET ContactName = @newContact
WHERE CustomerID = @custID
AND ContactName = @oldContact
IF @@ROWCOUNT = 0
RAISERROR('Update failed.',16,1)
RaisError T- SQL 函数使 VFP 接收一个ODBC错误 (1526 号), 在第一个参数里写错误信息.(另外二个参数指出严重和错误的状态) 在这种情况下, 当@@Rowcount=0的时候RaisError 被调用, 表明早先的 T- SQL 语句没影响任何的记录。
所有讨论的这一些表明你可以使用 CursorAdapter 的 BeforeUpdate 方法来描述发送到服务器用来更新的语句。 BeforeUpdate 方法接受五个参数, 有趣的是最后的二个 (cUpdateInsertCmd 和 cDeleteCmd) 建立了关联(注:原文the last two (cUpdateInsertCmd and cDeleteCmd) are interesting in that they are passed by reference. )。 在它们被送去数据源之前 , 这允许你修改指令。
在我们的例子中,我们使用这一个方法增加一个对@@Rowcount的测试然后调用 RaisError 。把下列代码写入 BeforeUpdate :
LPARAMETERS cFldState, lForce, nUpdateType, ;
cUpdateInsertCmd, cDeleteCmd
IF nUpdateType = 1 THEN
cUpdateInsertCmd = cUpdateInsertCmd + ;
" if @@ROWCOUNT = 0 "+ ;
"RAISERROR('Update Failed due to update " + ;
"conflict.',16,1)"
ENDIF
现在,为发送到后台数据库的每一行记录,用这段代码测试行是否被更新。 如果没更新, VFP 将会接收到错误,TableUpdate 将会失败,而且 AError 将会显示1526号错误,并显示预设的错误信息。
存在二个的问题。 首先,这是特别为 SQL server设置的;对其他的ODBC数据源 ( 比如ORACLE) ,这一段代码无效。 其次,这个错误信息是指一类错误,总是产生相同的 VFP 错误号, 而且在 VFP 中建立正确的错误处理句柄有一点困难。这一个问题可以通过在SQL SERVER服务器上建立通用的错误信息,每条信息对应着自己的唯一错误号码。
另外一个更好的解决方法是使用存储过程来执行更新,代替用VFP建立一个查询。 当然,采用存储过程的不利因素是不能使用VFP 的自动更新句柄来更新数据。
参数化设置 Parameterization
通过学习,你已经知道如何为 CursorAdapter 类的方法、属性添加命令。 基本上,在类中使用的每个事件(event)都有一组before和after方法, 比如 BeforeUpdate 和 AfterUpdate 。然而,没有 BeforeSelect 或 AfterSelect-替代它们叫做 BeforeCursorFill 和 AfterCursorFill,因为游标是用 SelectCmd 的结果来填充的。
BeforeCursorFill 方法接受三个参数, 而且返回 Boolean 值。 第一个参数 , lUseCursorSchema, 叙述 CursorSchema 属性是否控制游标的结构。 第二个参数 , lNoDataOnLoad,与视图的 NODATA 子句类似,只取回表结构,但是没有从数据源取回数据。
对于现在的讨论 , 第三参数 , cSelectCmd,是最有意思的。 我们已经提到过它 (象 BeforeUpdate 的 cUpdateInsertCmd 参数) ,先使用 SelectCmd 的当前设置。然而,如果你改变这一个参数的值,它不改变 SelectCmd 属性的值;取而代之的是,它的值改为被传给数据源的语句 。
举例来说, 假如你已经把 CursorAdapter 对象的 SelectCmd 设定为下列代码:
SELECT CustomerID, CompanyName, ContactName, City,
Region, Country FROM Customers
在呼叫 CursorAdapter 的 CursorFill 方法之前, BeforeCursorFill 方法的 cSelectCmd 参数会包含这个语句。 假如你在这一个方法中用下列代码:
cSelectCmd = cSelectCmd + ;
" WHERE CompanyName LIKE '" + ;
this.cCompanyName + "%'"
这就导致实际的select命令总是包含如上面代码描述的where子句和 this.cCompanyName(自定义属性) 的当前值.因为它不改变 SelectCmd 的初始值, 你不必用任何特别的代码来确定在select命令中是否包含两个where子句。
参数设置第二部分
如果你以前用过视图,或 SQL pass through,那么你可能熟悉在参数前使用 "?" 字符。 这一个用法也适用于 CursorAdapter 。 下面的代码例子告诉你如何在 CursorAdapter 的 SelectCmd 属性中使用参数:
This.SelectCmd = "SELECT * FROM Customers " + ;
" WHERE CompanyName like ?lcMyVar "
lcMyVar = 'C%'
This.CursorFill()
最要紧的是确定变量 "lcMyVar" 在 CursorFill 方法被请求之前被定义。否则的话,VFP会提示找不到值,使用者会无所适从。
你可以使用 CursorAdapter 的属性作为参数,用来替代本地的变量。这样做有这样的优点:只要对象存在,属性也会存在,而且你可以通过使用access/assign方法来确定分配的值符合规则。
使用存储过程
在上面说过, 使用储存过程是一个突破错误处理限制的好办法。让我们逐步探究在ODBC CursorAdapter中使用存储过程的方法。这样我们就能感觉出手动处理 CursorAdapter 类的更新错误是多么复杂。
这一段是关于通过调用数据源的存储过程来代替自动执行的update、insert和delete命令。 这意味着你必须处理 UpdateCmd , InsertCmd 和 DeleteCmd 属性, 而且假设 SQL server上的 Northwind 数据库已经建立存储过程来提供这些功能。
例子, 让我们看一看一个简单存储过程的完整代码,你可以用它更新 Northwind 数据库的customer表的 ContactName 字段:
--T-SQL code, not VFP
CREATE PROCEDURE UpdateCustomerContact (
@CustomerID nchar (5),
@ContactName nvarchar (30),
@oldContact nvarchar (30)
)
AS
IF @CustomeriD IS NULL
RAISERROR('CustomerID is a required parameter',16,1)
ELSE
UPDATE Customers
SET ContactName = @contactName
WHERE CustomerID = @customerID
AND ContactName = @oldContact
为了节约空间,这一个存储过程没有包含全部的错误处理代码。 不管这个,已经有足够的代码来说明怎样在 CursorAdapter 类中执行更新。
幸运地, 建立 UpdateCustomerContact 存储过程作为更新命令,可以取代 BeforeUpdate 方法,用下面的代码:
LPARAMETERS cFldState, lForce, nUpdateType, ;
cUpdateInsertCmd, cDeleteCmd
cUpdateInsertCmd = ;
"EXECUTE UpdateCustomerContact '" + ;
EVALUATE(this.Alias+".CustomerID") + "','" +;
ALLTRIM(EVALUATE(this.Alias+'.ContactName'))+ ;
"','" + ;
OLDVAL('contactname',this.Alias)+"'"
这里,代码放在 cUpdateInsertCmd 参数里,覆盖了默认的update命令。我使用了evaluate函数,是为了cursor的名字是动态的,说明cursor的名字可以很容易被改变,但是代码不变。还有,我使用了OLDVAL 函数取回 ContactName 字段被修改前的值。如果在存储过程的where子句里需要旧的数据,那么这个函数是必需的。这有点像自动产生update的情况。
记住,在记录被实际更新之前 ,通过TableUpdate自动呼叫 BeforeUpdate 方法。 因此, 无论UpdateCmd当前的值是什么,这个方法程序(指UpdateCmd)被禁用,而且总是使用存储过程。
注意,你也可以使用已经讨论过的参数设置方法,使用 BeforeUpdate 方法。 这就要求你为 CursorAdapter 提供 UpdateCmd设置,但是, 不要在参数里用固定的代码,要用变量或者属性,并在它们前面加上“?”。
在这里需要注意的重要的一点是 cUpdateInsertCmd( 或对象的 UpdateCmd属性) 不能返回任何值。 进一步说,如果你从存储过程返回一个值,它就没有地方 "去" ,那么这个值就会永远丢失。因此,在存储过程中添加一个RaisError呼叫,以便在更新过程中发生任何错误( 例如不正确的参数或一个更新冲突) 的时候,你的代码能够作出反应,这样做是非常重要的。你可以通过测试 TableUpdate 返回值来捕获错误 ,或者用 AError 方法, 然后分析错误。
相似的代码也应该写进 BeforeInsert 和 BeforeDelete 方法,以便它们也调用存储过程,而不是调用设置好的“查询”。 为了节约空间,我将留下代码当做 "读者的练习."
CursorAdapter with OLE DB
我们的下一个任务是看看如何通过 CursorAdapter 类使用OLE DB(对象连接与嵌入), 并且把它同我们用过的native和ODBC作一比较。OLE DB技术比ODBC有更多的处理能力, 而且能够连接的数据源类型比ODBC多。 CursorAdapter 通过嵌入ADO对象使用OLE DB,这是OLE DB技术标准的 COM 封装。 VFP 会自动地把ADO记录集 转换成一个 VFP 游标, 以及处理更新, 正如在早先的例子中讲到的一样。
第一件要做的事,当然还是建立一个新的 CursorAdapter 类,我们用代码建立一个。
开始建立一个新的程序,取名字 caADO.prg, 把下列代码添加进去:
PUBLIC goCAADO as CursorAdapter
goCAADO = CREATEOBJECT('caADO')
BROWSE
DEFINE CLASS caADO AS CursorAdapter
oConn = NULL
oRS = NULL
Alias = "cCustADO"
DataSourceType = "ADO"
SelectCmd = "SELECT " + ;
"CustomerID, CompanyName, ContactName, "+;
"ContactTitle, Address, City, Country "+;
"FROM Customers WHERE Customerid LIKE 'C%'"
FUNCTION Init()
This.DataSource = this.oRS
This.oRS.ActiveConnection = this.oConn
This.CursorFill()
ENDFUNC
ENDDEFINE
在这段代码中,我们将 DataSourceType 设为ADO而且把我们对Customers的查询加入 SelectCmd 。 当 DataSourceType 是ADO的时候, DataSource 属性必须包含有效的 RecordSet 或命令对象, 这依赖于你如何使用 CursorAdapter。 如果你不使用参数化查询 (就象前面例子中讲到的用 "?")那么你可以用记录集( RecordSet );否则,你必须使用命令对象,因为ADO已经代替了参数选择。 你的查询中的任何参数在命令对象中自动地被处理。
在这种情况下,我们使用 RecordSet 对象 , 但是应该注意我们必须提供一个“连接”对象。 在这两种情形中,我使用Access方法建立了关于这些对象的参考。 list3 中是Access方法的代码。
两个Access方法首先检查对象是否已经被建立。 如果没有,那么就继续创建对象。在使用 RecordSet 的情形,你只需要建立对象,剩下的由 CursorAdapter 来做。 用“连接”对象的情况下, 你必须提供连接字符串(连接句柄)并且打开连接,因为CursorAdapter 不为你打开连接。这是因为“连接”不是 CursorAdapter 的一个属性, 而是 RecordSet 对象的一个属性。
下一次发:用OLE DB更新
用OLE DB更新
如果不另外设定几个属性,这么简单的 CursorAdapter 是没有更新能力的。 把下面的代码放在定义类(define)的代码中,在init方法之前运行,将会允许自动更新:
KeyFieldList = "CustomerID"
UpdatableFieldList = ;
"CompanyName, ContactName, ContactTitle, "+ ;
"Address, City, Country"
UpdateNameList = ;
"CustomerID Customers.CustomerID, " + ;
"CompanyName Customers.CompanyName, " + ;
"ContactName Customers.ContactName, "+;
"ContactTitle Customers.ContactTitle, " + ;
"Address Customers.Address, "+;
"City Customers.City, Country Customers.Country"
Tables = "Customers"
然而, RecordSet 会建立它的默认的 CursorLocation 和 CursorType 属性。 如果不改变这些属性设置, RecordSet 最初是只读的,因此,你需要按以下方法修正 oRS_Access 方法:
FUNCTION oRS_Access() as ADODB.RecordSet
LOCAL loRS as ADODB.RecordSet
IF VARTYPE(this.oRS)<>"O" THEN
this.oRS = NULL
loRS = NEWOBJECT("ADODB.Recordset")
IF VARTYPE(loRS)="O" THEN
loRS.CursorType= 3 && adOpenStatic
loRS.CursorLocation = 3 && adUseClient
loRS.LockType= 3 && adLockOptimistic
this.oRS = loRS
ENDIF
ENDIF
RETURN this.oRS
ENDFUNC
为 RecordSet 加上这些代码以后, CursorAdapter 就能处理自动更新了。
下一次发cursoradapter with XML
CursorAdapter with XML
最后, 让我们建立使用XML作为数据源的 CursorAdapter。 这一节很有趣,因为XML文本不像通常的数据源。 同样,当数据源设置为XML的时候, CursorAdapter 不会自动建立 SQL 更新, 插入或删除命令。因此,这种类型的 CursorAdapter 需要大量的代码接收和更新数据。
在这一个例子中,我将使用 SQL server 2000 的 SQLXML 提供XML文本。 同时,因为SQLXML支持通过XML更新,我们将花一点时间写必要的代码来执行更新。 假设你已经配置了SQLXML,允许HTTP数据存取Northwind 数据库,而且允许用 UpdateGrams 对数据库进行更新。
在这里,我在 IIS 上面设置使用一个被称为 "nwind" 的虚拟目录存放 HTTP 数据。 因此,我的全部例子将会包含这个网址:
http://localhost/nwind,经由%20IIS%20存取%20SQLXML。
让我们开始建立一个新的程序,程序名字为caXML.prg,用下列代码(创建一个类):
PUBLIC oCAXML as CursorAdapter
SET MULTILOCKS ON && need for table buffering
oCAXML = CREATEOBJECT('xcXML')
BROWSE NOWAIT
DEFINE CLASS xcXML AS CursorAdapter
DataSourceType = "XML"
Alias = "xmlCursor"
UpdateCmdDataSourceType = "XML"
InsertCmdDataSourceType = "XML"
DeleteCmdDataSourceType = "XML"
BufferModeOverride = 5
*custom properties
oXMLHTTP = NULL
oXMLDOM = NULL
cServer = "localhost"
cVDir = "nwind"
ENDDEFINE
不同于通常的 DataSourceType 和Alias(别名)属性设置,这是我们第一次看见 xxxCmdDataSourceType 属性。 因为这是一个XML类型的 CursorAdapter,如果你想让它用来更新源数据,这些属性就必须进行设置 。推荐在这个类中使用 oXMLHTTP 和 oXMLDOM 属性,将会在下面做详细说明。
接收XML数据
在考虑 CursorAdapter 的更新能力之前,我们先研究怎样从SQLXML 服务器接收文本。 首先,一个简单的select命令不能工作,我们必须建立通用的 SelectCmd 。 这在 Init 方法中很容易做到,同时,在INIT中调用 CursorFill 方法,如以下代码:
FUNCTION INIT() as Boolean
LOCAL llRetVal, lcMsg, laErr[1]
this.SelectCmd = "this.GetXml()"
llRetVal = THIS.CursorFill()
IF NOT llRetVal THEN
AERROR(laErr)
lcMsg = "Cursor was not filled!"
IF NOT EMPTY(laErr[2]) THEN
lcMsg = lcMsg + CHR(13) + laErr[2]
ENDIF
MESSAGEBOX(lcMsg,16,"XMLCursorAdapter Test")
ENDIF
RETURN llRetVal
ENDFUNC
这一段代码建立 SelectCmd 作为本地用的方法,代替 SQL 命令。 以前的例子中没有这样做过,对任何 CursorAdapter 类,不管什么类型,都是合法的。然而,当你使用一个本地的方法作为 SelectCmd 的时候,你必须也提供代码用来更新,插入和删除, 因为如果不是一条SQL语句的话, VFP 是不会自动处理的。
当我们在 Init() 中使用 CursorFill 的时候,GetXML 方法被调用。 数据源设定为XML的时候,GetXML 方法必须返回只包含一个表的最终文本。 如果它包含多个表,你会得到料想不到的结果。 GetXML 方法在list4 中列示。
GetXML开始于提交一个 MSXML2.XMLHTTP COM 对象。 这个对象处理所有 HTTP 通信,包括发送请求到服务器并且返回结果。 你可以看到,oXMLHTTP 实例对象是被设计好的Access方法控制的,这样设计是为了防止连续地创建和破坏 COM 服务器。
然后,你可以看到我们的典型的SELECT描述, 除了LIKE子句有点稍稍不同以外。 HTTP 需要我们当十六位百分比字符显示到达25%的时候退出显示(指进度显示), 在 SQL server接收查询之前 , 这个值将会变成单一的百分比符号。
然后,代码用具体的查询要求来设置URL,并且通过HTTP发送URL到SQL SERVER服务器。SQL SERVER接收到这个查询,并且处理它,以XML方式返回值。这是因为我们已经在“查询”中包含了FOR XML子句。在这个例子中,XML的根元素叫做 "results" 。你从查询字符串中可以看出。这是按照你的习惯配置的。
此时, lcRetXML 包含一个来自 SQL server的XML数据流。 既然 GetXML 方法被 VFP 作为 SelectCmd 调用,你可以从 GetXML 方法中返回这个变量的内容,而且 VFP 将会把数据流转换成一个 VFP 游标。 你可以通过执行 caXML 程序测试。 会出现一个包含返回的XML文本内容的浏览窗户。 使用调试工具一步一步查看 GetXML 方法,可以看到在被转换到一个 VFP 游标并释放之前 , lcRetXML 变量的值是XML文本。
更新XML数据
下一个步骤是决定该如何使这个游标可更新,以便所作的更改能被发送回 SQLXML 服务器。 SQLXML 能使用一个特别的XML文本,即 UpdateGram, 利用它直接把更新发送到数据库。 在 VFP7 中,可以用 XMLUpdateGram 函数建立这个文本。 用VFP 8 的 CursorAdapter , UpdateGram 属性会自动建立。
第一个步骤是设置 updatable 属性并且建立一个更新指令。 在类定义的开始设置这些属性,并在CursorAdapter的init事件中增加一行代码,提供调用更新命令的方法。
KeyFieldList = 'customerid'
Tables = 'customers'
UpdatableFieldList = ;
"companyname, contactname, contacttitle, "+;
"address, city, country "
UpdateNameList= ;
"customerid customers.customerid, " + ;
"companyname customers.companyname, " + ;
"contactname customers.contactname, " + ;
"contacttitle customers.contacttitle, " + ;
"address customers.address, " + ;
"city customers.city, country customers.country"
FUNCTION INIT() as Boolean
LOCAL llRetVal, lcMsg, laErr[1]
this.UpdateCmd = "this.UpdateXML()"
this.SelectCmd = "this.GetXML()"
** balance of code skipped...
注意,我们已经在init方法中替换了属性表中的 UpdateCmd 和 SelectCmd 设置,实际上最终结果是一样的。 不管怎样, 这一段代码的第一部份现在看起来很熟悉,在这里我们设置了 KeyFieldList ,table, UpdatableFieldList 和 UpdateNameList 属性。 没有这些属性设置,就不可能创建 UpdateGram 。
然后,我们建立 UpdateXML 方法作为 CursorAdapter 的 UpdateCmd 。 没有参数传递给 UpdateXML 方法,所以,所有决定更新的工作必须这个方法里处理。还有,因为XML 类型的 CursorAdapter 没有默认的更新机制, 你必须写代码把更新传递给XML数据源。
在这一段代码中,使用 XMLHTTP 对象传递更新到服务器。 通过LoadXML方法加载 UpdateGram 属性的内容到 XMLDOM( 在ACCESS方法中) 之内,打开到服务器的连接,设定请求的内容为XML, 然后发送 XMLDOM。所有的结果通过 XMLHTTP 对象的 ResponseText 属性返回,接着装载到 XMLDOM 中,并且分析错误信息。
如果没有提示错误,更新已经成功,而且过程结束。然而,如果有错误, 就会产生符合语法的错误信息文本,并且包含在一个自定义的ERROR函数中,这样 TableUpdate 函数就能找到更新失败的原因。 如果没有这些代码, 即使有问题的时候,TableUpdate 总是返回成功信息。
测试一下这段代码,运行 caXML 程序,在游标中更改一个字段, 然后在命令窗口中发出 TableUpdate。 如果 TableUpdate 成功, 你应该能在服务器上看到更新结果。如果 TableUpdate 失败,你需要用 AError 函数接收 SQL server产生的错误信息。
如果你对 UpdateGram 的内容感到好奇, 你可以一步步查看类的 UpdateXML 方法,并且查看 UpdateGram 属性的内容。 然而,如果你不到有关数据变化的方法中 (如 UpdateCmd , InsertCmd 或 DeleteCmd 属性内容) ,你也不能完全弄明白 UpdateGram 属性的内容。
list6显示当ContactName字段的客户ID被更改为‘CACTU' 后UpdateGram的内容。
正如你看到的,SQLXML 能够读取这个文本并且能够很容易地建立一个Update-SQL 语句, 然后发送到 SQL server。 updg:sync(同步更新)元素使更新立即发生;因此,如果你有多个表需要更新,你可以把它们关联起来放到一个 UpdateGram中, 确定把他们封装进这个元素,执行一次就可以全部更新。
结束语
在这一篇文章中,我们涉及了许多方面,讲了新的 CursorAdapter 类的四种表现形式。 你已经学到了如何通过 DataEnvironment 、 CursorAdapter buileder、类设计器、和PRG建立 CursorAdapter 。 你也已经了解了建立本地、ODBC、OLE DB或XML型 CursorAdapter 类的基本方法, 并且怎样使这些类可更新。
下一个步骤是考虑如何把这些类应用到你的程序中。 对我来说,觉得 CursorAdapter 应用到任何程序的 UI 层中效果很好,同时应用于需要大量过程代码的许多商业开发。CursorAdapter并不是一个最好的选择对象用于层来传递数据。因为它把所有的来自数据源的数据转化成一个不便于携带的游标。
然而,在一个使用 CursorAdapter 类进行商业开发的方案中,它能接收来自数据源的数据, 然后用标准的 VFP 命令和函数处理数据,这是因为它产生的是一个 VFP 游标。 数据可以被转换为更一个较适当的类型,例如XML。
CursorAdapter 的另一个有利条件是通用的 OOP 接口,不管需要存取的数据是什么类型。 甚至用XML,需要大量代码来使类可更新,我们仍然可以用使用 CursorFill取回数据,用 TableUpdate 更新数据, 而且用 AError 返回错误, 像使用其他类型的 CursorAdapter一样。
基于一个事先的考虑或者计划,你想建立一个可以重复使用的CursorAdapter类。 这些类可以在程序之间重复使用或者在同一程序内部混合使用,来形成一种你的程序处理数据的标准方法。
有网友问使用cursoradapter是否还需要tableupdate(),下面索性再发一篇文章:
如何通过 CursorAdapter 对象使用 TableUpdate 函数更新数据
这一篇文章适用于:vfp 8.0
摘要:
这一篇文章讨论下列各项主题:
1、TableUpdate 交互地使用 CursorAdapter 类更新后端数据。
2、使用vfp 8.0 的新 CursorAdapter 类通过TableUpdate 函数更新数据的时候如何处理更新冲突。
较多的信息:
可以通过vfp 8.0 的 CursorAdapter 类取回来自本地的或远程的数据源的数据。 默认情况下,CursorAdapter 产生的游标不更新后端数据。 为了要更新后端数据,必须设定 CursorAdapter 的下列各项属性值:
InsertCmd
UpdateCmd
DeleteCmd
如果你不设定这些属性值,你能通过设定下列各项 CursorAdapter 属性值自动地产生后端 SQL 更新指令:
tables
KeyFieldList
UpdatableFieldList
UpdateNameList
SendUpdates
处理更新冲突
当你使用 CursorAdapter更新后端数据的时候, TableUpdate 函数返回 CursorAdapter 游标的更新结果。 如果一个更新冲突发生,后端数据的更新不可能成功。 然而, TableUpdate 可能仍然返回更新成功,因为在 CursorAdapter 游标中的数据被更新。
更新冲突是指,使用者试图编辑一条取回后已经发生改变的记录。 下列各项是更新冲突能发生的情况:
1. User1 打开由客户表建立的一个游标(cursor)。
2. User2 更新第一条记录而且提交更新。
3. User1 更新第一条记录(使用 TableUpdate() 函数).
此时, User1 有一个更新冲突: User1 正在尝试更新的后端记录在被取回之后已经发生改变。
DataSourceType 代码示例:
下列示例代码使用vfp 8.0 的native DataSourceType 更新 SQL server Northwind 数据库的一笔记录。 查证是否记录被更新,在更新命令中附加下列代码:
CRLF+[EXECSCRIPT+(" if _tally=0"+CHR(10)"+error ('update conflit')"+CHR(10)+"ENDIF")]
在这种情况下,Tableupdate() 返回 (.F。 ) 而且允许你处理更新失败。
#DEFINE CRLF CHR(13)+CHR(10)
Local loCursor,ovfp
CLEAR
ON ERROR
Set Exclusive Off
Close Databases All
Set Multilocks On
loCursor = Createobject('CA')
* Load loCursor with the data specified in SelectCmd and display error message if error occurs.
loCursor.CursorFill()
GO top
* Display the value of the company name before update.
? "Before:",companyname
?
ovfp=Createobject("visualfoxpro.application.8")
ovfp.DoCmd("set exclusive off")
ovfp.DoCmd("update (_samples+'\northwind\customers') set companyname='Alfreds Futterkisted' where customerid='ALFKI'")
GO top
* Update the data in the cursor.
replace companyname WITH 'Alfreds Futterkiste'
* Update the back end.
retval=TABLEUPDATE(0,.F.,locursor.alias)
Messagebox("Tableupdate="+Transform(retval))
* If update conflict occurs, display the error.
if(retval=.F.)
LOCAL ARRAY errors(1)
AERROR(errors)
* Displays the errors.
IF "Update conflict"$ errors[2]
MESSAGEBOX("Update Conflict-reverting changes")
=TABLEREVERT(.T.,locursor.alias)
ENDIF
endif
* Refresh the Cursor to get the updated data.
loCursor.CursorRefresh() && Get the data again to be sure
GO top
* Display the value of the company name after update.
?
? "After:",companyname
Define Class CA As CursorAdapter
Alias = 'test1'
DataSourceType = 'NATIVE'
SelectCmd = 'select * from (_samples+"\northwind\customers")'
Tables = 'Customers'
KeyFieldList = "customerid"
UpdatableFieldList = "companyname"
UpdateNameList = "customerid customers.customerid,companyname customers.companyname"
WhereType= 3
* This is a custom property, that is added to handle update conflicts. It does not do
* anything by itself. It is added below to the automatically-generated UpdateInsertCmd to
* test whether anything was actually updated.
ConflictCheckCmd =CRLF+[EXECSCRIPT("IF _tally=0" + CHR(10) + "ERROR('Update conflict')" + CHR(10) + "ENDIF")]
Procedure AfterUpdate
Lparameters cFldState, lForce, nUpdateType, UpdateInsertCmd, DeleteCmd, lResult
* To see why it will fail on the back end, look at the UpdateInsertCmd that is used
? "Update Command sent="+UpdateInsertCmd
* Swap the actual values in the command to see what occurred.
UpdateInsertCmd=Strtran(UpdateInsertCmd,[OLDVAL('customerid','test1')],Oldval('customerid','test1'))
UpdateInsertCmd=Strtran(UpdateInsertCmd,[OLDVAL('companyname','test1')],Oldval('companyname','test1'))
UpdateInsertCmd=Strtran(UpdateInsertCmd,[test1.companyname],test1.companyname)
? "With the OLDVAL() and test1.companyname evaluated the update statement is :"+UpdateInsertCmd
* Check tally.
? "Tally="+Transform(_Tally)
Procedure BeforeUpdate
Lparameters cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd
cUpdateInsertCmd=cUpdateInsertCmd+this.ConflictCheckCmd
ENDDEFINE
使用SQL server DataSourceType的示例代码:
下列代码使用 SQL server DataSourceType 更新 SQL server Northwind 示例数据库的一笔记录。 查证记录是否被更新,把下列代码加入更新指令:
IF @@ROWCOUNT=0 RAISERROR (' Update
conflict.', 16, 1)
在这种情况下,Tableupdate() 返回 (.F。 ) 而且允许你处理更新失败。
LOCAL loCursor,ovfp,nhnd,lsuccess
CLEAR
SET EXCLUSIVE OFF
CLOSE DATABASES ALL
SET MULTILOCKS ON
loCursor = CREATEOBJECT('CA')
* Load loCursor with the data specified in SelectCmd and display error message if error occurs.
IF !loCursor.CursorFill()
=AERROR(lar)
MESSAGEBOX(lar[2])
ENDIF
* Display the value of the company name before update.
? "Company Name Before Update:",companyname
?
* Create a connection handle for SQL Server so you can set up the update conflict.
nhnd=SQLSTRINGCONNECT([Driver=SQL Server; SERVER=<SQL SERVER NAME>; DATABASE=NORTHWIND])
=SQLEXEC(nhnd,[update customers set companyname='Alfreds Futterkiste' where customerid='ALFKI'])
=SQLDISCONNECT(nhnd)
* Now make a change to the local data, and then try to update it.
GO TOP
REPLACE companyname WITH 'Alfreds Futterkisted'
lsuccess=TABLEUPDATE(0,.F.,locursor.alias)
Messagebox("Tableupdate="+Transform(lsuccess))
* Error handling function. Displaying the error message if update conflict occurs.
IF !lsuccess
=AERROR(lar)
IF "Update conflict"$ lar[2]
MESSAGEBOX("Update conflict!-Reverting changes")
=TABLEREVERT(.f.,locursor.alias)
ENDIF
ENDIF
* Get the current data from the CursorAdapter.
loCursor.CursorRefresh()
GO TOP
* Displaying the value of the company name after update.
?
?"Company Name After Update:", companyname
DEFINE CLASS CA AS CursorAdapter
Alias = 'test1'
SelectCmd = 'select * from customers'
Tables = 'Customers'
KeyFieldList = "Customerid"
UpdatableFieldList = "companyname"
UpdateNameList = "customerid customers.customerid,companyname customers.companyname"
WhereType= 3 && Key and modified
* This is a custom property that is added to handle update conflicts. It does not do
* anything by itself. It is added below to the automatically-generated UpdateInsertCmd to
* test whether anything was actually updated.
ConflictCheckCmd =";IF @@ROWCOUNT=0 RAISERROR (' Update conflict.', 16, 1)"
* Initializing the connectivity to Data source (SQL Server) by using ODBC driver.
PROCEDURE init
WITH THIS
.DataSourceType = 'ODBC'
.DataSource=SQLSTRINGCONNECT([Driver=SQL Server; SERVER=<SQL SERVER NAME>; DATABASE=NORTHWIND])
ENDWITH
ENDPROC
PROCEDURE BeforeUpdate
LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd
? "Entering BeforeUpdate()"
cUpdateInsertCmd=cUpdateInsertCmd + THIS.ConflictCheckCmd
ENDPROC
PROCEDURE AfterUpdate
LPARAMETERS cFldState, lForce, nUpdateType, UpdateInsertCmd, DeleteCmd, lResult
* To see why it will fail on the back-end, look at the UpdateInsertCmd that is used.
? "Update Command sent="+UpdateInsertCmd
* Swap the actual values in the command to see what occurred.
lcActualCmd =Strtran(UpdateInsertCmd,[OLDVAL('customerid','test1')],Oldval('customerid','test1'))
lcActualCmd =Strtran(UpdateInsertCmd,[OLDVAL('companyname','test1')],Oldval('companyname','test1'))
lcActualCmd =Strtran(UpdateInsertCmd,[test1.companyname],test1.companyname)
? "With the OLDVAL() and test1.companyname evaluated the update statement is :"+UpdateInsertCmd
?
? "Leaving AfterUpdate()"
ENDPROC
* Destroying the connection.
PROCEDURE destroy
=SQLDISCONNECT(THIS.DataSource)
ENDDEFINE
注意:这种更新方式(如这一段代码所示的)不支持批量更新。 (例如,cursoradapter.batchupdatcount>1) 。使用批量更新的时候,下列各项事件不动作:
BeforeInsert
AfterInsert
BeforeUpdate
AfterUpdate
BeforeDelete
AfterDelete
叁考
更多的信息,vfp8帮助中的下列各项主题。
“CursorAdapter Object Properties, Methods, and Events"
"Data Access Management Using CursorAdapters"
"Detecting and Resolving Conflicts"
"Locking Data"
"Management of Updates with Views"
"Programming for Shared Access"
vfp8自带的cursoradapter帮助:
用 Visual FoxPro,可以用cursor adapters接收支持以下数据源类型的本地或远程数据:
• 本地的
• 开放式数据源连接(ODBC)
• ActiveX 数据对象 (ADO)
• 扩展标记语言 (XML)
Cursoradapter 类为不同的数据源类型作为本地临时表使用提供支持. Cursoradapter 对象具有以下功能:
• 灵活地使用不同的数据源.
• 使用 Cursoradapter对象数据源或者数据环境 .
• 在数据源限制范围内分享数据.
• 随意定义Cursoradapter 对象生成的临时表的表结构.
• 控制 Cursoradapter 对象生成的临时表的数据装载.
• 从不同数据源生成基于数据源类型的Visual FoxPro 临时表.
• 用 Cursoradapter 属性和方法控制怎样添加、更新、删除数据.
• 除数据环境以外,添加 Cursoradapter 对象到表单、表单集和其他容器.
• 用 Cursoradapter 类作为单独的、无数据环境的类.
对 Cursoradapter 对象来说,数据源只是一个翻译层,它从数据源获取数据生成 Visual FoxPro 临时表.
注意: Visual FoxPro 不支持Cursoradapter 对象建立连接关系. 但是, 可以在生成的临时表之间建立连接关系.
想了解更多 Cursoradapter, 数据环境, 和游标类的信息,请看: Cursoradapter Class, DataEnvironment Object, 和 Cursor Object.
用TABLEUPDATE()和TABLEREVERT()函数实现数据互动
TABLEUPDATE( ) 函数能够识别并且用 Cursoradapter 对象工作. TABLEUPDATE( ) delegates its operations to the cursor adapter associated with the cursor. TABLEREVERT( ) operates on Cursoradapter objects in the same way as other buffered cursors.
想了解更多的 Cursoradapter 对象怎样影响TABLEUPDATE( ) 和 TABLEREVERT( ) 函数的行为, 请查看 TABLEUPDATE( ) Function 和
TABLEREVERT( ) Function.
Automatic Updating and Cursoradapters
Visual FoxPro在本地和远程视图中自动产生 SQL INSERT, UPDATE, 和 DELETE 命令. 用 Cursoradapter 对象,可以指定、控制Visual FoxPro 怎样操作SQL INSERT, UPDATE, 和 DELETE 命令.
当 Cursoradapter InsertCmd, UpdateCmd, 和 DeleteCmd 属性为空的时候, Visual FoxPro 自动产生标准的SQL命令. 你必须确定那些自动产生的命令是否适合使用的数据源.要自动产生SQL INSERT, UPDATE, 和 DELETE 命令,你必须设定下面的 Cursoradapter特定属性:
• Tables
• KeyFieldList
• UpdatableFieldList
• UpdateNameList
Tables 和 UpdateNameList 属性要符合以下规则:
• Tables
如果允许自动更新,必须按顺序列出希望在 SQL INSERT, UPDATE, 和 DELETE 命令中显示的表的名称.
• UpdateNameList
• 必须指定一个包含成对出现的、本地和完整的远程数据字段的列表,用分号分隔。 没一对字段名中,本地的在前,远程的在后。完整的远程字段名应该这样写:<远程表名>, <远程字段名>, <远程表名> 必须和Tables 属性中的名字一致.
如果你设置以下Cursoradapter 的属性为 True (.T.),必须指定关键字段:
• AllowInsert
• AllowUpdate
• AllowDelete
你想了解更多关于SQL 的自动更新命令,请看 INSERT - SQL Command, UPDATE - SQL Command, 和 DELETE - SQL Command.
批量更新(Batch Updates)
如果 Cursoradapter BatchUpdateCount 属性的值大于1, Cursoradapter 对象使用批量更新 。 同时必须具备以下条件:
• Cursoradapter 对象设置为所有允许的操作使用相同的ODBC连接,包括AllowInsert, AllowUpdate, 和 AllowDelete属性中设置的INSERT,UPDATE,和DELETE操作。.
• Cursoradapter 对象设置为所有允许的操作使用相同的ADODB 命令对象。
• Cursoradapter 对象所有被允许的操作使用 "XML" 作为数据源。
如果使用批量更新, 下列 Cursoradapter 事件不发生:
• BeforeInsert
• AfterInsert
• BeforeUpdate
• AfterUpdate
• BeforeDelete
• AfterDelete
如果批量更新失败, Visual FoxPro 尝试在该批数据中的每一行发送一个分散的更新,然而,列举的事件不会发生。
更多的相关信息, 参见 BatchUpdateCount Property.
自动更新(Automatic Updating)和 ActiveX Data Objects (ADO)
用 ADO工作的时候, 可以用两种办法发送更新:
• 使用 Cursoradapter 对象,通过在Cursoradapter CursorFill 方法中使用 ADO 记录集对象发送更新
• 当使用 ADO记录集执行自动更新的时候, 设置ursorAdapter InsertCmdDataSource, UpdateCmdDataSource, 和 DeleteCmdDataSource 属性的时候,必须遵循下列规则:
• ADO记录集必须是可读写的并且标记为可用。
• 当使用客户端记录集对象的时候,书签必须总是可用。
• Bookmarks might be available when using Keyset or Static server-side cursors when supported by the OLE DB Provider.
• 按照一个字段一个字段的规则进行更新.更新完一条记录以后, Cursoradapter 对象在ADO记录集中寻找原始记录t, 用来改变可更新字段的值, 并且调用ADO记录集的update方法。 Cursoradapter 对象不调用ado记录集的updatebatch方法, 因此, 你的程序需要在恰当的时候明确地调用 UpdateBatch方法。
当使用这个方法程序的时候, 需要按以下方法设置 Cursoradapter 对象:
THIS.DataSourceType="ADO"
THIS.UpdateCmdDataSourceType=""
可以用数据环境(DataEnvironment)和 Cursoradapter builders 来建立一个可更新的表单,或者使用数据环境 DataEnvironment 和 Cursoradapter 类库,或者在Cursoradapter Builder中使用 AutoUpdate tab 制定一个主关键字段,更新字段,或者其他要求的信息。
• 通过一个自定义的或者自动产生的命令,通过 Cursoradapter 对象向数据库直接发送一个更新。
• 当执行自动直接更新, Cursoradapter 对象需要一个 ADO对象,其 ActiveConnection 属性设置成开放的ADO连接模型。
• 用这种方法的时候, 需要用以下方法设置Cursoradapter对象 :
THIS.UpdateCmdDataSourceType="ADO"
THIS.UpdateCmdDataSource=NewADODBCommandObject
在这个方法里不推荐使用 THIS.DataSourceType="ADO" 。
自动更新和 XML
Cursoradapter 对象使用XML数据源时,不自动产生SQL INSERT, UPDATE, 和 DELETE命令,因为已经存在好多产生XML的手段。 然而,可以使用Cursoradapter 对象产生一个 XML UpdateGram ,放在 Cursoradapter UpdateGram 属性.
尽管Cursoradapter 对象能产生XML UpdateGram, 你必须使用恰当的协议来实现最终更新。例如,SQL XML(HTTP), SQL XML OLE DB, 或者 XML Web service to .NET.
使用 XML 数据源和产生 XML UpdateGrams的时候必须遵循以下规则:
• 必须在 Cursoradapter InsertCmd, UpdateCmd, and DeleteCmd 属性中指定具体的命令,Visual FoxPro能够据以进行正确的插入、更新、和删除操作。 如果使用批量更新, Cursoradapter BatchUpdateCount 属性值必须大于1, UpdateCmd 在批量中只执行一次。
• 下列 Cursoradapter 属性必须设置为 "XML":
• InsertCmdDataSourceType
• UpdateCmdDataSourceType
• DeleteCmdDataSourceType
如果这些属性不设置为"XML", Visual FoxPro 执行 UpdateCmd, InsertCmd, DeleteCmd 属性中的命令而且不产生XML UpdateGram.
• 要想正确的建立XML 更新(UpdateGram), Cursoradapter 需要 Tables, UpdatableFieldList, UpdateNameList 属性中包含确定的值。 Cursoradapter 可以通过在Tables, UpdatableFieldList,和 UpdateNameList 属性中指定恰当的名称来产生一个多表更新。因此, 可以更新用XMLUPDATEGRAM( )函数产生的连接多个表的游标。更多的信息,请看 XMLUPDATEGRAM( ) Function.
• Cursoradapter 使用 WhereType 属性产生XML UpdateGram 的 before 事件。因此, 当你执行一个更新或删除的操作时, KeyFieldList 和 UpdatableFieldList 属性必须包含一个主关键字段.更多的信息,参见 WhereType Property.
• 如果设置了行缓冲, 或者 BatchUpdateCount 的值是1, Visual FoxPro 为每个update, insert, delete 操作建立一个XML UpdateGram 更新。
如果设置为表缓冲, 并且使用批量更新, 即atchUpdateCount 的值大于1, Visual FoxPro 为全部的批量创建一个 XML更新。 此种方式下, 必须使用TABLEUPDATE( ) 函数执行XML更新。
See Also
Working with Tables | Cursoradapter Class | Cursoradapter Object Properties, Methods, and Events