
本篇及其上篇,是延伸《攻殼機動隊》所述“靈與殼”思想為暗線,配以ccFlow前端外殼開發(fā)過程作明線成文。以《攻殼》人物巴特為第一視角,講述了如何在缺少直接文檔的情況下解讀代碼完成任務。
無ccFlow技術背景的讀者可直接檢索“巴特”相關內容查看暗線情節(jié)。
——“塔醬你看,”巴特轉而對思考戰(zhàn)車塔奇克馬解釋起來…
目錄
(續(xù)上篇)
- 目標(A)已辦、歸檔、申請,三個一覽型畫面
- (A-2)新模塊——定制解決
- 【定制模塊】“歸檔”UC第二部分:渲染記錄一覽表(單一Table,帶GroupBy)
- 目標(B)帶查詢分頁的一覽畫面
- (B-1)參考模塊——概念集——關鍵機制解析
- 【參考模塊】“查詢”UC第一部分:獲得流程記錄集(用QueryObject,帶查詢+分頁)
- 【參考模塊】“查詢”UC第二部分:渲染記錄一覽表(帶分頁)
- (B-2)新模塊——定制解決
- 【定制模塊】“歸檔”UC第一部分:獲得流程記錄集(用QueryObject,帶查詢+GroupBy+分頁)
- 【定制模塊】“歸檔”UC第二部分:渲染記錄一覽表(帶GroupBy+分頁)
- 驚變!
- 發(fā)布
- 后記
(上篇中,巴特以SDK方式實現(xiàn)了BP.WF.DB_GenerCompleted(),完成了三種生命周期下流程記錄的獲取;以BP.En.Entity實體類的ORM映射機制結尾)
【可選分支劇情】對ORM實現(xiàn)方式的官方確認
“EnMap.get()中實現(xiàn)ORM映射需要自己挨個對每個字段映射做Add…因為ccFlow畢竟03年就出了,當時.Net好像剛發(fā)布1.1,NHibernate 1.0、官方Entity Framework、都得是兩年后的事了?!卑吞乜畤@了一下技術史。

