FP开发者最头疼的事之一,就是数据在"里面"和"外面"之间来回折腾。CursorAdapter负责把外部数据源变成VFP临时表,XMLAdapter负责把VFP数据变成XML(或反过来)——两个类各司其职,组合起来却能打通任督二脉。
今天我们不讲大道理,直接上干货:这两个类到底怎么用,什么时候该用谁,怎么搭配效果最好。
一、CursorAdapter:让外部数据源变VFP临时表
1.1 核心定位
CursorAdapter是VFP9引入的数据访问类,它的核心使命就一句话:把任意数据源映射为VFP临时表。
支持的数据源类型(DataSourceType属性):
DataSourceType | 说明 | DataSource属性值 |
|---|---|---|
"Native" | VFP本地表/临时表 | 被忽略 |
"ODBC" | ODBC数据源 | 有效的ODBC连接句柄 |
"ADO" | ADO RecordSet/Command | 有效的ADO对象 |
"XML" | XML数据 | 被忽略 |
关键点:CursorAdapter打开的临时表始终是缓冲式临时表,默认开放式行缓冲。
1.2 五步上手CursorAdapter
*-- 第1步:创建CursorAdapter对象
LOCAL oCA
oCA = CREATEOBJECT("CursorAdapter")
*-- 第2步:设置数据源类型和连接
oCA.DataSourceType = "ODBC"
oCA.DataSource = SQLSTRINGCONNECT("Driver=SQL Server;Server=.;Database=Northwind;Uid=sa;Pwd=123")
*-- 第3步:设置查询命令
oCA.SelectCmd = "SELECT EmployeeID, LastName, FirstName FROM Employees"
*-- 第4步:设置可更新字段和主键(需要回写时)
oCA.Tables = "Employees"
oCA.KeyFieldList = "EmployeeID"
oCA.UpdatableFieldList = "LastName, FirstName"
oCA.UpdateNameList = "LastName Employees.LastName, FirstName Employees.FirstName"
*-- 第5步:填充临时表
oCA.CursorFill()
BROWSE就这么简单。CursorFill执行后,VFP自动创建一个临时表,你可以像操作普通VFP表一样浏览、修改。
1.3 CursorFill的核心参数
oCA.CursorFill([lUseCursorSchema [, lNoData [, nOptions [, Source]]]])- lUseCursorSchema
:.T.时使用CursorSchema属性定义的表结构创建临时表,.F.时自动推断
- lNoData
:.T.时只建结构不填数据——适合先搭框架再按需加载的场景
- nOptions
:对ADO是Command类型枚举值,对XML是XMLTOCURSOR()标记组合
- Source
:ADO场景下传入Command对象,VFP会自动解析参数
1.4 BeforeCursorFill:运行时动态改SQL
这是CursorAdapter最实用的功能之一——在执行查询前动态修改SelectCmd:
*-- 在BeforeCursorFill事件中动态添加WHERE条件
PROCEDURE oCA.BeforeCursorFill
LPARAMETERS lUseCursorSchema, lNoData, cSelectCmd
*-- 注意:修改的是参数cSelectCmd,不是属性SelectCmd
*-- 这样不会污染原始的SelectCmd属性
cSelectCmd = cSelectCmd + " WHERE LastName LIKE '" + THIS.cFilter + "%'"
*-- 不要返回.F.,否则CursorFill不会执行
ENDPROC关键细节:BeforeCursorFill中修改cSelectCmd参数不会改变SelectCmd属性值,CursorRefresh也使用参数值而非属性值。这意味着你可以在属性中保存基础SQL,在事件中按需追加条件。
1.5 CursorAttach:把已有临时表纳入管理
不是所有临时表都需要从零创建。如果已经有了(比如SQL Pass-Through的结果),CursorAttach可以把它"挂"到CursorAdapter上:
*-- 先用SPT获取数据
lnHandle = SQLSTRINGCONNECT(cConnStr)
SQLEXEC(lnHandle, "SELECT * FROM Orders", "tmpOrders")
*-- 再挂到CursorAdapter
oCA = CREATEOBJECT("CursorAdapter")
oCA.DataSourceType = "ODBC"
oCA.DataSource = lnHandle
oCA.Tables = "Orders"
oCA.KeyFieldList = "OrderID"
oCA.UpdatableFieldList = "CustomerID, OrderDate"
oCA.CursorAttach("tmpOrders", .T.) && .T.继承临时表已有属性注意:一个临时表同一时间只能附属一个CursorAdapter实例。要先换"主人",得先CursorDetach。
1.6 数据更新回写
CursorAdapter最大的价值不是读数据,而是改了就能回写:
*-- 修改临时表数据后,调用TABLEUPDATE
TABLEUPDATE(.T., .T., oCA.Alias)
*-- 或者用CursorAdapter的Save方法(框架封装)
oCA.Save()底层原理:CursorAdapter通过Tables、KeyFieldList、UpdatableFieldList、UpdateNameList四个属性构建UPDATE/INSERT/DELETE语句,调用TABLEUPDATE时自动执行。
二、XMLAdapter:XML与VFP数据的双向桥梁
2.1 核心定位
如果说CursorAdapter是"把外部数据拉进来",XMLAdapter就是"把VFP数据推出去(或拉回来)"的双向转换器。
核心能力:
- LoadXML:从XML文件或字符串加载到Tables集合
- ToXML:从VFP临时表生成XML文件或字符串
- ToCursor:从XMLTable生成VFP临时表
- ApplyDiffgram:将DiffGram变更应用到VFP表
2.2 从XML创建VFP临时表
LOCAL oXA AS XMLAdapter
oXA = CREATEOBJECT("XMLAdapter")
*-- 从文件加载XML
oXA.LoadXML("c:\data\employees.xml", .T.) && .T.表示是文件
*-- 查看加载了多少表
? oXA.Tables.Count&& 有几个XMLTable对象
*-- 将第一个表转为VFP临时表
oXA.Tables.Item(1).ToCursor(, "xmlEmployees")
BROWSE2.3 从VFP临时表生成XML
LOCAL oXA AS XMLAdapter
oXA = CREATEOBJECT("XMLAdapter")
*-- 添加临时表到XMLAdapter
USE employees IN 0 ALIAS emp
oXA.AddTableSchema("emp")
*-- 生成XML文件
oXA.ToXML("c:\output\employees.xml", , .T.) && .T.表示输出到文件2.4 DiffGram:变更追踪与回写
DiffGram是XMLAdapter的杀手级功能——它不只记录数据,还记录变更前后的差异:
*-- 生成包含变更的DiffGram
oXA.IsDiffGram = .T.
oXA.ToXML(@cXML, , .F., .T., .T.)
*-- 第4个参数.T.:包含before部分
*-- 第5个参数.T.:只输出变更
*-- 应用DiffGram回写变更
oXA2 = CREATEOBJECT("XMLAdapter")
oXA2.LoadXML(cXML)
oXA2.Tables.Item(1).ApplyDiffgram()ApplyDiffgram甚至可以配合CursorAdapter使用——传入oCursorAdapter参数,让更新走CursorAdapter的连接回写到远程数据库!
*-- 用CursorAdapter作为更新通道
oXA2.Tables.Item(1).ApplyDiffgram(, oCA, .T.)2.5 嵌套表处理
XMLAdapter对嵌套表的处理因数据源而异:
数据源 | 处理方式 |
|---|---|
ADO.NET DataSet | 每个嵌套表作为独立XMLTable,可分别ToCursor |
SQL XML | 所有嵌套表合并为单个结果,只有顶层父表在Tables集合中 |
RespectNesting属性:设为.T.时,LoadXML保留XML层次结构,ToXML生成嵌套XML。
三、双雄合璧:CursorAdapter + XMLAdapter实战场景
3.1 场景一:BS模式下JSON转CursorAdapter
祺佑框架的DAL_CA类提供了parsejson方法,底层正是CursorAdapter + XML/JSON转换:
*-- 从WebAPI获取JSON,转为CursorAdapter管理的临时表
oDALCA = NEWOBJECT("DAL_Employees", "dal_employees.prg")
oDALCA.CursorFill(.T.) && 用CursorSchema填充
*-- 修改数据
oDALCA.Add() && 新增空行
REPLACE LastName WITH '中国' IN (oDALCA.Alias)
*-- 检查、保存
? oDALCA.detectchanged() && .T.表示有变更
IF !oDALCA.Save()
WAIT WINDOW oDALCA.msg && 显示错误原因
ENDIF
*-- 撤销
? oDALCA.Undo() && 撤销所有操作3.2 场景二:XML中间层数据交换
WebAPI返回XML数据,用XMLAdapter解析后挂到CursorAdapter上管理:
*-- 1. XMLAdapter加载XML
oXA = CREATEOBJECT("XMLAdapter")
oXA.LoadXML(cWebResponse)
*-- 2. 转为临时表
oXA.Tables.Item(1).ToCursor(, "tmpData")
*-- 3. 用CursorAttach纳入CursorAdapter管理
oCA = CREATEOBJECT("CursorAdapter")
oCA.DataSourceType = "Native"
oCA.Tables = "MyTable"
oCA.KeyFieldList = "ID"
oCA.UpdatableFieldList = "Name, Value"
oCA.CursorAttach("tmpData")
*-- 4. 修改后回写
*-- ... 修改数据 ...
TABLEUPDATE(.T., .T., "tmpData")3.3 场景三:离线编辑 + DiffGram同步
这是最经典的组合用法:
*-- 1. 从远程拉数据
oCA = CREATEOBJECT("CursorAdapter")
oCA.DataSourceType = "ODBC"
oCA.DataSource = lnHandle
oCA.SelectCmd = "SELECT * FROM Products"
oCA.CursorFill()
*-- 2. 生成XML(带Schema),保存到本地
oXA = CREATEOBJECT("XMLAdapter")
oXA.AddTableSchema(oCA.Alias)
oXA.ToXML("c:\offline\products.xml", , .T.)
*-- 3. 离线编辑...(可能在另一台机器上)
oXA2 = CREATEOBJECT("XMLAdapter")
oXA2.LoadXML("c:\offline\products.xml")
oXA2.Tables.Item(1).ToCursor(, "Products")
*-- ... 用户修改数据 ...
*-- 4. 生成DiffGram
oXA3 = CREATEOBJECT("XMLAdapter")
oXA3.IsDiffGram = .T.
oXA3.AddTableSchema("Products")
oXA3.ToXML(@cDiffGram, , .F., .T., .T.)
*-- 5. 回到在线环境,用ApplyDiffgram + CursorAdapter回写
oXA4 = CREATEOBJECT("XMLAdapter")
oXA4.LoadXML(cDiffGram)
oXA4.Tables.Item(1).ApplyDiffgram(, oCA, .T.)四、CursorAdapter关键属性速查
属性 | 说明 |
|---|---|
DataSourceType | 数据源类型:Native/ODBC/ADO/XML |
DataSource | 数据源对象(ODBC句柄/ADO RecordSet) |
SelectCmd | 查询命令(SQL SELECT语句) |
CursorSchema | 临时表结构定义 |
Tables | 对应的基表名列表 |
KeyFieldList | 主键字段列表 |
UpdatableFieldList | 可更新字段列表 |
UpdateNameList | 字段到基表列的映射 |
AllowInsert/AllowUpdate/AllowDelete | 是否允许对应操作 |
FetchMemo | 是否获取备注字段 |
BufferModeOverride | 缓冲模式覆盖 |
五、XMLAdapter关键属性速查
属性 | 说明 |
|---|---|
IsDiffGram | 是否以DiffGram格式处理 |
RespectNesting | 是否保留XML嵌套层次 |
PreserveWhiteSpace | 是否保留XML中的空格 |
UTF8Encoded | 是否UTF-8编码 |
RespectCursorCP | 是否尊重游标代码页 |
XMLSchemaLocation | 外部Schema文件位置 |
XMLNameIsXPath | XMLName是否为XPath表达式 |
UseCodePage | 是否使用代码页 |
六、避坑指南
6.1 CursorAdapter常见陷阱
- 错误2112
:临时表已附属另一个CursorAdapter——必须先CursorDetach再CursorAttach
- 不能使用本地/远程视图作为Native源
——但可以用CursorAttach挂上
- CursorFill会关闭当前已附属的临时表
——要保留的话先CursorDetach
- BeforeCursorFill返回.F.会阻止CursorFill执行
——但当前临时表不会被关闭
6.2 XMLAdapter常见陷阱
- XDR Schema必须转为XSD
——需修改XMLNamespace属性,否则XML解析器无法重新加载
- 数值溢出(.
)的XML会导致加载失败**——MSXML解析器会报错
- XMLNameIsXPath为.T.且XMLName不为空时,ToXML会失败
- ApplyDiffgram只为Tables集合中的XMLTable支持
——嵌套在链中的子表不支持
- UseCodePage和UTF8Encoded的组合会影响编码结果
——多表场景确保代码页一致
七、总结
维度 | CursorAdapter | XMLAdapter |
|---|---|---|
核心用途 | 外部数据源→VFP临时表 | VFP数据↔XML双向转换 |
数据源 | ODBC/ADO/Native/XML | XML文件/字符串 |
更新回写 | 自动构建UPDATE/INSERT/DELETE | DiffGram追踪变更 |
最佳场景 | 在线CRUD操作 | 离线同步、跨系统数据交换 |
框架应用 | DAL_CA派生类 | XML中间层、WebAPI交互 |
一句话:CursorAdapter是"住"在数据源旁边的管家,XMLAdapter是"跑"在数据路上的快递员。管家管日常,快递跑远程——配合使用,VFP的数据交互能力远超你想象。

390

被折叠的 条评论
为什么被折叠?



