客户/服务器示例

客户/服务器示例描述了如下一些特征,使您可以了解到在客户/服务器应用程序中,如何将 Visual FoxPro 作为一个理想的前端开发工具。

若要运行客户/服务器示例应用程序

  1. 从“程序”菜单中,选择“运行”。

  2. 选择“...\Samples\Vfp98\CSApp”文件夹。

  3. 双击 Sample.app。

若要在“项目管理器”中打开客户/服务器示例应用程序的项目

注意如果您选择“商务规则”区域时,接收到一条“Class definition Bizrules.SalaryRule is not found”的错误,您需要在“项目管理器”中打开位于 Csapp 文件夹中的“BizRules”项目并为此项目重新编译 .exe 文件。建立的 .exe 文件在 Windows 注册表中注册类,防止了此错误的出现。

注释 这些主题的代码示例的编写强调了某些特别的代码行。如果要查看整个代码,包括错误检查和验证,打开项目中的类和程序。

客户/服务器示例应用程序类

在客户/服务器示例应用程序中,大多数的功能是通过类来实现的。对于用户的操作,相应地创建了基于这些类的对象。

CSEngine

当运行这个应用程序时,在 Csmain.prg 中在首先创建了一个基于CSEngine 类的对象,CSEngine 类在 Csprocs.prg 中定义。

oEngine = CREATEOBJECT('csengine')

CSEngine 类不仅提供了通用的环境设置、清除和实用方法程序,也包含了一些管理应用程序功能的方法程序。

方法程序 说明
DropTable 在后端数据库上删除一个表。
ExecuteTempSPT 执行 SQL pass through 命令,同时对 pass through 所产生的错误进行管理。
ServerStart 为了使雇员工资有效,创建商务规则对象。
ServerStop 释放商务规则对象。
ServerValidateRow 使用商务对象验证数据。

若要了解 CSEngine 类中的以上和其他的方法程序,请参阅 Csprocs.prg 中的代码。

OpenDBC

在创建了 oEngine 对象之后,Csmain.prg 中的代码在 Sample.vcx 中 OpenDBC 表单基础上创建了一个对象:

oStartForm = CREATEOBJECT('OpenDBC')

OpenDBC 表单允许用户在独占或共享方式下打开示例数据库,关闭数据库,修改和后端数据库之间的连接。在本示例中,您需要创建一个和后端数据库之间的连接来升迁表,然后将它作为远程视图来访问。

对于 SampleApp 表单上的“Database”按钮,在其 Click 事件中还包含一些代码可创建并显示 OpenDBC 表单。

OpenDBC 是一个模式表单。当在 OpenDBC 表单中选择“OK”后,将执行下面代码,同时程序继续执行下去。

RELEASE THISFORM
oEngine.Start = .T.

如果 OpenDBC 表单是在 Csmain.prg 中创建的,则关闭 OpenDBC 表单时,将执行下面代码,创建 Sample.vcx 中 SampleApp 类的一个实例:

IF oEngine.Start
   oCSApp = createobject('SampleApp')
   oCSApp.Show
ENDIF

SampleApp

在客户/服务器示例应用程序中,SampleApp 是一个主表单。此表单包含一个带有两个页面的页框。第一个页面上的控件允许您打开表、本地视图和远程视图,升迁表,恢复表的原始数据,并将视图在联机状态和游离状态之间切换。第二个页面上的控件允许您查看和编辑所选表或视图的数据。

根据需要,通过创建了两个附加的对象,使得对象的功能封装性能更加良好。这两个附加的对象是:Salaryrule 和 Conflicts。SampleApp 表单中的代码要对这些功能进行访问。

SalaryRule

SalaryRule 是一个自动服务程序(Automation Server)中的类。该自动服务程序的项目文件是 Bizrules.pjx,类是在 Bizrules.prg 中定义的。

DEFINE CLASS salaryrule AS Custom OLEPUBLIC

在 chkRules 复选框中,它的 Click 事件代码调用了 CSEngine 的 StartServer 方法程序,该方法程序中的代码创建了 SalaryRule 类的一个实例:

this.oServer = CREATEOBJECT('Bizrules.SalaryRule')

有关的详细内容,请参阅本节稍后的“在客户/服务器示例应用程序中实现商务规则”。

Conflicts