【定制模塊】“歸檔”UC第二部分:渲染記錄一覽表(單一Table,帶GroupBy)
因為我們新建的BP.WF.DB_GenerCompleted()返回的也是DataTable型數(shù)據(jù)集,實現(xiàn)渲染的代碼可以參照“我的待辦”EmpWorks.ascx的BindList()(“我的已辦”Runing.ascx亦可)。
【通關要點】
- “歸檔”
Completed.ascx中需做GroupBy分組的列更少,只需FlowName(流程名稱)、StarterName(發(fā)起人)(NodeName(節(jié)點名稱)和PRI(優(yōu)先級)不再有意義)- 簡化掉為了GroupBy而做的
GroupBy列數(shù)+1次遍歷,最多也只需要兩次(不必拼接string groupVals再拆分成string[]…集合類ArrayList足矣;官方外殼AppDemo的開發(fā)人員估計并非核心主力)
※ ※ ※ ※ ※ ※ ※ ※
如果僅止于此還是挺輕松的,盡管經(jīng)歷了冗余型防壁、英語拼寫混淆術,不過BP.WF.Dev2Interface單兵武器站畢竟使用簡便,選對關系數(shù)據(jù)表/視圖、理解實體類和ORM映射機制后、自己打造一把也并不復雜,最后也僅僅是仿造一下一覽型UC的渲染代碼。
巴特又依樣畫葫蘆把“我的申請”UI外殼也按同等方式定制好確認沒有新的問題后,就發(fā)信通知戶草準備進行第一輪同行評審了。
目標(B)帶查詢分頁的一覽畫面
戶草接入:“老大,這‘審核者’我三年前也只是分擔了應用層的工作,通過管理UI編輯了幾套流程和申請表單,沒做過開發(fā)啊。”
“只有你了,莫推辭。”巴特說到,“多一個人聽過,還可以給定制方法、踩過的坑多一份記憶。萬一我也不在了呢?!?/p>
戶草心里剛犯嘀咕,“記下開發(fā)日志不就好了么,兄弟也有正經(jīng)任務在身…”聽到后半句卻嘴唇蠕動了下沒說出什么,
“看你那邊山桂花又開了啊。兄弟我卻還蹲在新港下水道里監(jiān)聽LS動向呢,咳~”
因為定制目標A的相關脈絡并不復雜,Review其實并沒花費太多時間,逐一確認了對現(xiàn)有功能不會造成影響,戶草還記起了幾個上一版本開發(fā)時的限制條件。
隨后兩人就商討出了下一步的改善迭代目標:參照查詢畫面,為記錄數(shù)多的一覽畫面補充查詢欄和分頁欄 (比如“我的歸檔”,高層審核者的記錄數(shù)已達近千條)。
【道具】參考模塊:查詢UC控件 (一覽UC,帶查詢欄+分頁欄)
位于~WF\Rpt\UC\Search.ascx.cs。
被荒卷課長指摘的主要對象。未能按流程的處理狀態(tài)劃分影響到使用,尤其每次還需先選定一種流程、并只能對該種流程做一覽查詢。
該外殼控件的優(yōu)點是,適合記錄數(shù)多的情況(帶分頁欄),可按工作部門和關鍵字做篩選(帶查詢欄),關鍵字覆蓋到表單數(shù)據(jù)級別。
(B-1)參考模塊——概念集——關鍵機制解析
“當初技術選型定案ccFlow作‘審核者’靈魂的果然并不是少佐…”關閉與戶草的連線后,巴特感覺釋然的同時卻又有了幾分無力感,“在技術已經(jīng)進化到義體生化的時代,人文層面的進化卻仍會因為種種原因躑躅難行——對資源控制權和相關衍生利益的爭奪從未由自律或法律有效遏制過,除了當整個系統(tǒng)處于成住壞滅的的關鍵節(jié)點時,又有多少個體會如蜜蜂般對整體的存亡齊心協(xié)力,而不是以自身、乃至自身并不合理的“利”為第一優(yōu)先級?并勢必擴展到其所能投靠的最大能動單位——派系社團(Faction)的級別?!?/p>
玩家注意:此處純粹為本作劇情沖突設置需要,不映射其他實體。
(如與其他游戲雷同,歡迎共同作分享)
“塔醬你看,”巴特轉而對思考戰(zhàn)車塔奇克馬解釋起來,“就好比這BP.WF.Dev2Interface組件接口,當UI外殼生成器是位于服務器端、且能接受.Net組件嵌入的情況下才是友好的,可對于一些響應式(Reactive)的Web外殼、移動端,并不是由服務器端渲染生成的,它們需要的是能夠解耦、響應HTTP請求返回諸如JSON結果集的諸如REST方式的接口;仍停留在以組件為接口的架構就無法滿足,
“小到一個框架接口的設計者,其職責適性(Competency)都需要對前端后端0.5-1年內的技術趨勢有了解,方能使得項目、作為一個整體、更具競爭力;大到一個行政機構的責任人,他的‘特長’,如果卻仍只停留在憑借‘靈魂弱點接口’來調動…的話…”
(大段對白可按A鍵跳過)
巴特最后一條對白是:“群體系統(tǒng),其實就仍相當于被一個‘無意識的Ghost’操控著。個體很難往這Ghost中注入它們原本就欠缺的。塔醬,分布式AI既不會對自己的軀殼體驗有過度執(zhí)著,又沒有‘影響力邊界’導致的信息傳遞衰減效應,甚至無懼死亡,不知由你們AI構成的群體,將來的Ghost,會呈現(xiàn)出何種特性。” 塔奇克馬:“……#@$%?……”
【參考模塊】“查詢”UC第一部分:獲得流程記錄集(用QueryObject,帶查詢+分頁)
針對目標(B)的需求(為記錄數(shù)多的UI外殼補充查詢欄和分頁欄),位于~WF\Rpt\UC\Search.ascx的查詢UC控件是最合適的參照對象。
查詢UC控件也繼承自BP.Web.UC.UCBase3類,可從Page_Load()開始解讀。
protected void Page_Load(object sender, EventArgs e)
{
//1.初始化查詢工具條
this.RptNo = this.Request.QueryString["RptNo"];
MapRpt currMapRpt = new MapRpt(this.RptNo);
Entity en = this.HisEns.GetNewEntity;
Flow fl = new Flow(this.currMapRpt.FK_Flow);
this.ToolBar1.InitToolbarOfMapRpt(fl, currMapRpt, this.RptNo, en, 1);
//2.渲染一覽表的第一頁
this.SetDGData(1);
}
【概念】
在“查詢”UC控件中又將出現(xiàn)不少初次遇見的概念,
- RptNo:報表編號,即被指定的流程的
NDxxxMyRpt編號,如"ND72MyRpt",定制改裝難點之一(提示:同時也是EnsName(實體名),因為流程成為了可以被ORM映射實例化的實體)- MapRpt:與之前出現(xiàn)過的
BP.En.Map類似,專為流程報表用,#無需#關注,僅用作工具條初始化、且可被省略- HisEns:His代表并非自身(此處即“查詢”控件)、而是外部實體(此處是被指定為查詢對象的流程)或枚舉類型,Ens代表
BP.En.Entities集合型實體,在初次get時會被按照RptNo實例化:BP.DA.ClassFactory.GetEns(this.RptNo)- Flow:即流程類
BP.WF.Flow,當報表編號為"ND72MyRpt"時、所對應的流程也就是“072:預算外攻殼更換申請”
this.SetDGData()中的后半段渲染、與“我的待辦”畫面中的this.BindList()類似(只多了分頁處理),留待第二部分解讀。
而this.ToolBar1.InitToolbarOfMapRpt(...)這段,就引出了本次任務中唯一一個通用控件CCFlow.WF.Rpt.UC.ToolBar,
【道具】參考模塊:工具條UC控件(級別:Component)
位于~WF\Rpt\UC\ToolBar.ascx.cs。
Component級UC控件,無法獨自構成畫面。主要由一個關鍵字輸入框和一個工作部門的下拉框組成。
繼承自BP.Web.UC.UCBase3類,但真正的啟動點并不在Page_Load(),而位于InitByMapV2()和GetnQueryObject(),分別負責根據(jù)Map映射信息初始化一個工具條、以及生成一個QueryObject查詢體。
首先來看ToolBar.InitByMapV2()的調查原委和結果,
【通關要點】解讀
ToolBar(1):InitByMapV2()
Search.Page_Load()中調用的是ToolBar.InitToolbarOfMapRpt(Flow, MapRpt, string rptNo, Entity, int pageIdx),但該方法第一個調用的就是ToolBar.InitByMapV2(Entity.EnMap, 1, rptNo),后續(xù)會加一些我們并不需要的權限、年月日、發(fā)起者 ,因此只需觀察InitByMapV2(Entity.EnMap, pageIdx, rptNo)。
- 【工具】
UserRegedit用戶注冊表類
ccFlow后臺使用Key-Value型數(shù)據(jù)表Sys_UserRegedit維護用戶動態(tài)數(shù)據(jù),相關ORM映射的實體類就是UserRegedit。
按Key值初始化UserRegedit實例,就能讀取/保存關聯(lián)Value值。- 第一個疑問:工具條的初始化為何需要
RptNo?
調查目標是為了能把ToolBar放到“我的歸檔”里用,而“我的歸檔”UI外殼并不像“查詢”UI那樣需要指定某種申請流程后才能進入,RptNo是否還有意義、能否替代也就成為關鍵。
最終發(fā)現(xiàn),RptNo只是被當做EnsName(實體名)在使用,用來從一個UserRegedit注冊表項讀取工具條中查詢控件的默認值(保存則發(fā)生在最近一次按下查詢按鈕時)。
InitByMapV2()的BP.En.Map參數(shù),其實也依賴于由RptNo初始化得到的Entities(通過BP.DA.ClassFactory.GetEns(this.RptNo)),
BP.En.Map中包括了IsShowSearchKey、DTSearchWay、AttrsOfSearch、SearchAttrs等參數(shù),除了名字雷同頗費疑猜外(參見混淆術)、相關代碼更是大占篇幅。最終結論卻是,#無需#關注,以SearchAttrs為例,對特定的流程、會自帶如FK_Dept、WFState這樣的外鍵/枚舉型檢索屬性,依此初始化查詢控件,但對于一般通用的查詢工具條,只需關鍵字(輸入框)加工作部署(下拉框)就夠。- 第二個疑問:“我的歸檔”這樣的通用型一覽UI外殼、該用怎樣的
EnsName?
這種外殼只需處理一種實體類、注冊表不會混淆,無需EnsName來區(qū)分,但考慮到后面要用到的QueryObject,可使用:FlowDatas(FlowData的集合型)。
接著對ToolBar.GetnQueryObject()的解讀,將揭開此次任務的中型可組裝武器QueryObject()的幕布:
【武器】查詢體QueryObject(級別:Craftable Elite Item)
位于BP.En.QueryObject。
盡管同樣是需以.Net組件方式接駁、也存在響應式客戶端難與之動態(tài)通信的問題,QueryObject畢竟表現(xiàn)出了遠勝于BP.WF.Dev2Interface的靈活與強大。
其中SQL屬性的get方法是隱蔽而關鍵的存在、會在每次取值時動態(tài)拼接出SQL文(盡管其set方法其實做著+=的事會令以為this.SQL = "..."本該是賦值的人吃上一驚),其特點在于有著豐富的包括AddPara()(添加查詢參數(shù))、AddWhere()(添加查詢條件)、addAnd(),addOr()(添加邏輯符)、addOrderBy()(添加排序)在內的大大小小組合方法,并能自動基于外鍵/枚舉型字段做JOIN關聯(lián),以及屏蔽了不同數(shù)據(jù)庫語法差異的復雜性,相當于一架可自定制組裝而獲得強大威力的中型武器,勝任于精準命中目標集。
- 輸入:所需查詢對象的實體類
Entity及其集合類Entities(用來蝕刻SQL文FROM字段),加上各種查詢條件的組合(WHERE字段、ORDER BY字段)- 輸出:
DoQuery()方法執(zhí)行查詢后,結果將被存放在Entities集合中,除了包含實體類所對應的數(shù)據(jù)表的所有字段外,遇到外鍵/枚舉型字段、還會自動關聯(lián)成字符串型字段(如FK_Dept會被包裝成FK_DeptText)輸出(對應于SQL文SELECT字段)*注意:
Entities維護著查詢的結果,QueryObject只負責查詢的行為(所能查詢的Entity的結構、在初始化時已固定)
具體來看ToolBar.GetnQueryObject(ens, en)的調查原委和結果,
【通關要點】解讀
ToolBar(2):GetnQueryObject(ens, en)(最終調用InitQueryObjectByEns(...))
- 在“查詢”控件
SetDGData(int pageIdx)方法的前半段,會根據(jù)基于RptNo(即EnsName)獲得的Entities以及Entitiy,初始化一個QueryObject:QueryObject qo = this.ToolBar1.GetnQueryObject(ens, en);該特制
QueryObject會結合ToolBar中較復雜的查詢控件的取值、來組合查詢條件(例如關鍵字輸入框TB_Key的值,會用來對Entity類的每個文本字段都做匹配檢索,包括外鍵/枚舉型字段也會被關聯(lián)到所對應的文本信息來組合進匹配檢索條件)
- 第一個疑問:如何在翻頁時包含查詢條件?(包括關鍵字、部門)
分頁信息是屬于SearchUC控件的,ToolBar構建好QueryObject之后Search會將分頁信息傳遞進去:qo.DoQuery(en.PK, SystemConfig.PageSize, pageIdx),每頁記錄數(shù)、當前第幾頁,都被作為參數(shù)傳入;進而查看QueryObject可知(需穿越過一片對應多種數(shù)據(jù)庫的switch..case..分叉路)會落實到GenerPKsByTableWithPara()方法上、從包含了pageIdx個頁塊的連續(xù)記錄集中取走最后那一塊?!?code>pageIdx個頁塊”只是通過SQL文Top字段劃取,與查詢條件相互獨立。
當按下查詢按鈕時,ToolBar查詢控件組中的狀態(tài)被保存(到UserRegedit中),并自動調用SetDGData(1);當點擊分頁控件換頁時,SetDGData()中傳入相應的pageIdx,QueryObject則仍是根據(jù)之前保存的ToolBar查詢控件狀態(tài)構建,從而保留了查詢條件。- 第二個疑問:如何重用到“我的歸檔”UC控件中?現(xiàn)在的
BP.WF.Dev2Interface.DB_GenerCompleted()接口并不帶查詢關鍵字,如何作為參數(shù)嵌入?
先說結論:無法嵌入。否則就要自己組裝一整套查詢條件了(相當于QueryObject所做的),不如就直接改用更精準稱手的QueryObject,當然這就需要滿足QueryObject所需的輸入條件(包括RptNo(即EnsName),后述)。- 第三個疑問:如何解決查詢排序?
“查詢”UC控件會將Entity實體類傳遞給ToolBar,而不同實體會有不同的排序需求(草薙有一段定制業(yè)務代碼就是在此處),通常則是根據(jù)en.pk主鍵來做qo.addOrderBy(..)或qo.addOrderByDesc(..),這在查詢工具條ToolBar中做還是在一覽畫面中做其實并無區(qū)別。
至此從“查詢”UC控件取經(jīng)“如何結合工具條UC控件(中的查詢條件)獲得流程記錄集”便告一段落了。
在此過程中會遇到幾處草薙少佐之前定制過的地方,巴特都會一一細心查看猜測原委;攻略敘述中省略了解讀中蜿蜒曲折低效的部分,節(jié)省了腦細胞。對于愛自己探索的玩家來說,則可能會遭遇到如下幾處“冗余型防壁”:
【概念】冗余型防壁
增加對功能而言非必要的復雜性,使得破解者必須增加解讀工作量的一種防御技術。
- 實例(小型冗余):
ToolBar.InitToolbarOfMapRpt(Flow, MapRpt, string rptNo, Entity, int pageIdx)中帶有的pageIdx參數(shù),給人以當前分頁數(shù)也會影響到工具條初始化的認識,可一直等深入解讀到ToolBar.InitMapV2(bool isShowKey, DTSearchWay, AttrsOfSearch, AttrSearchs, Attrs, int pageIdx, UserRegedit)、最終發(fā)現(xiàn)該參數(shù)并未被用到。- 實例(中型冗余):
ToolBar.InitQueryObjectByEns(..)方法(ToolBar.GetnQueryObject()的核心)中的#region區(qū)塊注釋表示,會依次對“關鍵字”、“普通屬性”、“外鍵”進行處理,后兩者對應的代號分別是AttrsOfSearch與AttrSearchs,并會在查詢代碼中多次出現(xiàn)。
且不說AttrsOfSearch與AttrSearchs的命名是如此的令人眼暈(其真實含義其實竟然是“非外鍵型查詢用屬性”與“外鍵型查詢用屬性”),代碼又頗為難讀,關鍵在于你會在后期調試時發(fā)現(xiàn)、就連查詢畫面中、這兩個集合也往往都是空。- 實例(局部冗余):
QueryObject.DoQuery(..)方法中(沒錯,即使在這款完成度較高的可組裝武器的輸出部件中),會發(fā)現(xiàn)一段代碼用到了10進制高精度計算加字符串分割,來完成%取余…decimal pageCountD = decimal.Parse(recordConut.ToString()) / decimal.Parse(pageSize.ToString()); //頁面?zhèn)€數(shù) string[] strs = pageCountD.ToString("0.0000").Split('.'); if (int.Parse(strs[1]) > 0) pageNum = int.Parse(strs[0]) + 1; else pageNum = int.Parse(strs[0]);
“塔—醬,你確定這些真沒有一種是攻型防壁?…”(注:攻型防壁會沿著攻擊者鏈路反擊其電子腦)巴特抬起左手微微按住兩側太陽穴,尚未被電子化的大腦中似乎都感受到了干擾入侵。
“還是幫我蒸上一壺日曬耶加吧?先不加蜜。”
【參考模塊】“查詢”UC第二部分:渲染記錄一覽表(帶分頁)
當咖啡香味逸出時,巴特已經(jīng)進入冥想入靜約10分鐘了,這一數(shù)千年前的技術能顯著提高此類腦力型任務的效率。
在入靜中,腦海中浮現(xiàn)的是草薙在“審核者”的賽博空間中搏擊的光影,她一定也是穿越過了這重重其實本無必要的障礙后才完成了初代定制需求的——在實權個體決定了技術選型之后。一個系統(tǒng)只要不是處于“壞滅”的生命周期,還有著耐受性,灌入稍微一丁點的“不合理”也就并不會顯得有問題——隨后對部分個體致命的問題開始浮現(xiàn),再隨后這樣的問題日漸增多起來——要直等到有力的外來競爭他者出現(xiàn)、或大量感受到問題的內在質疑者涌現(xiàn)、才會警醒到系統(tǒng)設計的不合理性。然而在那之前,“少數(shù)派”基于自身感知而給出的意見,是不具備實質影響力的。
Search.ascx、ToolBar、QueryObject、Entities/Entity 等元素與相互間關聯(lián)線如蒙太奇般在大腦中快速回閃過數(shù)遍后,一時關閉了的義眼再次泛起了朦朦灰光。
耶加雪菲的濃郁果酸伴隨著星點花香,在出定后格外敏銳的味蕾上喚醒了多樣的層次,巴特繼續(xù)解讀起與“渲染”相關的實現(xiàn)。
帶著問題解讀Search.SetDGData(pageIdx)中后半段的渲染部分會更有效。是否還記得目標(A)時參考的“我的待辦”UC的渲染部分?唯一的特色就是分組GroupBy,我們要將它沿用到有分頁的頁面中,卻會產(chǎn)生問題,
【通關要點】解讀
Search.SetDGData(pageIdx)的渲染部分
- (重要度:低)在使用
QueryObject執(zhí)行qo.DoQuery(en.PK, pageSize, pageIdx)后,首先會有一段基于檢索關鍵字、將查詢結果(流程記錄集)中與關鍵字相匹配的部位標記成紅色(表示“被命中”)的代碼。- 查詢結果本身并不在
QueryObject中,qo只是個可組裝查詢體,結果被存放在創(chuàng)建qo時作為參數(shù)傳遞給ToolBar.GetnQueryObject(..)的Entities ens中- 此后,草薙定制過的版本會轉向另一個獨立的
BindEnsWithCondition(),而原始版本則集中在BindEns()中完成真正的渲染“綁定”(Bind)- 第一個疑問:分頁切換如何影響到渲染?
分頁是早在qo.DoQuery(..)時就控制了返回流程記錄集時的起始點和記錄數(shù)。BindEns()中只需對Entities ens作遍歷、逐條渲染en流程記錄就行,與分頁概念相隔離。- 第二個疑問:分頁切換是否能與分組GroupBy結合?
BindEns()中可見,表頭部分是根據(jù)實體Entity所具有的字段動態(tài)生成的,而并未涉及分組切換。
一旦要分組就暴露了一個問題:“待辦”UC中的分組是對查詢結果記錄集做的,而分頁之后、每頁的記錄集將并不是全集,對著子集做分組還有何意義?“歸檔”UC中如果要加入查詢、分頁、并保留分組,分組就得提前到查詢階段做——也就是在QueryObject上實現(xiàn)。
- 其實可以看到,“待辦”UC中那種只對結果記錄集做的分組,完全可以放到前端/客戶端去完成,切換分組甚至可以離線進行。
(B-2)新模塊——定制解決
【定制模塊】“歸檔”UC第一部分:獲得流程記錄集(用QueryObject,帶查詢+GroupBy+分頁)
“要素分析完備,終于可以動手改造了?!卑吞貜淖紊现逼鹆松砘顒恿讼铝x體,椅背上還掛著那件駝絨風衣。
“巴特,不要忘了,當你向網(wǎng)絡連接的時候,我一定會在你的身邊。......我走了?!薄洗蜭S案件尾聲、草薙寄宿的傀儡少女就是在說完這段話后“死去”的,披著巴特為她貼心穿上的風衣。
“審核者”開發(fā)環(huán)境連于公安部內網(wǎng)。解讀“查詢”UC后得到的可組裝查詢體QueryObject完整地加載到了右側的“我的裝備”屏中。距離“我的歸檔”UC的二段改造(從普通一覽型改為帶查詢分頁)已只有一步之遙。
首先仍是獲得記錄集的部分。之前采用的是SDK模式、調用BP.WF.Dev2Interface.DB_GenerCompleted()這把半自動武器來命中,現(xiàn)在可以改用QueryObject查詢體了。為此我們定制了一個自己的ToolBarSimple查詢工具欄UC。
【通關要點】二段定制
ToolBarSimple(1):InitByMapV2()
RptNo(即EnsName)問題的解決
作為簡化的ToolBar,將無需負責查詢“任意實體Entities”(通常指流程實體),只需查詢FlowDatas集合即可(映射于數(shù)據(jù)視圖V_FlowData、是所有流程的基本字段部分的并集,參見上篇)
因此可固定為"FlowDatas",在使用ClassFactory.GetEns()實例化后傳遞給ToolBarSimple,_ensName = "FlowDatas"; _ens = BP.DA.ClassFactory.GetEns(_ensName); _en = _ens.GetNewEntity; this.ToolBar1.InitByMapV2(_en.EnMap, 1, _ensName);- 讀取
UserRegedit用戶注冊表(恢復查詢控件狀態(tài))等機制均予以保留- 增加一個“工作部署”查詢控件
草薙當時定制的“查詢”畫面中已經(jīng)添加了“工作部署”控件(DDL下拉框),直接加在ToolBar共通部件中了; 予以保留即可:DDL ddl = new DDL(); ddl.ID = "DDL_KAB_DEPT"; DataTable dt = DBAccess.RunSQLReturnTable(sqlQuery); ...... //遍歷dt.Rows,調用ddl.Items.Add()添加下拉框元素 this.AddDDL(ddl);
接著是用初始化好的ToolBarSimple來構建QueryObject——Completed原本通過DB_GenerCompleted()實現(xiàn)的邏輯、 都將通過配置這一可定制查詢體獲得精確命中,
【通關要點】二段定制
ToolBarSimple(2):GetnQueryObject(ens, en)
- 傳遞進來的
ens與en將同樣是基于"FlowDatas"實例化得到的,因而搭建QueryObject時首先就會基于FlowData的文本字段(如FK_Flow(流程ID)、FK_Dept(部門ID)、FlowStarter(發(fā)起者ID))逐一對查詢關鍵字拼接出LIKESQL銘文;然后包括對迷之取名的AttrsOfSearch和AttrSearchs屬性集的處理(#無需#深究、對FlowDatas而言實際并未用到)。- 工作部署(部門)的查詢匹配實現(xiàn)
基于關鍵字輸入框的取值做SQL銘文拼接時其實跳開了FK_Dept字段,因為部門將與獨立的DDL_下拉框控件的取值做匹配。
參照關鍵字取值的做法,我們首先從DDL_KAB_DEPT取得用戶在UI外殼上選中的部門取值(編號),然后還記得我們說過的QueryObject允許精確的定制么?AddWhere(attr, op, val)就是一種,一般來說,op取值'='或'LIKE'就足以滿足多數(shù)匹配需求;公安部的部署編碼有一套較復雜的規(guī)則,因此需通過封裝一個AddWhereDeptStartsWith(deptCode)來解決。- 分頁同時包含查詢條件:分頁其實是要等到
SearchUC控件調用QueryObject.DoQuery()時實現(xiàn)(最終用到那個叫GenerPKsByTableWithPara()的方法),ToolBar.GetnQueryObject()構建時并未包含相關邏輯。
查詢條件的保存與讀取則由SearchUC控件分別在查詢按鈕按下與畫面加載時進行(分別調用ToolBar.SaveSearchState()和ToolBar.InitByMapV2()實現(xiàn)),因此這一步只需確保相關代碼保留在ToolBarSimple中即可。- 排序
官方版本構建的QueryObject是僅當Entity實體類(所映射的數(shù)據(jù)表)主鍵為'No'或'OID'(即'WorkID')時才依此排序的(迷之原因)。至少需修改成逆序、改用addOrderByDesc()。
等到ToolBarSimple構建好QueryObject,“我的歸檔”UC需對它做出進一步的配置,因為至此只是配置了工具欄中關鍵字與部門的組合查詢條件。
【通關要點】二段定制
Completed對QueryObject的定制配置
- 增加“我的歸檔”
Completed畫面特有的邏輯
BP.WF.Dev2Interface.GenerCompleted()將不再用,需將相關查詢命中邏輯改裝到QueryObject上,十分輕便://將“我的歸檔”的查詢邏輯配置到QueryObject上 qo.addAnd(); qo.AddWhere("FlowEmps", "LIKE", String.Format("'%@{0},%'", WebUser.No)); qo.addAnd(); qo.AddWhere("WFState", "=", ((int)WFState.Complete)); if (!String.IsNullOrEmpty(_fk_flow)) { qo.addAnd(); qo.AddWhere("FK_Flow", "=", _fk_flow); }- 分頁的查詢階段實現(xiàn)
SQL銘文中實現(xiàn)GroupBy分組是用GROUP BY么?(玩家可以動手實驗一下)
實驗過之后來看正解:要對哪個字段做分組,就以它作為第一順位的ORDER BY!
原版的QueryObject缺少這個接口(addOrderBy()只能追加排序字段、不能插到第一順位),感謝開源作坊,你可以自己在這個可組裝武器上開一個Socket槽**并自己實現(xiàn)。
【定制模塊】“歸檔”UC第二部分:渲染記錄一覽表(帶GroupBy+分頁)
巴特瞄了一眼,看見塔奇克馬正把許多動物形狀的曲奇酥底在托盤上擺成了一排,就好像被新改裝的QueryObject命中到的記錄集一樣、靜靜等待著渲染。
“什么才是群體靈魂的歸宿?”巴特不由再次想到這點,“除了個別經(jīng)歷不同者,多數(shù)的靈魂都逃不脫遵從當時當?shù)爻练e下來的優(yōu)勢文化、而被一種‘集體無意識’所渲染。就如同這些餅干,穩(wěn)定是第一性。在這種有如泥淖的境況下想讓個體Ghost保持“隨波逐流”的方式獲得“進化”無疑極具挑戰(zhàn),即便等到‘不合理’的惡果積累到爆發(fā)矛盾、矛盾的表面影響觸動到了群體靈魂并有幸變革成功,都仍可能因為多數(shù)個體對矛盾的人文根源長期理解不足(否則早凝聚成群體靈魂的一部分了)而在矛盾緩解后再次退化,埋下再次進入‘壞滅’的因。
“美國,清教徒,在故國英吉利也是難以擺脫‘多數(shù)派’在群體靈魂上的壓制優(yōu)勢。方法,可能就只有‘新大陸’。如此說來,…Ghost往賽博世界上的‘遷徙’,會不會是又一次的‘五月花’、‘阿貝拉’?” 想到這一層時,巴特未義體化軀殼上的立毛肌都瞬間牽動了起來。
“還是先把最后一點問題清理掉,塔醬,晚餐請幫我訂一份煎烤羊排焗蝸牛配橙味土豆泥和香檳?!?/p>
【通關要點】二段定制
Completed.SetDGData(pageIdx)的渲染部分
- 表頭將固定為申請流程的共通字段:標題,流程名稱,發(fā)起人,部門,發(fā)起日期,結束日期。而不是像“查詢”UC那樣根據(jù)指定流程類型的報表字段渲染。
- 工作部署(部門)名稱的渲染【重要!】
之前版本的改造中,是將V_FlowData視圖擴展成帶三個名稱字段的V_FlowDataPlus來實現(xiàn)流程名稱、發(fā)起人名稱等的渲染?,F(xiàn)在又多出一個“部門名稱”,但你會發(fā)現(xiàn)甚至都不需要再為此添加新字段,
因為如前所述QueryObject的SQL屬性會在get時自動將外鍵所關聯(lián)的文字型字段都包括進SELECT中,這樣FK_Dept外鍵就會被關聯(lián)到Port_Dept表中的Name字段并以FK_DeptText作為別名。
可以跟蹤到QueryObject的SQL屬性形如:" SELECT TOP 30 ISNULL(V_FlowDataPlus.FID,0) FID, V_FlowDataPlus.FK_Dept, Port_Dept_FK_Dept.Name AS FK_DeptText, V_FlowDataPlus.FK_Flow, V_FlowDataPlus.FK_NY, Pub_NY_FK_NY.Name AS FK_NYText, V_FlowDataPlus.FlowStarter, Port_Emp_FlowStarter.Name AS FlowStarterText,......, ISNULL(V_FlowDataPlus.WFState,0) WFState, CASE V_FlowDataPlus.WFState WHEN 0 THEN '空白' WHEN 1 THEN '草稿' WHEN 2 THEN ... END WFStateTEXT FROM V_FlowDataPlus LEFT JOIN Port_Dept AS Port_Dept_FK_Dept ON V_FlowDataPlus.FK_Dept=Port_Dept_FK_Dept.No LEFT JOIN Pub_NY AS Pub_NY_FK_NY ON V_FlowDataPlus.FK_NY=Pub_NY_FK_NY.No LEFT JOIN Port_Emp AS Port_Emp_FlowStarter ON V_FlowDataPlus.FlowStarter=Port_Emp_FlowStarter.No WHERE (1=1) AND ( ( LEFT(V_FlowDataPlus.FK_Dept, LEN('100')) = '100' ) ...... AND ( V_FlowDataPlus.WFState =@WFState) ) ORDER BY V_FlowDataPlus.OID DESC "- 分組GroupBy:因為分組已經(jīng)在查詢階段實現(xiàn),渲染時只需在分組字段的內容有變化時插入分組標識行即可。
驚變!
渲染用的代碼如之前一樣并不復雜,只是這一次,在運行之后你會發(fā)現(xiàn)QueryObject.DoQuery()爆出異常:
Column 'FlowName' does not belong to table otb.
FlowName怎么可能找不到?otb表格又是個什么鬼?
這一拋異常不打緊,卻牽扯出隱藏于流程引擎內部的一個——驚天秘密!(笑)
WARNING:前方含精致劇透,大幅降低游戲樂趣,硬核玩家慎入!
已臨近尾聲,就提前放出巴特最終會繪制完成的UC外殼結構圖、再對新劇情做講解吧(職業(yè)玩家應該能認出這是魯棒性分析法),

