Foxwx微信公众号管理软件---同心软件 -以高级编程语言进行微信公众号管理,我们一直在努力!

CURSOR对象化(END)

在我们将数据操作和界面操作分离的同时,我们也完成了数据操作的封装。

你可以为 MyCursor 类 添加 Edit、Deleted 自定义方法,然后在表单和数据环境类中添加对应的调用方法。这样,你就完成了针对DBF操作的数据操作封装。假设,表单长了翅膀飞走了,那么,你完全可以用这样的代码来操作数据:

  1. Private loCursor

  2. Local loCursor as MyCursor of MySPT.VCX


  3. m.loCursor = NewObject([MyCursor], [MySPT.VCX])

  4. m.loCursor.CursorInit()

  5. m.loCursor.Append()

看到了,这就是“对象化操作Cursor”。它无需知道需要在什么表单上运行,其本质仅仅只需要这样的语句即可。当你修改表单时,你无需考虑数据操作的问题,因为它们已经被分离出去了,你唯一需要考虑的,是如何来调用它们。而这些调用的代码,你完全可以视为对表单和表单元素的操作。

也许你会有一个疑问,既然这样就可以,那为什么还要使用一个自定义数据环境类呢?

问题就在于,你的数据环境中存在唯一表的概率其实是非常低的。。。

如果你给前面的通讯录再配套一个备忘录,你就会发现数据环境类的重要性。这也是我们在内存中构建“数据库”的容器。VFP 基本知识里有一项是数据环境里表和表之间的关系。这个关系是临时性的,对于刚入门的来说,这一部分好像还不是很好理解。。。。但是,这是我们在内存中构建“数据库”的关键。。。

我们先看看 VFP “经典”的临时关系是怎样的。

打开前面所说的 NorthWind 数据库,从其中挑两个有固定关系的表出来,放入表单的数据环境中,你会看到如下的内容:

这里,你可以看到 VFP 对临时关系的设置。

我们要的就是它。没问题,复制一遍。虽然我们可以在 Relation 类中设置这些属性,但,但,但,你需要用代码来实现这个临时关系。打开前面创建的MyRelation 类,添加一个自定义方法 SetCursorRelation:

  1. If !Empty(This.ChildAlias)        And ;

  2.    !Empty(This.ChildOrder)        And ;

  3.    !Empty(This.ParentAlias)        And ;

  4.    !Empty(This.RelationalExpr)    And ;

  5.    This.lRelation = .F.


  6.     If Empty(This.RelationalExpr)

  7.         This.RelationalExpr = (This.Parent.Parent.Name) + "." + (This.Parent.Name) + "." + (This.Name) + "." + "ChildOrder"

  8.     EndIf


  9.     Select (This.ParentAlias)

  10.     Set Relation To (This.RelationalExpr) Into This.ChildAlias In This.ParentAlias Additive

  11.     Set Skip To (This.ChildAlias)


  12.     This.lRelation = .T.

  13. Endif

代码中的 lRelation 是一个自定义属性。这样,我们就完全获得了 VFP 表单固有表单环境中临时关系的设置。

假设,你在设计一个一对多的查询表单,那么,使用 MySPT 类库中的类,你已经可以完美的在将数据操作分离出来的状态下达到和VFP经典设计一样的效果。

结束了吗?没有。因为我们还要靠它构建我们内存中的“数据库”。

在 MyRelation 中,增加 InsertTirgger、UpdateTirgger、DeleteTirgger三个自定义方法,它们是空方法。需要你在实际业务场景来实现,如果你需要插入触发器、更新触发器和删除触发器。

这时,你的数据环境类中 Append 方法的方法就可以改写了:

  1. Private All Like l*

  2. Local loCursor        As MyCursor Of MySPT.VCX,        ;

  3.       loRelation    As MyRelation of MySPT.VCX,    ;

  4.       llOver        As Logical,                     ;

  5.       llReturn        As Logical


  6. m.llOver = .F.


  7. *!* 执行新增

  8. For each m.loCursor in This.Objects

  9.     If Upper(m.loCursor.BaseClass) == [CURSOR]

  10.         m.llReturn = m.loCursor.Append()

  11.         

  12.         If m.llReturn = .F.

  13.             m.llOver = .T.

  14.         EndIf

  15.     EndIf

  16. EndFor


  17. *!* 执行插入触发器

  18. If m.llOver = .F.                                            && 如果没发生错误,则执行触发器

  19.     For each m.loRelation in This.Objects

  20.         If Upper(m.loRelation.BaseClass) == [RELATION]

  21.             m.llReturn = m.loRelation.InsertTirgger()

  22.             

  23.             If m.llReturn = .F.

  24.     Exit

  25.             EndIf

  26.         EndIf

  27.     EndFor

  28. EndIf


  29. Thisform.Refresh

用同样的方式,更改你的数据环境的 Edit 和 Deleted 方法。这样,你就构建出来一个内存数据库的主要部分。