Conflicts 表单类显示带有数据冲突记录的字段的当前值、原始值和所改变的值。在 SampleApp 表单的 ResolveConflicts 方法程序中可创建 Conflicts 类的一个实例:

frmConflicts = createobject('Conflicts')
frmConflicts.Show

有关的详细内容,请参阅本节稍后的“在客户/服务器示例应用程序中实现商务规则”。

客户/服务器示例应用程序数据库

在客户/服务器示例应用程序中,数据库设计的很小,只是用来说明客户/服务器最基本的功能。

选择数据库

在“Open Database”对话框中,第一步要为客户/服务器示例应用程序选择数据库。该示例要求选择 Cs.dbc,该文件和客户/服务器示例应用程序其他文件所安装的目录相同。如果默认的文件夹是 Visual Studio ...\Samples\Vf98\Csapp 文件夹,或者该文件夹在您的路径里,则会自动为您选择此数据库。

CS 数据库包括如下内容。

说明
Employee 带有 employee ids、names、addresses 等字段的一个表。
Emp_init Employee 表的一个备份表。在试验示例中的各个选项时,改变了 employee 表的数据,在 SampleApp 表单中选择“Reset Initial Data”按钮,可以恢复这个表中的原始数据。

视图 说明
Emp_view_local 包含 employee 表的全部字段和记录的一个本地视图。
Emp_view_remote 包含 employee 表的全部字段和记录的一个远程视图。

连接 说明
Emp_connection 视图和一个数据源之间的一个连接。在打开 emp_view_remote 视图之前,需要创建这个连接。

打开数据库

在“Open Database”对话框中,第二步允许您打开或者关闭数据库。

“Open”按钮中的 Click 事件代码用于打开数据库。在打开数据库之前,Engine 对象的 DatabaseIsOpened 属性被设置为“真”(.T.),假定将要打开数据库。

OEngine.DatabaseIsOpened = .T.
lcExclusive = IIF(this.parent.chkExclDatabase.value = 1, 'EXCLUSIVE', 'SHARED')
OPEN DATABASE (OEngine.DatabaseFile) &lcExclusive

如果不能打开数据库,将触发“Open”按钮中的 Error 事件。同时,Engine 对象的 DatabaseIsOpened 属性被设置为“假”(.F.),并且显示一个错误信息。

修改远程连接

在“Open Database”对话框中,第三步为您打开 Visual FoxPro 连接设计器,允许您创建一个远程连接。在本示例中,需要创建一个和后端数据库的连接来升迁表,并将已升迁的表作为一个远程视图来访问。同时,将需要修改这个连接,以便提供一个用户 ID,密码和连接字符串。

有关创建远程连接的详细内容,请参阅 Visual FoxPro 《程序员指南》中的第八章“创建视图”

在客户/服务器示例应用程序中选择一个临时数据表

在 SampleApp 表单的第一个页面上,您可以打开表、视图,或者打开远程视图。cmdOpen 的 Click 事件代码根据 opgCursorType 选项按钮组的 Value 值,使用 USE 命令来打开本地表,本地视图或远程视图。

LblStatus 标签显示表或视图的状态信息。GetStatus 方法程序代码试图以独占方式打开表或视图,同时修改标签标题。如果表或视图不能以独占方式打开,则就以共享方式打开。如果不能以共享方式打开,则重新设置 lblStatus 标签的标题,表明表或视图已经以独占方式打开了。

在客户/服务器示例应用程序中更新数据

在编辑表的数据时,如果“Buffering”设置为“None”(CURSORSETPROP(“Buffering”, 1)),则直接对表中的数据进行修改,不能选择“Update”或者“Revert”按钮。

当选择其他的缓冲选项时,您就可以更新数据或恢复对数据所作的修改。

正在更新缓冲数据时(本地或远程方式下),请使用 TABLEUPDATE( ) 函数。该函数的参数允许您确定更新的范围,以及是否要强制进行更新。例如,在 SampleApp 表单中,“Update”按钮的 Click 事件执行下面的代码:

lnUpdateType = this.parent.opgUpdate.value - 1
llForce = this.parent.chkForce.value
llUpdate = TABLEUPDATE(lnUpdateType, llForce)

LnUpdateType 值由“Update Scope”区域中所选择的选项按钮来确定。

