ccFlow工作流UI外壳定制攻略(上)

144
作者 天空之诚
2016.12.29 16:31* 字数 7417

攻壳机动队

(关键字:前端、Web界面、SDK模式、QueryObject、ORM映射)

目录

  • 本攻略的好处 = 5万元+
  • 缘起
  • 起步
  • 外包
  • 目标(A)已办、归档、申请,三个一览型画面
    • (A-1)参考模块——概念集——关键机制
      • 【参考模块】“待办”计数器
      • 【参考模块】“待办”UC第一部分:获得流程记录集(调用SDK接口)
      • 【参考模块】“待办”UC第二部分:渲染记录一览表(单一Table,带GroupBy)
    • (A-2)新模块——定制解决
      • 【定制模块】“已办”计数器、“归档”计数器
      • 【定制模块】“归档”UC第一部分:获得流程记录集(调用SDK接口)
      • 【定制模块】“归档”UC第二部分:渲染记录一览表(单一Table,带GroupBy)

本攻略的好处 = 5万元+

5万元,是基于.Net国产开源工作流引擎ccFlow定制Web界面外壳的官方报价(5万/人月,或12万/次培训)。
与笔者之前调研的jBPMN4涅槃之作Activiti引擎相比的话,ccFlow胜在是中文文档、自带符合国内用户习惯的Web界面(AppDemo包)、并在国内拥有了一定的用户群;但就此次定制开发的经历而言、问题也同时存在:

  • 代码易维护性易读性较低
    类名/函数名英文拼写错误,C#中字符串拼接/空判断方法不规范、三元表达式语法不熟练等基础问题降低了代码易维护性,无标注又无实际作用的冗余代码段降低了代码易读性
  • 对BPMN2.0国际标准的支持是在2015年9月新近追加
  • ccFlow6起取消对多国语言的支持
  • 参考资料尚不够面向开发目标化
  • 值得注意的是,ccFlow的开源遵循的是GPL协议,商用则无法闭源

——本文仅供【已经】选型了ccFlow并需定制界面部分的技术者参考,并不代表作者个人推荐——
将比《黑客帝国》还早10年的神作《攻壳机动队》(但比《机械战警》要晚2年)用于“渲染”ccFlow攻略,未免有点明珠弹雀了,不过脑洞既开、就顺着这个笔法来写吧。

缘起

巴特面前摆放着一本《开源工作流引擎ccFlow——从入门到迷路》,早已义体化的双眼则泛着微光直视前方,空无而深邃。

距离草薙少佐离开公安9课已经3年多了,八幡宫研究所外也再次随风吹起山桂花的幽香,“人生一世,草木一秋”,虽说义体比普通人的肢体强健得多,可是——灵魂呢?

赛博(Cyber)驱动的仿生人(Android)“审核者”是采用公元2003年就发布的ccFlow引擎作为其“灵魂”(Ghost)的,最初每家采购“审核者”的企业还需要各自维护其灵魂的升级(注:当对灵魂做过定制时、这种升级并不总是简单),后来随着云时代到来、以及基于对定制影响拓扑分析的区隔热更新技术成熟,灵魂主体部分已经上升到了云端。“审核者”的灵魂的安全强度,也随之晋级到了联盟区块链的级别。

可是,人的灵魂呢?

在能真正探明人的灵魂“生从何处来,死至何处去”之前,从逻辑上说,恐怕无法真正回答得了这个问题。除了草薙素子之外,毕竟还没有原生人(包括由人生育后天经过改造的义体人)、其灵魂能进出于赛博世界;而即使是与(傀儡师的)“赛博自主意识”融合而变强的草薙的灵魂,她真正人类的部分是否真地已依附电子媒介活得好好的,或是只是赛博读取记忆后的再生体,都并无法断言…

【任务】审核者UI外壳定制(级别:E,极安全)
荒卷课长对仿生人“审核者”的UI外壳不甚满意,提出了新的需求

若是以前,巴特一定不会对此种内部维护任务感兴趣,包管找个理由丢给户草了事。可是这次,“审核者”……

起步

深吸了一口桂树林间的怡人空气,巴特飞速输入“\\Kusanagi:”(友情提示:草薙的日文读音)对“审核者”代码做了全文检索。“只修改过这点么…”巴特自语到,“主要都聚集在界面相关的页面.aspx、控件.ascx…恩,这里有一个BP.En包中的QueryObject…”

