CURSOR对象化(END)
在我们将数据操作和界面操作分离的同时,我们也完成了数据操作的封装。
你可以为 MyCursor 类 添加 Edit、Deleted 自定义方法,然后在表单和数据环境类中添加对应的调用方法。这样,你就完成了针对DBF操作的数据操作封装。假设,表单长了翅膀飞走了,那么,你完全可以用这样的代码来操作数据:
Private loCursor
Local loCursor as MyCursor of MySPT.VCX
m.loCursor = NewObject([MyCursor], [MySPT.VCX])
m.loCursor.CursorInit()
m.loCursor.Append()
看到了,这就是“对象化操作Cursor”。它无需知道需要在什么表单上运行,其本质仅仅只需要这样的语句即可。当你修改表单时,你无需考虑数据操作的问题,因为它们已经被分离出去了,你唯一需要考虑的,是如何来调用它们。而这些调用的代码,你完全可以视为对表单和表单元素的操作。
也许你会有一个疑问,既然这样就可以,那为什么还要使用一个自定义数据环境类呢?
问题就在于,你的数据环境中存在唯一表的概率其实是非常低的。。。
如果你给前面的通讯录再配套一个备忘录,你就会发现数据环境类的重要性。这也是我们在内存中构建“数据库”的容器。VFP 基本知识里有一项是数据环境里表和表之间的关系。这个关系是临时性的,对于刚入门的来说,这一部分好像还不是很好理解。。。。但是,这是我们在内存中构建“数据库”的关键。。。
我们先看看 VFP “经典”的临时关系是怎样的。
打开前面所说的 NorthWind 数据库,从其中挑两个有固定关系的表出来,放入表单的数据环境中,你会看到如下的内容:
这里,你可以看到 VFP 对临时关系的设置。
我们要的就是它。没问题,复制一遍。虽然我们可以在 Relation 类中设置这些属性,但,但,但,你需要用代码来实现这个临时关系。打开前面创建的MyRelation 类,添加一个自定义方法 SetCursorRelation:
If !Empty(This.ChildAlias) And ;
!Empty(This.ChildOrder) And ;
!Empty(This.ParentAlias) And ;
!Empty(This.RelationalExpr) And ;
This.lRelation = .F.
If Empty(This.RelationalExpr)
This.RelationalExpr = (This.Parent.Parent.Name) + "." + (This.Parent.Name) + "." + (This.Name) + "." + "ChildOrder"
EndIf
Select (This.ParentAlias)
Set Relation To (This.RelationalExpr) Into This.ChildAlias In This.ParentAlias Additive
Set Skip To (This.ChildAlias)
This.lRelation = .T.
Endif
代码中的 lRelation 是一个自定义属性。这样,我们就完全获得了 VFP 表单固有表单环境中临时关系的设置。
假设,你在设计一个一对多的查询表单,那么,使用 MySPT 类库中的类,你已经可以完美的在将数据操作分离出来的状态下达到和VFP经典设计一样的效果。
结束了吗?没有。因为我们还要靠它构建我们内存中的“数据库”。
在 MyRelation 中,增加 InsertTirgger、UpdateTirgger、DeleteTirgger三个自定义方法,它们是空方法。需要你在实际业务场景来实现,如果你需要插入触发器、更新触发器和删除触发器。
这时,你的数据环境类中 Append 方法的方法就可以改写了:
Private All Like l*
Local loCursor As MyCursor Of MySPT.VCX, ;
loRelation As MyRelation of MySPT.VCX, ;
llOver As Logical, ;
llReturn As Logical
m.llOver = .F.
*!* 执行新增
For each m.loCursor in This.Objects
If Upper(m.loCursor.BaseClass) == [CURSOR]
m.llReturn = m.loCursor.Append()
If m.llReturn = .F.
m.llOver = .T.
EndIf
EndIf
EndFor
*!* 执行插入触发器
If m.llOver = .F. && 如果没发生错误,则执行触发器
For each m.loRelation in This.Objects
If Upper(m.loRelation.BaseClass) == [RELATION]
m.llReturn = m.loRelation.InsertTirgger()
If m.llReturn = .F.
Exit
EndIf
EndIf
EndFor
EndIf
Thisform.Refresh
用同样的方式,更改你的数据环境的 Edit 和 Deleted 方法。这样,你就构建出来一个内存数据库的主要部分。
我们离成功已经很近了。。。。。。
现在我们还差记录级规则的实现和字段级规则的实现。从观察中我们可以感觉到,记录级规则是已经添加/编辑完记录之后执行的。所以,我们可以再稍微改造一下 MyCursor 类,增加三个自定义方法:BeforeNew、New、AfterNew(),其中,BeforeNew 中写入:
Return .T.
然后将 Append 中的代码剪切到 New 方法中,Append 的方法代码重新书写为:
With This
If .BeforeNew()
.New()
.AfterNew()
Endif
Endwith
这样,在实际应用场景中,你就可以在 AfterNew 方法中写上记录级规则了。BeforeNew() 干什么用?当然是在新增前的检查方法了。。。它一定要有一个逻辑返回值,注意!
OK,到这里,就只剩下字段级规则了。。。。。我实在没办法在 Cursor 类中找到合适的方法来实现,如果你知道,请你告诉我。我实现的方法是在表单中绑定字段的控件的 Valid 方法中予以检查,这个执行效果几乎和 DBC 的字段级规则表现的一模一样。。。而无需你在 Error 中进行错误捕获。。。。。。
至此,我已经将本地应用下的“对象化操作Cursor”的具体步骤写完了。如果你想知道它如何来操作远程视图、SPT,那么,明天再来围观。。。。。
在继续前进之前,还是应该停下来先梳理一下已有的东西。
前面,构建了一个应用于本地DBC/DBF的数据处理组件。通过控件的 Valid 事件模拟出字段级规则,通过 Relation 类模拟出触发器,通过 AfterXXXX方法模拟出了记录级规则。
实际上,这样的架构可以实现很多其他的功能,例如,你可以在 BeforeXXXX 方法中做一个日志,记录数据变化前的状态,然后在 AfterXXXX 方法中记录变化后的状态,形成一个完整的数据操作日志。等等等等。。。。类库的设计方法,可以让你实现这里所描述的功能之外的需求。。。。
此外,由于基于DBC/DBF来设计,所以没有考虑到缓冲和事务。接下来的内容,会涉及到这些知识,基础差的自己恶补啊。。。。。。
远程视图在不太遥远的过去,曾经为第一代 C/S 架构的 VFP 程序立下了汗马功劳,因为它是可视化的。简单,易学。在 VFP 的帮助文件中介绍了一个最经典的 C/S 架构开发方法:由 DBC 构建原型,通过升迁向导,可以将 DBC 升迁到 SQL Server (早期的 VFP 甚至可以将 DBC 升迁到 Oracle),然后在 DBC 中创建和 DBF/本地视图 同名的远程视图,在表单数据环境用远程视图替换掉 DBF/本地视图。这样,基本就完成了一个C/S架构的程序。当然,在现实应用中,你总需要再添加点儿代码,才能更好的运行。
由此可以看出,它和 DBC/DBF 的唯一区别,就是,它在打开时,默认是使用缓冲的。这也是为什么说 VFP 是最适合构建程序原型的原因。
当我们在 MySPT.VCX 中增加少量的关于缓冲和事务处理的代码后,它就可以很好的服务于基于远程视图的 C/S 架构程序了。
首先,我们可以在 MyEnvironment 的 Append/Edit/Deleted 方法头部添加一句 Begin Transaction ,然后在其 Save 方法的末尾添加 End Transaction 后,整个结构就开始支持 VFP 的事务处理了。有人会说,你能保证每次都能提交更新成功吗?我只能说,你真的没动脑子。。。。
缓冲如何处理呢?我们可以在 MyCursor 的 CursorInit 方法中予以处理。还记得前面的截图吗?既然关系可以设置,缓冲为什么不能进行设置呢?
OK,到这里,如果你动手动脑的话,C/S 架构的开发已经是小 CASE 了。。。。
再补充,关于 VFP 的兼容性,或者在 64 位OS系统上运行的问题,在本论坛,在N年前,就有讨论,其实就是一个 UAC 的问题。我本来有一个系列的这方面的主题要翻译,结果发了第一篇后,意兴阑珊,有兴趣的童鞋可以用 BING 搜索关键字:VFP SPS,有详细的介绍。。。。老外还提供了一个钩子,就像老外所说的,你下载它并不是为了学习如何改写应用程序清单,而仅仅是使用它。
如果继续纠结要VFP编译出64位程序的童鞋,不久之前,CCB2000发布了一个64位的 VFP 版本:http://www.mzvfp.com/read.php?tid=100603&fpage=2 。如果你说它到处出错,那真的真的是强求了。。。。你写的程序编译出来是64位不就行了?如果你说,它不是 MS 出品的,那我更无话可说。我只能建议你试试精简版的精简到极致的 VFP 版本。。。因为,你留下来的,都是 MS 出品的,你精简下去的,绝大部分都不是 MS 出品的。。。。
*!* 设置游标是否可更新
MakeTransactable(This.Alias) && 允许游标支持事务处理
If This.lBuffering = .T.
CursorSetProp([Buffering], This.BufferModeOverride, (This.Alias)) && 设置缓冲
EndIf
If This.lSendupdates=.T.
*!* 设置游标可更新
CursorSetProp([Tables], This.cRemoteTableName, This.Alias) && 设置要更新的远程表名称
CursorSetProp([UpdateNameList], This.cFieldRelation, This.Alias) && 设置游标字段和源表字段的对应关系
CursorSetProp([KeyFieldList], This.cMainKey, This.Alias) && 设置主键(主键,时间戳)
CursorSetProp([UpdatableFieldList], This.cEnabledField, This.Alias) && 设置可更新字段
CursorSetProp([SendUpdates], This.lSendUpdates, This.Alias)
*!* 设置更新方式
CursorSetProp([WhereType], This.nWhereType, This.Alias) && 设置更新方式
CursorSetProp([UpdateType], This.nUpdateType, This.Alias)
Endif
当然,你可以修改它以适应你的应用场景。
然后,你可以修改你的 Save 方法:
This.BeforeSave()
Select (This.Alias)
m.LcOldError = On([Error])
On Error
If This.BufferModeOverride = 3[/color]
m.Temp = Tableupdate(.F., This.lForce) && 开放式行缓冲,提交当前行
Else
m.Temp = Tableupdate(.T., This.lForce) && 开放式表缓冲,提交当前表
Endif
If m.Temp = .F. && ODBC 错误
*!* 在这里处理更新冲突,
Else
This.AfterSave()
On Error &LcOldError
Endif
看,就是这么简单,我们就完成了SPT的设置和更新。当然,如果你非要提交长长的 Insert/Delete/Update ,也不是不可以,但是,
这需要你在这个结构上自己实现。。。虽然我也打算增加对这种方式的支持,但是,我仅仅是将其作为一个备用手段,而不是主要手段。
到这里,就该结束了。后面还有一段废话。写不写在我,看不看在你。
熟悉CA的都知道,CA有生成器,而且,可以很容易的添加到数据环境中。至于那种把 CA 拖放到表单上的做法,我通常是嗤之以鼻。
这里是我的一个演示,它像CA吗?
当你把我前面所描述的东西完善之后,再去看CA,你会哈哈大笑。。。。如果你看过我写的B/S开发入门教程 ,在 MyCursor 上动一动手脚,那么,这玩意估计就可以通吃各种架构的应用程序了。
至此,你就成为了万里挑一的绝世高手(是星爷演的那个绝世高手,不是那个负责搞笑的“绝世高手”哈)。
当你换一种语言开发程序时,相信你会很容易的理解它们所描述的数据处理方式和术语。事实上,你在 VFP 的世界构建出了自己的 BLL 和 DAL 。
VFP过时了吗?
————————————全文到此结束,谢谢欣赏——————————————————————
补遗:远程数据库的事务在哪里启动?在 MyEnvionment 的 Save 方法中。。。