“Update Scope”(更新作用域)有三个选择项。

Row Update(更新行)

TABLEUPDATE( ) 函数的第一个参数设置为 0 时,只更新当前的行,而不管表缓冲和行缓冲是否有效。对于其他行的修改不能写到数据源中。

Table Update(更新表)

TABLEUPDATE( ) 函数的第一个参数设置为 1 时,则更新表中所有改变的行,从第一个被修改的行开始。如果遇到了数据冲突,也就是如果在行中的一个字段的 CURVAL( )lngCURVALLP_RP 值和该字段的 OLDVAL( )lngOLDVALLP_RP 值不同,则在产生冲突的行上停止表更新。

Table Update All(全部更新表)

TABLEUPDATE( ) 函数的第一个参数设置为 2 时,则更新表中所有改变的行,从第一个被修改的行开始。所有没发生数据冲突的行将被更新。

其他的选项在“Update Options”区域里被指定。

Force(强制更新)

在 TABLEUPDATE( ) 函数中,第二个参数确定是否进行强制更新。如果在“Update Options”区域里选择“Force”,则 TABLEUPDATE( ) 函数中的第二个参数为“真”(.T.),所作的修改在开始编辑后自动覆盖其他人对数据的改变。如果进行强制更新,则不会有任何冲突。

注释 如果用户改变了一个记录的主关键字,该行不能被更新,并在本地的临时表中删除此行。

Resolve Conflicts(解决冲突)

如果选择了“Resolve Conflicts”,客户/服务器示例应用程序会在发生冲突时发现它们,并允许您查看有冲突的数据,来确定是覆盖已有的修改或者是放弃你作的的修改。如果没有选择“Resolve Conflicts”,当发现冲突时,不能更新为你的数据。

IF this.parent.chkConflicts.value
   thisform.ResolveConflicts
ELSE
   wait window 'Update failed' nowait timeout 5
ENDIF

有关解决冲突的详细内容,请参阅本节稍后的“在客户/服务器示例应用程序中管理数据冲突”。

Business Rules(商务规则)

如果选择了“Business Rules”选项,客户/服务器示例应用程序将行使该程序所创建的商务规则。要了解有关“Business Rules”选项的详细内容,请参阅本节稍后的“在客户/服务器示例应用程序中实现商务规则”。

升迁 Employee 表

升迁数据库的代码是在客户/服务器 Application 表单的 cmdUpsize 中的 Click 事件里。该代码执行如下的操作:

  1. 和后端数据源进行连接。

  2. 如果一个表已经存在,则删除表。

  3. 创建一个字符串,该字符串中包含 SQL CREATE TABLE 命令。

  4. 将此字符串作为一个参数调用 SQLEXEC( )lngSQLEXECLP_RP,执行这个 SQL 命令。

  5. 打开 employee 表。

  6. 打开数据库中的远程视图。

  7. 从一个本地表中向远程视图添加记录。

  8. 使用 TABLEUPDATE( ) 更新远程视图。

创建一个游离视图

创建或删除游离视图的代码是在 SampleApp 表单中的 Offline 方法程序里。

IF lcMode = 'create'
   =createoffline(lcView)
ELSE
   =dropoffline(lcView)
ENDIF

在客户/服务器示例应用程序中管理数据冲突

当另外一个用户正在编辑源表中的数据时,如果数据改变了,就有可能发生数据冲突。在客户/服务器示例表单中,在“Update Options”区域有几个办法可解决冲突。

更新选项区域

如果在“Update Options” 区域选择了“Force”,当您开始编辑数据时,其间数据的任何变化都被您的修改所覆盖。

如果在“Update Options”区域选择了“Resolve Conflicts”,客户/服务器示例应用程序在发生冲突时找到它们,并允许您查看有冲突的数据,来确定是覆盖已有的改变或是放弃您作的修改。如果没有选择“Resolve Conflicts”,当发现冲突时,不能更新数据。

IF this.parent.chkConflicts.value
   thisform.ResolveConflicts
ELSE
   wait window 'Update failed' nowait timeout 5
ENDIF

如果选择了“Business Rules”选项,客户/服务器示例应用程序将根据商务规则来更新数据。要了解有关“Business Rules”选项的详细内容,请参阅本节稍后的“在客户/服务器示例应用程序中实现商务规则”。