思考战车塔奇克马(Tachikoma)上堆放着其他资料,一边烘焙着已泛金黄的桂花曲奇酥煮着玄米茶、一边为他的老友切上了一首遠野魂音版的《那些花儿》。

依照惯例,首先列出我(玩巴特这个人物通关后)觉得有用的资料、便于推断本攻略的参考价值,第三方资料几近于无:

选用巴特视角进入游戏时、“审核者”的开发环境已经构筑于一台虚拟机之中,只需登录后打开IDE就能进行修改-调试-发布的整个过程(否则需自行构筑。草薙作为前任开发者留有一段作业交接视频,不过不知是否时间匆忙、录制得并不算完整。

“帮我加载一个版本2的AppDemo包做对照,另外,给这首歌挂个单曲循环吧”巴特关照塔奇克马。

【道具】AppDemo包(品质:Rare)
官方的UI外壳范例,定制的关键。原始版本原本位于开源码库的CCFlow\AppDemo,因为不可知的原因已被官方“临时下架”,但9课保留了历史版本


“嘿,连Demo包目录名都没改就用了…那个人可真够敷衍了事啊”巴特随手复制出一个AppNew目录,用来存放这次修改的代码,“类重名错误,给每个.aspx.cs都补上namespace就行。”

红圈自动差分提示修改范围,这次的UI外壳定制就从这里出发。

外包

“……单单看这一小段交接视频和这堆混乱的代码、资料,不知道要看到猴年马月…”

猴年马月,按照中国太阴历,这个月正是申猴年午马月。”塔奇克马呆萌的声音解释到。

“……”悻悻地嚼了一口曲奇酥,巴特首先接驳上公众终端QQ模块,白色义眼扫入了383-352-596,QQ外壳上立刻显示出“ccFlow 19号”,“申请加入” 巴特语音确认道。

很快一个清脆干练的女声传来:“您好,客服编号‘敏’为您服务,请问您是需要使用ccflow吗?”

【人物】CCFlow客服‘敏’(原生人,女性)
CCFlow美女工程师兼首席客服,接通QQ群即可收到联系。
NPC,保留真人玩家以该角色视角在线的可能。
性格开朗,敬业认真,专业娴熟,不过也曾有过因客户频繁需求变更、不得不超期驻扎外包现场的经历。


经过一番简短的询问,


“50,000RMB/人月…9课近期不会有这样一笔预算给内部项目吧。”巴特心里盘算着,已经有了结论。

目标(A)已办、归档、申请,三个一览型画面

荒卷老爹的新需求可以简化成这张图来满足:


UI外壳改造目标

相当于新增两个位于不同处理状态(已办、归档)、和一个发起人是自己的流程的一览画面,避免都使用查询画面做入口不够清晰;这看起来也是个很简单的需求么,先理清几个概念:

【概念 & 相关代号】

  • 发起人:一个申请流程实例的发起者。变量名/缩写=Starter
  • 处理人:一个申请流程实例的审批者,可以有多人。变量名/缩写=Emp、Worker
  • 我的待办:流程处于“运行中”生命周期状态、且当前处理人是自己。变量名/缩写=EmpWorks
  • 我的已办:流程处于“运行中”生命周期状态、且自己已处理过。别名:在途变量名/缩写=Runing(注:[SIC]原拼写就这样…)
  • 我的归档:流程处于“已完成”生命周期状态,且自己已处理过。变量名/缩写=Completed
  • 我的申请:流程处于任意生命周期状态,且发起人是自己。变量名/缩写=Apply

*提示:部分常量定义位于Components\BP.WF\PubLib\Enumlib.cs,如WFState.Complete(状态=已完成)

(A-1)参考模块——概念集——关键机制

编程是一个需要"实践记忆"的活动,单单看“攻略记录”可是学不来的。建议玩家读取CCFlow5.sln项目存档一起操作。

“从启动页AppNew\Default.aspx看,这个AppDemo包原来采用的还是一种古老的UI外壳技术——Frame,不知是何用意…”巴特审慎地解读到。

打开左侧导航栏所对应的AppNew\Left.aspx,扫视当前版本从“发起”到“设置”的5个链接,很容易便能顺藤排摸出这个UI外壳的线路纹理(如下图):


UI外壳结构规则
  • 导航栏链接会指向位于WF\目录的某个.aspxUI页面,要做一览型UI需参照“待办”、也就是~WF\EmpWorksSmall.aspx(如前述EmpWorks是“待办”的代号,Small则标识了页面布局风格)
  • WF\目录下的UI页面其实都是空壳,注册重用了UC控件,如~WF\UC\EmpWorks.ascx
  • WF\UC\目录下的UC控件类,才包含了真正的流程实例获取&渲染的代码

【道具】第一块参考模块:我的待办UC控件 Get!(一览型UC)
位于~WF\UC\EmpWorks.ascx.cs
主要功能是查询取得符合特定条件的流程实例,然后渲染为一览列表
虽说继承自BP.Web.UC.UCBase3类,追根溯源也是一个ASP.Net的System.Web.UI.UserControl,从Page_Load()方法开始解读即可。
*提示:UCBase3封装了共3层UC共通功能,不过完成此次UI定制#无需#去费时解读它们。

【参考模块】“待办”计数器

在解构EmpWorks.ascx.cs前先解决掉一个“小目标”练手:可以注意到Left.aspx的“待办”与“抄送”的右边各有一个计数器、每过100秒会自动刷新(0表示无记录)。这也是需要模拟的对象。

巴特很容易地从Left.aspx.cs中找到了计数器的实现,代码只有寥寥几行,通过直接SQL文查询视图WF_EmpWorks实现,

//Left.aspx页面属性:“待办”流程的COUNT计数值,只读。
public int EmpWorks
{
    get
    {
        string sql = @"SELECT COUNT(*) AS Num FROM WF_EmpWorks 
                        WHERE FK_Emp='" + BP.Web.WebUser.No + "' ";
        return BP.DA.DBAccess.RunSQLReturnValInt(sql); //返回Int型计数结果
    }
}

所涉及的“最小概念集”正好值得梳理一下作为入门。当然如果是大脑电子脑化过的角色(如荒卷课长)、阅读时的短期记忆能更快转化为长期记忆、这一学习环节就会更为简单,

【概念】流程相关编码
掌握数据表/视图/实体类解读技能的前置知识点。与仓库计件一样,对运行中/运行完成的流程、以及流程中的每个(环节)节点,都会用“编码”作标识。

  • WorkID:动态自增序列号,Int64型、全局唯一。一个流程启动后(运行时)生成,标识该流程实例,所有流程表单、节点表单的主键都使用该值。别名:OID
  • 流程编码:静态概念。三位数String型编号(最大999),标识某种流程(如“072:预算外攻壳更换申请”)。常用于字段FK_Flow(FK=外键)
  • 节点编码:静态概念。流程编码(e.g.001)+节点ID(两位数Int,最大99),表示某种流程中的某个节点(如“07201:发起”(预算外攻壳更换申请),不同流程中的节点允许重名)。常用于字段FK_Node(FK=外键)

【道具】第一张数据表/视图:WF_EmpWorks
开发环境中已有名为ccport的数据库,保管着开发用的数据表/视图。
WF_EmpWorks是其中专门为“待办”流程配置的视图(用到WF_GenerWorkFlowWF_GenerWorkerlist两张核心数据表,筛选条件为IsEnable = 1 AND IsPass = 0(表示未办理)

  • 数据库配置:项目的Web.configAppSettings下有AppCenterDSNAppCenterDBType
  • 数据库命名规范【重要】
    • Sys前缀=系统表,将用到的是Sys_MapDataSys_MapAttr映射表(任务后半段分析)
    • Port前缀=组织结构,如Port_Emp(人员表,Emp代表Employee)、Port_Dept(部门表)
    • WF前缀=流程规则描述,尤其是WF_GenerXxx系列是流程运行控制表,如WF_EmpWorks所用到的WF_GenerWorkFlowWF_GenerWorkerlist,分别维护着所有运行中流程实例的细节、和每个流程节点的处理者信息(如IsPass表示是否已被处理者审核通过)。
    • V前缀=国际惯例视图的前缀应为V_,却也有多个原因不明的例外(如WF_EmpWorks视图的命名就是)
    • ND前缀=节点表单(NDxxxxx)、流程报表(NDxxxRpt)、或流程轨迹表(NDxxxTrack)。本次UI外壳任务不涉及,但属于基础知识。
      当CCFlow处于“轨迹模式”时,编辑创建一个节点时就会创建一个表与此节点相对应,便于记录节点级别的数据变化(例如“ND07201:预算外攻壳更换申请发起节点表”包含了该环节的表单数据)。另一个“合并模式”不常用,不创建节点表、更快、但不记录细节。
  • 常见字段含义

    • 每个节点表都会有的字段:OID:工作ID,RDT:记录日期,Rec:记录人,CDT:完成时间,NodeState:节点状态
    • 开始节点(才)有的字段: WFState:流程状态

    *提示:字段列的命名有些颇为费解,可通过列属性确认其含义(如图)


“计数器居然是后台代码直接SQL文读的,没至少弄个REST方式的封装感觉对前后端分离不很友好啊” 巴特吐槽到,“算了我也没功夫自己弄一个。”

接着就开始解剖第一个大的参考模块EmpWork.ascx.cs控件,如前所述从Page_Load()方法开始,

//Step1.调用SDK接口获得“待办”流程的记录集(DataTable型)赋值给成员变量
dt = BP.WF.Dev2Interface.DB_GenerEmpWorksOfDataTable();

//Step2.基于成员变量dt进行一览表渲染
this.BindList();
【参考模块】“待办”UC第一部分:获得流程记录集(调用SDK接口)

首先F12切换到DB_GenerEmpWorksOfDataTable()方法的定义,可以看到针对是否授权(IsAuthorize)及何种授权有若干分支,这里#无需#对此多纠结、只需参考到其本质其实也是在对WF_EmpWorks做SQL查询即可(吐槽:与计数器那边用的同一视图,一览这边却有着多重条件分歧,两边竟然还能匹配得上…不深究)

【武器】BP.WF.Dev2Interface下的
DB_GenerEmpWorksOfDataTable()DB_GenerRuning()等接口
SDK模式开发接口层。其中DB_GenerXxx系列与完成本次定制任务有关,其特点是简单粗暴易于上手且输出够猛烈,接近于半自动型AK-b48。
输入:当前用户的编码BP.Web.WebUser.No (全局变量,string型)
输出:当前用户的待办、已办等不同特征流程的记录集(DataTable型)
*注意:用了SELECT * 所以相关表中所有字段均可在结果记录中用到

【道具】BP包
ccFlow官方的核心工具箱,多格。BP标志代表"Brain Power"(见官方文档),宣称着电子脑阵营的强大[*需注明出处]

  • BP.En30:基层实体类库,用处多多
    1.数据库交互
    2.实体类:实体的工作基于Map映射(即ORM)
    3.其他数据源交互:XML文件等
    4.组织结构类库:人员、部门、岗位,并提供登录控制
  • BP.WFv4:W/F(WorkFlow)相关
    • BP.WF.Dev2Interface:SDK模式开发接口层,仅DB_GenerXxx系列有用。完整列表则可参见CCFlow5接口说明文档(官方文档似乎经过了…削减(?),链接到9课缓存吧)
  • BP.Web.Control:公共Web组件库

*相比之下,CCFlow包下的 WF、WF.UC、WF.Rpt.UC(Rpt代表报表)更偏应用层

【参考模块】“待办”UC第二部分:渲染记录一览表(单一Table,带GroupBy)

得到DataTable记录集之后转到this.BindList()方法做渲染,迎面猝不及防便是一大段冗余代码——当按照PRI做GroupBy时将跳转到另一个BindList_PRI(),而这两套BindList却是犹如双生子般类似。

“古老的冗余型防壁么…迫使意图定制修改者必须肉眼多解读一倍以上内容,并增加后续维护所需耗费的精力。” 这里其实#无需#阅读BindList_PRI(),只需参照BindList()就能仿制出自己的UI外壳。

【概念】GroupBy分组
一览型UC的唯一特色。如下图,当前是按照流程名称(FlowName)分组的(例如“多次签证申请”),如果点击表头中的状态,会切换到按状态(WFState)分组,而状态列则会被当前的分组字段流程名称所替代。



伏笔剧透:这里所能参考到的GroupBy实现、其实只是伪实现…。玩家可检查代码看能否发现疑点所在。

巴特点起了一支烟又被塔奇克马迅速扑灭,只能抿了口玄米茶叹道:“这BindList()中搞了半天无非是遍历dt集合,把每条dr记录渲染成行,唯一特色无非就是得兼顾到GroupBy的因素,却又冗余地做成了(GroupBy列数+1)x记录数的遍历复杂度,而且对一次读入的记录数并没有限制…”

“嘛,算了,数据库里如果真积压着几千条“待办流程”影响到显示、卡死的也就不只是一个UI外壳而是N个人脑了。实际环境中还是可以用,在新UI外壳里面改掉也就是了。”

(A-2)新模块——定制解决

解读现有模块完成,开始定制新模块。

先从“我的已办”(=在途)的UI外壳入手。这里有一个捷径,

【道具】AppDemoLigerUI包(品质:Rare)
同样是官方的UI外壳范例,同样因为不可知的原因被官方“临时下架”了,但9课保留了历史版本。
除了使用LigerUI技术风格更接近主流外,其中还包括了一些AppDemo包中被省略了的内容。
*LigerUI技术本身并不推荐使用,优缺点可参考这篇技术选型真少不了甄别比较、无论面向灵魂的还是外壳。


AppDemoLigerUI包的导航栏UI

导航链接指向\WF\RuningSmall.aspx页面,“在途的英文…是Runing么?”巴特也不禁疑惑道,电子眼识别出是拼写混淆术

【概念】英语拼写混淆术(Spelling Obfuscation)
相比传统的代码混淆技术(Code Obfuscation),英语拼写混淆术在非英语母语尤其东亚地区运用较多。相比前者,拼写混淆更容易打击解读者信心,令其怀疑究竟是误解了单词含义还是用眼过度。眩晕对象0.5秒,并有小概率引发爆笑。

  • 这次任务会遇见的混淆/非常规代号、及其真实含义:
    IsExitsContral(=IsExistsControl..),Runing(=Running),Sta(=Status),Cash(=Cache!),LGType(=LoGicType)
【定制模块】“已办”计数器、“归档”计数器

“我的已办”的左侧栏计数器,如法炮制的话,是否也需自己编制一个WF_Runing视图呢?答案是不需要。

可以参考到~WF\UC\下面其实已经有一个“我的已办”一览型UC控件,它所用到的记录查询SDK接口是BP.WF.Dev2Interface.DB_GenerRuning(),参考就能镌刻出计数器所需的SQL铭文了:

public int Runing
{
    get
    {
        string sql = String.Format(@"SELECT * FROM WF_GenerWorkerList 
                WHERE FK_Emp='{1}' and IsPass=1 AND IsEnable=1", BP.Web.WebUser.No)
        return BP.DA.DBAccess.RunSQLReturnValInt(sql);
    }
}

编译运行即可看到第一个UI定制成果——导航栏中“我的已办”计数值已正确显示。


而“我的已办”一览UC控件(~\WF\UC\Runing.ascx.cs)经验证风格与“我的待办”相同,可直接链接上使用。

快速进入下一个一览画面的打造——“我的归档”(Completed)

首先仍是计数器部分。这次发现~WF\UC\目录下也没有现成的UC控件可用了,没有现成的DB_GenerXxx接口参考,得自己分析SQL铭文。

“还是用WF_GenerWorkFlowWF_GenerWorkerList定制出一张‘我的归档’视图?”这是巴特的第一反应。玩家只需记住一条线索,“我的归档”的特征、是WFState=3(3:Complete),然而你在WF_GenerWorkFlowWF_GenerWorkerList中却找不到一条WFState=3的记录。

巴特这时会切入CCFlow官方社区查询,再次获得客服‘敏’的回答,可这次却是包含着错误信息的(“History也在WF_GenerWorkFlow中”)


客服‘敏’对WF_GenerWorkFlow的解释隐含误导

正确的解答在这边——当然深度玩家可尝试自行在资料堆中查找


官方文档:高级开发 > 流程实例生命周期 > 流程完成后(数据归档)

节点数据表(NDxxx)的话就意味着有多少种流程就有多少个、包括“审核者”上线后新建的,该怎么组成视图呢?

其实数据库中一共也没多少视图,很快就能发现一张名为V_FlowData的视图,包含着WFState字段、并且已经是所有NDxxRpt数据表的拼接;在“审核者”中新建流程时,流程编辑器也会维护修改该视图。

【道具】核心视图:V_FlowData(可改装为:V_FlowDataPlus)
V_FlowData是数据库中唯一无差别维护着所有种类所有状态的流程实例的视图(包括“已完成”状态的流程实例),但只维护被所有种类流程共有的基本字段信息、不包含表单字段业务信息。
V_FlowDataPlus是通过对V_FlowData增设一览型UI所需的三个名称字段构成:FlowName(流程名称), FlowStarterName(发起人名称), FlowEndNodeName(结束节点名称)

  • 重要字段
    • OID:即WorkID,唯一标识一个流程实例
    • FK_Flow:外键,流程ID,关联到WF_Flow
    • FK_Dept:外键,部门ID,关联到Port_Dept
    • FlowStarter:流程的发起者的ID
    • FlowEmps:流程的处理者的ID组,多人,以特定格式分隔
    • FlowEndNode:流程的结束节点ID,关联到WF_Node

获得了V_FlowData之后,“我的归档”的计数器也就轻松完成了。官方文档会告诉你“流程状态=1、表示已完成”,你可以选择相信它、然后用实际查询结果教育自己

public int Completed
{
    get
    {
        string sql = @"SELECT COUNT(*) AS Num FROM V_FlowData 
                WHERE WFState=3 AND FlowEmps LIKE '%@" + BP.Web.WebUser.No + ",%' ";
        return BP.DA.DBAccess.RunSQLReturnValInt(sql);
    }
}

“我的归档”从UI画面~WF\Completed.aspx到UC控件~WF\UC\Completed.ascx都需要自己创建(参照“UI外壳结构规则”),遵照风格,真正的代码也藏到UC控件中。

【定制模块】“归档”UC第一部分:获得流程记录集(调用SDK接口)

需创建一个BP.WF.Dev2Interface.DB_GenerCompleted()方法,参考“我的已办”中用到的DB_GenerRuning()更为方便。

“少佐对BP.WF.Dev2Interface.DB_GenerRuning()已做过了精简,”巴特发现,

//Kusanagi: 1.コーディングスタイル改善 2.ソーティング
WF.Port.WFEmp emp = (WebUser.IsAuthorize) ? new Port.WFEmp(WebUser.No) : null;
sql = "SELECT a.WorkID FROM WF_GenerWorkFlow A, WF_GenerWorkerlist B WHERE "
        + (string.IsNullOrEmpty(fk_flowNo) ? "" : ("A.FK_Flow='" + fk_flowNo + "' AND "))
        + "A.WorkID=B.WorkID AND B.IsEnable=1 AND B.IsPass=1"
        + " AND B.FK_Emp='" + WebUser.No + "'"
        + (emp == null ? "" : (" AND A.FK_Flow IN " + emp.AuthorFlows));

GenerWorkFlows gwfs = new GenerWorkFlows();
gwfs.RetrieveInSQL(GenerWorkFlowAttr.WorkID, "(" + sql + ")", "RDT", true);
return gwfs.ToDataTableField();

“懒如伊人都忍不住要对这Coding Style出手改了吧~还有补了Sorting排序。”巴特从注释中读到。

值得注意的是,这里首次出现了ORM映射,就是GenerWorkFlows.RetrieveInSQL()这段(排序也是改在这里)。

【概念】ORM映射(Object-Relational Mapping)
实体类与关系型数据表之间的映射。以GenerWorkFlow实体类为例,

  • 实体类都继承自BP.En.Entitiy基类(或其辅助用子类)
  • 实体类都有一个集合类(如GenerWorkFlows),并继承自集合型的BP.En.Entities类(或其辅助用子类)
  • 实体基类的集合型BP.En.Entities中,已实现了从相应关系数据表读取实体实例集合的公开方法(如RetrieveInSQL()
  • 实体基类BP.En.Entity中,已实现了对应到单条关系数据表记录的CRUD(增删改查)操作
  • 实体类中必须重载的,就是只读属性EnMap(实体映射),需在这里声明所关联的关系数据表的名称(如Map map = new Map("WF_GenerWorkFlow"))、追加需映射到实体类属性集的数据表字段(如map.AddTBIntPK(GenerWorkFlowAttr.WorkID, 0, "WorkID", true, true)添加了从WorkID字段到同名属性的映射,并且从方法名可以看出,该字段可用输入框渲染(TB)是数值型(Int)且是主键(PK))
  • 实体类的集合类中必须实现的,只有只读属性GetNewEntity,通常只需象工厂类一样new一个实体类的实例即可
  • 基本属性(如GenerWorkFlowAttr中所列出)什么的、只属于具体实体类的个性/表相,它们会在EnMapget实现中被一一映射到关系数据表的字段上

*提示:映射思想是CCFlow的核心灵魂之一(官方语),贯穿于方方面面:类映射、UI展现映射、字段关系映射、查询条件映射、方法映射等等。

“我们用到的是V_FlowData视图,需要自己配一个FlowData实体类咯?”巴特继续自言自语着…至少有塔奇克马做伴、做定制开发类任务时也都并不孤单吧。

“匹配显示已有一个实体类的代码中包含了V_FlowData”塔奇克马提供了一条有价值的智能助言(IntelliHint)。

“哦?打开看看——果然!实体类BP.WF.FlowData”巴特为这个小確幸不由轻扬起了嘴角——“你装的这付义眼有点忒大,都看不清你的表情”草薙在时会这样说他。

“守护天使啊,如果你仍在那里的话,继续给我些幸运吧。”

//BP.WF.FlowData - revision 2
public override Map EnMap
{
    get
    {
        if (this._enMap != null)
            return this._enMap;

        //关系数据表映射
        Map map = new Map("V_FlowDataPlus");  //为获得名称使用V_FlowData的改装体
        ......
        //字段-属性映射
        map.AddTBIntPKOID(FlowDataAttr.OID, "WorkID");
        map.AddTBInt(FlowDataAttr.FID, 0, "FID", false, false);
        map.AddDDLEntities(FlowDataAttr.FK_Dept, null, "部门", new Port.Depts(), false);
        map.AddTBString(FlowDataAttr.Title, null, "标题", true, true, 0, 100, 100);
        map.AddTBString(FlowDataAttr.FlowStarter, null, "发起人", true, true, 0, 100, 100);
        ......
        map.AddSearchAttr(FlowDataAttr.FK_NY);
        map.AddSearchAttr(FlowDataAttr.WFState);
        map.AddSearchAttr(FlowDataAttr.FK_Flow);
        map.AddHidden(FlowDataAttr.FlowEmps, " LIKE ", "'%@@WebUser.No%'");

        //方法映射
        RefMethod rm = new RefMethod();
        rm.Title = "工作报告";
        rm.ClassMethodName = this.ToString() + ".DoOpen";

        this._enMap = map;
        return this._enMap;
    }
}

ORM问题解决,于是BP.WF.Dev2Interface.DB_GenerCompleted()就可以写成,

WF.Port.WFEmp emp = (WebUser.IsAuthorize) ? new Port.WFEmp(WebUser.No) : null;
sql = "SELECT OID FROM V_FlowData WHERE "
        + (string.IsNullOrEmpty(fk_flowNo) ? "" : ("FK_Flow='" + fk_flowNo + "' AND "))
        + "WFState = " + WFState.Complete
        + " AND FlowEmps LIKE '%@" + WebUser.No + ",%'"
        + (emp == null ? "" : (" AND FK_Flow IN " + emp.AuthorFlows));

FlowDatas fds = new FlowDatas();
fds.RetrieveInSQL(FlowDataAttr.OID, "(" + sql + ")", "FlowStartRDT", true);
return fds.ToDataTableField();

当山桂花飘香时,那段混合着斑驳阳光飒飒白衣与那个人盈盈笑声的影像就会再次被加载到记忆里。

“这是真实的存在,不是幻觉迷宫。”

(未完待续,继半自动单兵武器站BP.WF.Dev2Interface之后,可组装中型武器BP.En.QueryObject将登场)

博文强志