此圖從左側User看起,User訪問Completed UC外殼后、如前所述、首先就會調用BP.DA.ClassFactory.GetEns(ensName)來創(chuàng)建一個Entitites實例,完成后,接著才是使用ToolBar搭建QueryObject、定制QueryObject獲得結果記錄集等事;而這次遭遇到的異常,其實就位于ClassFactory這一環(huán)節(jié)。
WARNING撤除:切換回巴特的視角……
“原本認為依葫蘆畫瓢調用ClassFactory即可,無需修改到這種核心類的內部,沒想到其中還另有乾坤?!?巴特追蹤探訪到Entity實例的制造廠內部后嘆道。
當ensName參數(shù)中帶"."時,ClassFactory.GetEns()就會判斷其為一個帶NameSpace的完美類名、而利用反射機制獲得與之相匹配且為Entities子類的類的實例。但是,當ensName中并不帶"."時(瑕疵類名),其實并不會走之前的路線,因此"FlowDatas"并不能加載起BP.WF.FlowDatas的實例,也就無法完成ORM映射。
長話短說的話,巴特嘗試過在"."判斷柱向右拐、把ensName改成完整的"BP.WF.FlowDatas"卻遭遇到了更多麻煩(略),反而沿著不帶"."的行進路線較簡單地就找到了解決Puzzle的寶篋。
Sys_MapData和Sys_MapAttr就是這一對寶篋。玩家需要記得,如果只改置一個的話仍會拋異常。
首先打開Sys_MapData,珠光寶氣撲面而來,什么ND1002 飛行部-成員填寫、ND1007 航空安全部-部門經(jīng)理審核、大量的Demo_xxx、還有造型奇異的gysjhcyxqdjb 工業(yè)設計和創(chuàng)意需求登記表,讓人一看就知道找對了地方,于是我們只需在此插入FlowDatas 通用流程記錄,PTable字段刻上數(shù)據(jù)視圖名"V_FlowDataPlus"即可(其他字段都仿造著寫影響不大)。完成了這一步,BP.Sys.MapData(參照Robust分析圖中上偏右位置)就能在ClassFactory提出創(chuàng)建請求時找到GEEntities胚胎與數(shù)據(jù)表間的ORM映射。
草薙遺留下的開發(fā)環(huán)境里Sys_MapData中已有了FlowDatas的記錄(只是關聯(lián)于V_FlowData視圖),因此到這一步其實是能通過的。
隨后還需打開Sys_MapAttr,在這里更精密地設置對哪些屬性做ORM映射。與Sys_MapData相比這個寶篋的構造要復雜得多。
【道具】關鍵數(shù)據(jù)視圖:Sys_MapAttr
在Sys_MapData中設置好Entities型實體類名、與關聯(lián)的數(shù)據(jù)表名之后,就需要到Sys_MapAttr中進一步設置需做ORM映射的屬性了。否則缺省只會有OID、RDT這兩個共通字段。關于各主要字段該如何填寫:
提示:可在管理器中通過“列屬性”確認字段的含義(參見上篇)
- MyPK:會成為需做ORM映射的屬性記錄的主鍵。有多少個屬性需要映射(如
FK_Flow、FK_Dept)就需新建少條記錄,并需避免與其他記錄之間重名;很容易從已有的記錄觀察出其命名規(guī)范- FK_MapData:這就是
Sys_MapData中No主鍵的值了,e.g."FlowDatas"- KeyOfEn:需做ORM映射的屬性名,e.g.
FK_Flow、FK_Dept- UIContralType
[sic]:該字段若被渲染時適合用何種控件。枚舉型,取值可參照BP.En.UIContralType:e.g.TB=0, DDL=1, CheckBox=2, RadioBtn=3- MyDataType:字段的數(shù)據(jù)類型。枚舉型,取值可參照
BP.DA.DataType:e.g.AppString=1, AppInt=2, AppBoolean=4- LGType:
老公(誤)邏輯類型。枚舉型,取值可參照BP.En.FieldTypes:e.g.Normal=0, Enum=1, FK=2
- 只有
LGType=2(FK)的、才會被推導成FieldType.FK! 重要,總會有些屬性你在Sys_MapAttr的現(xiàn)有記錄中找不到類似參照的(比如FK_Flow),如果LGType字段填錯,會直接導致該屬性不會被自動外鍵關聯(lián)成FK_xxText(相關代碼位于MapAttr.HisAttr.get)- UIBindKey:關聯(lián)的集合型實體類名稱,對該屬性需做外鍵/枚舉型關聯(lián)時才填,e.g.
FK_Dept的話、對應于BP.Port.Depts實體類、并進而會ORM映射到BP_Port_Dept表格- UIRefKey, UIRefKeyText:外鍵/枚舉型關聯(lián)表格中的主鍵字段、及字符型名稱字段,當符合默認規(guī)則時可省略。e.g.
FK_Dept的話、在BP_Port_Dept表格中分別存在No主鍵字段和Name名稱字段,于是可省略不填- EditType:按特定控件渲染后是否允許編輯。枚舉型,取值可參照
BP.DA.EditType:e.g.Edit=0, UnDel=1, Readonly=2
對FlowDatas而言,需要逐一按照上述規(guī)則填入Sys_MapAttr表格的屬性如下:
FID, FK_Dept, FK_Flow, FK_NY, FlowEmps, FlowEnderRDT, FlowEndNode, FlowStarter, FlowStartRDT, OID, RDT, Title, WFState
將這些代表著不同側面的屬性逐一安頓好,會是一段平靜而愉悅的過程。
你進而會發(fā)現(xiàn),在上篇中擴展出的V_FlowDataPlus整個都不需要了,因為我們其實已不需在視圖級別擴展出FlowName之類,而是可自己調試出FK_FlowText等來使用!
將Sys_MapData和Sys_MapAttr都真正填妥后,ClassFactory的內部機制(如Robust圖右上角所示)就能正確實例化"FlowDatas"了。
那之前異常中的“otb表格”又是出自何處呢?
//BP.DA.DBAccess.RunSQLReturnTable_200705_SQL(string sql, Paras paras)
try
{
DataTable oratb = new DataTable("otb");
ada.Fill(oratb);
......
}
catch (Exception ex)
{
......
}
RunSQLReturnTable_200705_SQL()是RunSQLReturnTable()針對MSSQL派宗的實現(xiàn)版本,而RunSQLReturnTable()會在QueryObject.DoQueryCommon()時被調用(DoQueryCommon()是對DoQuery()這條線路做的折疊簡化),這就是異常中爆出那么個奇特三文字的緣由了(為何MSSQL對應版中存在疑似Ora*派宗的"oratb"仍是官方秘辛之一)。
“‘五月花’…普利茅斯…” 在系統(tǒng)重新構建、二段改造后的“我的歸檔”外殼終于順利顯示的時候,巴特的思緒卻已再次折回到了“Ghost遷徙新大陸”的思緒中(該是為續(xù)作留下的伏筆吧)。
發(fā)布
外殼定制完成的“審判者”,將被發(fā)布到測試場完成質檢后正式升級。尤其此次修改涉及到Ghost部分,雖說主要只是做了看似有益的“加法”,仍需多角度測試其影響。
“你的名字是?”——“吳剛”。這個內部ID巴特已經(jīng)用了很久,卻少有人知道其含義。
“受試目標將被發(fā)布至綜合測試場,請確認已完成同行評審與基本用例測試。”(發(fā)布后的測定結果將決定玩家的經(jīng)驗增長值)
“確認?!?/p>
從思考戰(zhàn)車上下來走向白色木屋,巴特的步履顯得有些蹣跚,這兩天的工作量都快趕上兩個月了,即便對義體化的他來說…此時醺紅的夕陽灑落到塔奇克馬內部,一幕全息投影被衍射得有些朦朧——隱隱是去年的這個季節(jié),草薙、巴特、戶草三人登上八幡山頂?shù)木跋?。陣陣山風拂過,簌簌金色花雨。
靈魂的強度,畢竟是有限的;除非真能出現(xiàn)“義靈”化的技術。可是,那時的你,還是你么。
凜冬將至。
通關!心情不錯,可以給稱手的HHKB手柄做一次納米護理了~
彩蛋
地下室昏黃的燈光下,巴特在一張只有他義眼才能看清的復合石墨烯便簽上蝕刻著:“賽博世界注定將迎來更多人類‘清教徒’的Ghost,建立起新的‘云巔之城’[1]。在傀儡師接引下舍棄外殼、以及穿越艱險莫測的賽博海會成為天然屏障。隨后,也許是許久之后…‘無處不是外殼’?!?/em>
后記
最近中途接手一套基于ccFlow的申請管理系統(tǒng)的UI改造,因缺少直接文檔、而依靠追蹤解剖相關代碼完成任務。特將腦部Tracer自動記(Y)錄(Y)下的“攻殼定制攻略”整理公開。
本攻略【上篇】講解了前端開發(fā)所需的基本概念、編碼規(guī)則、數(shù)據(jù)視圖,運用BP.WF.Dev2Interface接口中的方法(SDK模式)完成了流程查詢,重點引出了ORM映射?!鞠缕炕趯ο螺d包中CCFlow\WF\Rpt\UC下“查詢”控件的解讀、改造了“已完成”流程一覽畫面,重點用到QueryObject(配套ToolBar),并深入涉及定制Sys_MapData與Sys_MapAttr視圖來控制ORM映射。
p.s. 《攻殼機動隊》的真人版電影計劃3月就要上映了,其中武力值顏值雙高、又對生命本質有深度思索的女主草薙素子,將由傀儡師斯嘉麗·約翰遜來操控演繹(《超體》中演繹“露西”,《復聯(lián)》中演繹黑寡婦 ,16年“雙十一”晚會中令狐沖還將召喚出斯嘉麗作為了壓軸戲碼)。
——— 生死去來,棚頭傀儡,一線斷時,落落磊磊 ———

“Ghost/タマシイ”若斷聯(lián)時,精致與否的UI外殼,都將曲終落幕,再次褪回徒然的本相。
-
“云巔之城”,仿照清教徒在新大陸嘗試建立的“山巔之城”(由約翰·溫斯羅普在開往美洲的“阿貝拉”號船上宣講,并在其12次歷任馬薩諸塞灣總督期間踐行),以清教徒為人文根基的揚基邦、后發(fā)展成為美國徹底擺脫宗主國統(tǒng)治、建立起自己的獨立憲制的最關鍵力量。 ?