我们离成功已经很近了。。。。。。

现在我们还差记录级规则的实现和字段级规则的实现。从观察中我们可以感觉到,记录级规则是已经添加/编辑完记录之后执行的。所以,我们可以再稍微改造一下 MyCursor 类,增加三个自定义方法:BeforeNew、New、AfterNew(),其中,BeforeNew 中写入:

  1. Return .T.

然后将 Append 中的代码剪切到 New 方法中,Append 的方法代码重新书写为:

  1. With This

  2.     If .BeforeNew()

  3.         .New()

  4.         .AfterNew()

  5.     Endif

  6. 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 出品的。。。。


远程视图不过是 SPT 的简单封装。既然已经搞定了远程视图,那么,SPT也变得很容易了。唯一需要注意的地方就是对 Cursor 的设置以及提交更新的方法。
按照 VFP 帮助文件中所推荐的方法,当我们修改完数据后,执行 TableUpdate() 函数就可以了。个人认为,这种方式才算是正宗的SPT,当然,在特定环境下,执行远程的存储过程,或者直接发送 Insert/Delete/Update 语句也是必要的,但是,如果你在提交更新时统统使用 Insert/Delete/Update 的话,我想,这是一个人为的灾难。

我们可以修改 MyCursor 类的 CursorInit 方法来使 Cursor 纳入我们的这套体系。你只需要恰当的将下列代码巧妙的置于 CursorInit 之中即可:

  1. *!*    设置游标是否可更新

  2. MakeTransactable(This.Alias)        &&  允许游标支持事务处理


  3. If This.lBuffering = .T.

  4. CursorSetProp([Buffering], This.BufferModeOverride, (This.Alias))        &&  设置缓冲

  5. EndIf


  6. If This.lSendupdates=.T.

  7.     *!*    设置游标可更新

  8.     CursorSetProp([Tables],                This.cRemoteTableName,    This.Alias)        &&  设置要更新的远程表名称

  9.     CursorSetProp([UpdateNameList],        This.cFieldRelation,    This.Alias)        &&  设置游标字段和源表字段的对应关系

  10.     CursorSetProp([KeyFieldList],        This.cMainKey,            This.Alias)        &&  设置主键(主键,时间戳)

  11.     CursorSetProp([UpdatableFieldList],    This.cEnabledField,        This.Alias)        &&  设置可更新字段

  12.     CursorSetProp([SendUpdates],        This.lSendUpdates,        This.Alias)


  13.     *!*    设置更新方式

  14.     CursorSetProp([WhereType],            This.nWhereType,        This.Alias)        &&  设置更新方式

  15.     CursorSetProp([UpdateType],            This.nUpdateType,        This.Alias)

  16.     

  17. Endif


当然,你可以修改它以适应你的应用场景。

然后,你可以修改你的 Save 方法:

  1. This.BeforeSave()


  2. Select (This.Alias)


  3. m.LcOldError = On([Error])

  4. On Error


  5. If This.BufferModeOverride = 3[/color]

  6.     m.Temp = Tableupdate(.F., This.lForce)  && 开放式行缓冲,提交当前行

  7. Else

  8.     m.Temp = Tableupdate(.T., This.lForce)  && 开放式表缓冲,提交当前表

  9. Endif


  10. If m.Temp = .F.                                && ODBC 错误

  11.     *!*  在这里处理更新冲突,


  12. Else

  13.     This.AfterSave()

  14.     On Error &LcOldError

  15. Endif

看,就是这么简单,我们就完成了SPT的设置和更新。当然,如果你非要提交长长的 Insert/Delete/Update ,也不是不可以,但是,

这需要你在这个结构上自己实现。。。虽然我也打算增加对这种方式的支持,但是,我仅仅是将其作为一个备用手段,而不是主要手段。

到这里,就该结束了。后面还有一段废话。写不写在我,看不看在你。

熟悉CA的都知道,CA有生成器,而且,可以很容易的添加到数据环境中。至于那种把 CA 拖放到表单上的做法,我通常是嗤之以鼻。

这里是我的一个演示,它像CA吗?

当你把我前面所描述的东西完善之后,再去看CA,你会哈哈大笑。。。。如果你看过我写的B/S开发入门教程 ,在 MyCursor 上动一动手脚,那么,这玩意估计就可以通吃各种架构的应用程序了。

至此,你就成为了万里挑一的绝世高手(是星爷演的那个绝世高手,不是那个负责搞笑的“绝世高手”哈)。

当你换一种语言开发程序时,相信你会很容易的理解它们所描述的数据处理方式和术语。事实上,你在 VFP 的世界构建出了自己的 BLL 和 DAL 。

VFP过时了吗?

————————————全文到此结束,谢谢欣赏——————————————————————


补遗:远程数据库的事务在哪里启动?在 MyEnvionment 的  Save 方法中。。。

Tags:

发布: admin 分类: 程序开发 评论: 0 浏览: 230
留言列表
发表留言
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。