若要产生数据冲突

  1. 分别运行 Visual FoxPro 的两个实例,模拟多用户对数据的访问。

  2. 在每个实例中分别运行客户/服务器示例应用程序。

  3. 在两个工作期中使用缓冲方式。

  4. 确保在 Visual FoxPro 的每个实例里都没有选中“Force”复选框。

  5. 在每个工作期中,分别对表的同一行的同一个字段进行修改。

  6. 在一个工作期中对表更新。

  7. 在另外一个工作期中对表更新。

当第二次选择更新表时,在 cmdUpdate 的 Click 事件中,下面代码试图对表更新:

llUpdate = TABLEUPDATE(lnUpdateType, llForce)

如果 llForce 被设置为“真”(.T.),将覆盖另外一个工作期的修改。如果 llForce 被设置为“假”(.F.),会检查出一个数据冲突,并且 TABLEUPDATE( ) 函数返回“假”(.F.)。如果 llForce 被设置为“假”(.F.),将调用 ResolveConflicts 方法程序:

IF llUpdate
   wait window 'Update succeded' nowait timeout 5
ELSE
   frmConflicts = createobject('Conflicts')
   frmConflicts.show
ENDIF

Conflicts 表单中的 Activate 事件代码创建了一个带有三个空记录的临时表,分别显示该行的当前值、原始值和改变的值。

=AFIELDS('aEmployee')
SELECT 0
CREATE CURSOR CS_CONFLICTS FROM ARRAY aEmployee

APPEND BLANK
APPEND BLANK
APPEND BLANK

Conflicts 表单中的 Next 事件代码用 Populate 弹出这个临时表,Populate 是一个带有当前值、原始值和改变值的冲突表格。例如,下面的 FOR 循环用表的原始值填充一个记录:

FOR m.i = 1 TO ALEN(aEmployee, 1)
   REPLACE (aEmployee[m.i,1]) WITH ;
      OLDVAL(aEmployee[m.i,1], lcEmployee)
ENDFOR

当用户要详细查看哪里发生了冲突时,这些冲突被放在一个明显的位置,来确定是否覆盖新值、恢复所作的改变,也可忽略冲突。

Conflicts 表单上,cmdUpdate 中的 Click 事件代码强制进行更新:

llUpdate = TABLEUPDATE(.F., .T.)

cmdRevert 中的 Click 事件代码放弃用户所作的改变:

lnRows = TABLEREVERT(.F.)

如果用户在 Conflicts 表单上选择了“Ignore”,则继续处理表中的其他冲突。

在客户/服务器示例应用程序中实现商务规则

客户/服务器示例应用程序使用一个自定义的自动服务程序来强制执行商务规则。这种结构被称之为三层模型,在中间层实现商务规则,将实际的数据以及客户界面分离开来。多个应用程序和多个数据库存都可以使用相同的商务规则集合,将它们在一个单独的位置上编成代码并进行维护。

在客户/服务器示例应用程序中,自定义的自动服务程序是 Bizrules。该自动服务程序的项目文件为 Bizrules.pjx,类是在 Bizrules.prg 中定义的。

DEFINE CLASS SalaryRule AS Custom OLEPUBLIC

在 CSEngine 中的类代码为商务对象提供了一个界面,该对象具有 ServerStart、ServerStop、ServerIsStarted 和 ServerValidateRow 方法程序。

ServerStart 方法程序创建 SalaryRule 类的一个实例,而 ServerValidateRow 方法程序将值传递给服务程序来进行有效检查:

lcError = this.oServer.validate(m.cTitle, m.nSalary, m.dBirth, m.dHire, m.cCountry)

SalaryRule 类的 Validate 方法程序实行一个通用的商务规则集合,并返回一个错误信息列表,每个错误信息对应一个规则的失败。例如,Validate 方法程序的下面代码确保工资减少的数目处于 Bizrules.dbf 表中指定的范围:

PROCEDURE validate
PARAMETERS lcTitle, lnSalary, ldBirth, ldHire, lcCountry

SELECT bizrules
LOCATE FOR lcTitle = ALLTRIM(title)
IF EOF()
   * Display error message
ELSE
   IF !BETWEEN(lnSalary, min_salary, max_salary)
      * Display error message
   ENDIF
ENDIF