應用程序框架實戰(zhàn)三十六:CRUD實戰(zhàn)演練介紹
從本篇開始,本系列將進入實戰(zhàn)演練階段。
前面主要介紹了一些應用程序框架的概念和基類,本來想把所有概念介紹完,再把框架內(nèi)部實現(xiàn)都講完了,再進入實戰(zhàn),這樣可以讓初學者基礎牢靠。不過我的精力很有限,文章進度越來越慢,所以準備切換一下介紹順序,把實戰(zhàn)演練提前,以方便你閱讀代碼。
實戰(zhàn)演練介紹
本系列實戰(zhàn)演練共分兩個部分。
實戰(zhàn)演練第一部分介紹如何快速解決CRUD機械操作,這一部分我將手把手帶領各位同學從搭建VS環(huán)境開始,創(chuàng)建程序集及各程序集間的依賴關系,以及引入依賴的外部DLL,并手工完成代碼示例中Application的三種界面操作。當你熟悉了手工操作方式后,你會發(fā)現(xiàn)這些工作枯燥乏味,效率低下,且容易出錯。為解決該問題,我將為你介紹PowerDesigner(PD)和CodeSmith兩大工具,分享PD及數(shù)據(jù)建模技巧,并發(fā)布配套的CodeSmith模板,你將體驗到高質(zhì)量完成機械代碼的最佳實踐。
實戰(zhàn)演練第一部分介紹的內(nèi)容很有用,它將幫助你完成大量體力活,但這個示例太簡單,體現(xiàn)不出領域模型的威力,實戰(zhàn)演練第二部分以權限模塊為例,演示如何開發(fā)具有一定業(yè)務邏輯的模塊。
當然,在介紹實戰(zhàn)演練第二部分之前,我需要先把框架內(nèi)部重要代碼講解完,本系列大致構成如下:
- 應用程序框架介紹
- 實戰(zhàn)演練1:CRUD開發(fā)
- 框架代碼詳解
- 實戰(zhàn)演練2:權限開發(fā)
示例代碼閱讀建議
很多同學反映,閱讀我的示例代碼非常困難,經(jīng)過了解,我發(fā)現(xiàn)大多是閱讀方法有問題,在此給出一些建議。
對于別人的代碼,你閱讀起來很痛苦是正?,F(xiàn)象,因為編程習慣和風格不同,另外一個原因是對代碼的意圖不了解。
我提供了Managements和Util兩個VS解決方案,Managements是簡單管理系統(tǒng)的代碼示例,Util是框架代碼,它是基礎設施層的一部分,被分離出來的原因是讓業(yè)務項目更簡單、編譯更快。
閱讀代碼需要從簡單的東西入手,這里就是Managements解決方案,它里面包含了一個叫Application的CRUD操作,它是權限系統(tǒng)中的應用程序模塊,具體功能先不要考慮。Application模塊是一個單表操作,非常簡單,幾乎沒有業(yè)務邏輯,很適合用來入門。EasyUi提供了三種數(shù)據(jù)錄入方式,即表單操作、表格操作、行內(nèi)操作,我使用Application模塊演示了這幾種操作方式。
有些同學下載代碼后,直接看的Util代碼,它主要包含一些基類和公共操作類,看起來困難就在所難免了。
為了減少程序集數(shù)量,我把一些第三方開源框架的代碼直接放入Util解決方案中,有些同學直接就看到這里面去了,然后告訴我異常復雜。不要泄氣,說明你是一個正常人,對于第三方開源框架的代碼,我也害怕,嘿嘿。我的代碼風格很容易辨認,就是每個方法都具有中文注釋,當你發(fā)現(xiàn)代碼沒有注釋或注釋全是英文,那一定不是我寫的,我的英文水平很菜。
當你把Application看熟以后,可以自己動手創(chuàng)建與Application類似的表,并手工完成三種CRUD操作。
當你對CRUD相關的類和配置熟悉之后,就可以查看基類實現(xiàn),這時候帶著問題去看Util代碼,會容易理解得多。
下一步就是把這些代碼逐步移植到你自己的項目中,只有把它們變成你的東西,才能發(fā)揮更大作用。這也是我寫這個系列的目的,不僅授之以魚,更要授之以漁。
建議你至少能夠擴展之后,才把我的東西用到你的項目上,不然坑很多,風險高。
CRUD概述
CRUD是Create、Retrieve(Read)、Update、Delete的縮寫,中文名:增刪改查。不論哪家的應用框架,都特別關照它幾兄弟,為什么?
對于一般的中小型項目,業(yè)務邏輯復雜的模塊只占很小的比例,一半以上的模塊都比較簡單。這些簡單模塊大致會通過一個界面或設備接口把數(shù)據(jù)收集上來,基本不經(jīng)過中間處理(業(yè)務邏輯),直接存入數(shù)據(jù)庫,在有需要的時候會把這些數(shù)據(jù)展示出來,或者為復雜模塊提供基礎支持。
這些簡單模塊工作量大,技術含量低,通過手工的方式編寫效率低下。
當采用了分層架構,特別是DDD分層架構之后,更是雪上加霜。
對于采用了DDD這種復雜分層架構,哪怕業(yè)務邏輯很復雜,還是存在不少機械工作,主要是創(chuàng)建各層的構造類型,比如領域?qū)嶓w及屬性、DTO及屬性映射、EF映射等,這些工作是必須的,但很枯燥乏味。
可以看到,只要是信息系統(tǒng),不論簡單還是復雜,都存在機械工作,不同性質(zhì)的項目機械工作所占比例不一樣而已。
后面我將用CRUD來指代開發(fā)中碰到的一切機械工作。
對于機械工作,最好的辦法是依靠生成器自動創(chuàng)建代碼,在討論生成器之前,先討論下EF相關的概念。
混亂的EF概念
EF在剛出土的時候,提供一個叫實體數(shù)據(jù)模型的edmx文件,打開這個文件,發(fā)現(xiàn)它是一個可視化類圖設計器。
在新建edmx文件時,有“從數(shù)據(jù)庫生成”和“空模板”兩個選項。
如果你選擇“從數(shù)據(jù)庫生成”,說明你自己先創(chuàng)建了數(shù)據(jù)庫,再通過edmx的反向生成工具生成代碼,這就是所謂的db first,first是先行或優(yōu)先的意思,db first就是數(shù)據(jù)庫先行,先創(chuàng)建數(shù)據(jù)庫,再讀取數(shù)據(jù)庫的元數(shù)據(jù),生成代碼。
如果你選擇了“空模板”,你可以在edmx可視化類圖設計器中創(chuàng)建一些類和關聯(lián),edmx會自動幫你生成代碼和數(shù)據(jù)庫。這種方式稱為model first,即模型先行,先有模型,后面再創(chuàng)建代碼和數(shù)據(jù)庫。
代碼生成出來后,你會發(fā)現(xiàn)這些代碼文件被包含在edmx文件中,包括領域?qū)嶓w和DbContext工作單元,還有一些T4模板,這有什么影響?
如果采用DDD分層架構,領域?qū)嶓w屬于領域?qū)?,而DbContext屬于基礎設施層,放到一起會導致高耦合以及分層不清,這是edmx的主要問題。另外一堆不相干的代碼生成模板與領域?qū)嶓w放到一起,估計也讓你看著心煩。
EF后續(xù)推出了更加輕量的使用方式,讓你可以拋棄edmx文件,直接使用原生的DbContext,并支持了code first開發(fā)模式。code first即代碼先行,先寫代碼,再自動創(chuàng)建數(shù)據(jù)庫。code first開發(fā)模式能夠真正實現(xiàn)持久化無關,從而設計出更加純凈的領域模型,特別在采用TDD開發(fā)時,更加威猛。
從上面可以看出,edmx和原生DbContext是兩種不同的EF技術,而db first、model first、code first則是不同的開發(fā)模式,但這些術語非?;靵y,不同的人說同一個術語時可能指的是不同的東西。
經(jīng)常聽到有人說他用的是code first,但實際上他的開發(fā)方式是先創(chuàng)建數(shù)據(jù)庫,再生成代碼,這屬于db first,他用code first指代原生DbContext技術。
還有一些人害怕使用EF,因為他認為原生DbContext只能使用code first開發(fā)模式,而他想采用db first方式,但他又不喜歡edmx。
我用原生DbContext這個詞的意思是,單獨使用DbContext這個基類,因為edmx也使用的是DbContext,以示區(qū)別。
下面用一個圖來總結一下,如果說得不正確,請各位同學批評指正。

Code First還是Db First
從上面分析得知,edmx不適合DDD分層架構,所以我們在EF技術上采用原生的DbContext,這個沒有什么疑問了。那么開發(fā)模式是否一定要采用code first呢?
前面說了,code first可以獲得更純的領域模型,但你見過爐火純青的領域模型長什么樣嗎?對于DDD架構初學者,在很長時間都難領悟到它的精髓,所以不論你以code first還是db first,其結果沒有顯著不同。
其次,.net大部分項目都是中小項目,且不太復雜,CRUD機械工作占很大篇幅,使用code first手工敲代碼,效率十分低下,且工作量與表中的字段數(shù)量成正比。在配合TDD的情況下,才可以和生成的代碼質(zhì)量媲美,否則BUG依舊。
可以看到,雖然code first萬眾矚目,但卻只有DDD高手開發(fā)很復雜的業(yè)務才能真正發(fā)揮威力。很復雜的業(yè)務需求,可能邏輯非常復雜,僅簡單搜集數(shù)據(jù)字段,并不能很好的完成任務,這種場景基于DDD進行行為建模并配合TDD推進項目更有保障。
根據(jù)我的項目實際情況,我采用了db first,第一步用PD數(shù)據(jù)建模,第二步用PD生成建庫腳本創(chuàng)建數(shù)據(jù)庫,第三步采用CodeSmith生成代碼,第四步選擇性的復制代碼并建立領域模型。
代碼生成器介紹
很多同學一聽代碼生成器,就會問哪種生成器最好,還有些同學則干脆自己開發(fā),畢竟大家都是程序員,要開發(fā)個生成器軟件有何難。
技術人員總是對技術本身比較感興趣,容易忽略做一件事的真正目的。其實對于代碼生成器來說,真正重要的不是生成器軟件,而是你需要獲得的最終代碼,它是由你的模板決定的。
要創(chuàng)建出一套高質(zhì)量的模板,關鍵是不斷提純自己的代碼,把重復的代碼盡量提取到基類。
對于采用哪種生成器軟件,根據(jù)自己的熟悉程度和喜好進行選擇,我采用的是CodeSmith。
CodeSmith是一個收費的代碼生成器,不過大家都使用綠色環(huán)保版本。使用它的原因是功能比較強大,能夠與VS進行集成,編寫模板時具有代碼提示,類似ASPX語法,學習成本低,另外官方提供了一套EF DbContext模板,我們只需要簡單修改,就可以用于實際開發(fā)中。
需要創(chuàng)建哪些部分的代碼呢?在最理想的情況下,所有機械代碼全部生成,這樣你可以在最短時間內(nèi)拿下大部分機械工作,為你能夠集中火力完成核心功能奠定基礎。
下面討論幾個與生成器相關的問題。
代碼生成器與數(shù)據(jù)建模的關系
一般的代碼生成器都是通過讀取數(shù)據(jù)庫元數(shù)據(jù)來生成代碼。
如何評價生成的代碼質(zhì)量高低?
第一個特征,所有代碼是否具有準確的注釋。
大部分程序員都不喜歡寫注釋,不知道是因為打字慢,還是覺得沒必要。哪怕你英文很牛X,你的命名非常標準,但你不能保證看你代碼的人具有同樣的英文水平。何況大部分人的英文還是和我一樣菜,命名十分晦澀。在這種情況下,不要說給別人留條活路,那是給自己將來留的。
如果你采用code first模式,手工編寫所有代碼,相信能給全套代碼寫全注釋的人不多,每個領域?qū)嶓w的屬性頭上都要加上注釋,而且還有大量相似類,比如Dto,不復制代碼很難做到。
第二個特征是自動幫你生成EF導航屬性及相關映射配置,這是通過讀取外鍵關系來創(chuàng)建的。
很多.Net程序員不知道數(shù)據(jù)建模的價值,如果你問我一個項目里,哪種文檔最重要,我會毫不猶豫的告訴你——數(shù)據(jù)建模文檔。
為什么數(shù)據(jù)建模這么重要,如果你現(xiàn)在接手一個遺留系統(tǒng),你最需要什么?需求文檔?類圖?序列圖?需求你不懂,還可以找用戶問,但數(shù)據(jù)庫中一個命名很晦澀的列,你要猜出它是什么意思,則難如登天。而類圖和序列圖等UML建模,主要是前期幫助理解和設計領域模型,不一定能夠與代碼同步更新,另外也不可能對每一個模塊創(chuàng)建UML,完全沒有必要。
通過PowerDesigner進行數(shù)據(jù)建模,你可以讓系統(tǒng)清晰度上升幾個層次,讓你看清表之間的關系,以及每個列的具體含義。
你可以在數(shù)據(jù)建模時,把每個列的注釋加上,用PD創(chuàng)建數(shù)據(jù)庫后,生成的代碼中就具有良好的注釋了。
當你在表之間用關聯(lián)線一拖,外鍵關系就建立了,生成的代碼就具有了導航屬性。
當然你可以直接在數(shù)據(jù)庫中創(chuàng)建表,并添加注釋,并手工創(chuàng)建外鍵關系。但這并沒有讓你減輕工作量,反而工作量更大,使用數(shù)據(jù)建模,工作輕松高效,且對項目未來維護有深遠影響。
生成高質(zhì)量代碼,除了你的模板外,另一個影響它的就是數(shù)據(jù)建模。
我將在后面幾篇分享我整理的CodeSmith模板,對于簡單的CRUD操作,它可以生成全套代碼,代碼質(zhì)量與我手工編寫無異。
是否批量生成代碼
對于從三層架構過來的朋友,很多都用過代碼生成器。
如果系統(tǒng)有100個表,他們會把這100個表先建好,然后一次生成出來,然后再花幾小時到幾天的時間來整理修改。
這個開發(fā)效率看上去很誘人,對于比較簡單的三層架構和SQL操作可能是有效的,但對于EF+DDD分層架構卻不太吃香。
對于EF的導航屬性,生成出來都是雙向?qū)Ш?/strong>,但為了降低復雜度,可能會手工調(diào)整為單向?qū)Ш?/strong>,這時候也需要手工修改映射代碼。
EF操作,我總是保持小步前進,前進太快,出現(xiàn)任何一個問題,都可能浪費更多時間。很多時候看EF異常提示很難定位到問題,甚至斷點調(diào)試也不起作用。這種情況下,最好的辦法就是小步走,一出問題就可以迅速解決。
用代碼生成器創(chuàng)建DDD分層架構,一個弊端是導致一個表對應一個聚合,每個表都有一個倉儲,這把你又帶回了三層架構時代。不過對于新手來說,這沒有多大問題,每個人都有一個成長的過程,第一步把充血模型用起來就行了,下一步再考慮聚合。
但對于達到一定經(jīng)驗的人,直接用生成的代碼就不合適了,因為聚合是DDD分層架構的核心,聚合使用得好,能顯著降低系統(tǒng)復雜性,并使業(yè)務邏輯更好的內(nèi)聚。
所以如果你具備一定經(jīng)驗以后,不應該完全采用代碼生成的老方式,更不能偷懶。應該選擇性的復制代碼,手工組織聚合結構,這樣一來,很多生成的代碼都不需要了,比如某個倉儲操作的是聚合內(nèi)部實體,系統(tǒng)復雜性會大幅降低。
我的方法是,按依賴順序手工復制需要的代碼,按聚合粒度復制并組織代碼,一次操作一個聚合,把界面運行通過后再復制下一個。
這樣可以讓你用db first模式開發(fā)出較高質(zhì)量的領域模型,當然質(zhì)量高低與水平成正比。
對于很簡單的CRUD模塊,大多都是單表結構,這種情況下,一個表本來就是一個聚合,代碼直接COPY,你的主要工作是調(diào)整下界面。
對于比較復雜的模塊,根據(jù)自己的理解手工復制代碼組織聚合,生成的代碼一般都達不到要求,比如界面布局比較復雜,這時候你會發(fā)現(xiàn),生成的代碼主要用于填充內(nèi)容,你自己完成布局等功能。
更復雜的模塊,可以先不生成代碼,用TDD推進并模擬出業(yè)務邏輯后,再進行數(shù)據(jù)建模生成代碼,并復制需要的文件。
是否把代碼生成器嵌入生產(chǎn)項目
對于強大一點的生成器,都能夠嵌入VS,并一鍵生成。這個特性也很誘人,如果把生成器嵌入生產(chǎn)項目,就不需要COPY文件了,這看起來能夠極大的提升開發(fā)效率。
與上一個問題一樣,當你把生成器嵌入生產(chǎn)項目,生成的所有文件都進入你的項目,不論你需不需要它,這導致每個表一個倉儲,增加了復雜性。
我的方法是,把代碼生成器與生產(chǎn)項目分離,手工復制相關文件,雖然看上去效率低,但可以根據(jù)需要選擇代碼和重新組織代碼,質(zhì)量將高得多。
數(shù)據(jù)庫增加一個字段也要生成一下嗎
采用代碼生成器的一個問題是,每當數(shù)據(jù)庫增加一個字段,代碼上相關的位置都要同步更新,很多懶漢希望通過重新生成并全面覆蓋來解決這個問題。
我的方法是僅在第一次生成全套代碼,后面通過手工添加相關屬性,如果增加的字段比較多,我可能先生成代碼,再手工將差異屬性復制過去。原因很簡單,項目上的代碼不是完全生成的,有修改過的地方,重新生成并完全替換,可能會覆蓋已修改代碼。
一鍵生成簡單代碼,還是配置生成智能代碼
很多人在生成器下了大量功夫,能夠支持復雜的配置,以生成出非常智能的代碼。
這可能造成對代碼生成器的高度依賴,我僅使用代碼生成器解決機械的簡單工作,對于更智能的手工完成。
我的方法是一鍵生成簡單代碼。
分層構造元素是否可以簡化
有人看見生成的代碼中,很多類都直接從基類派生,里面完全是空的,是否可以簡化掉。
這些類中啥也沒有的原因是,基礎操作已抽象到基類,由于沒有什么業(yè)務邏輯,所以是空的。
一般來說不能簡化,因為對于稍復雜的模塊,都需要往這些類中添加內(nèi)容,如果沒有它們你的代碼將變得混亂,這些構造很好的組織了代碼。
除非你能確定你的項目基本都是CRUD,這種情況下確實可以簡化,而且最好的辦法是采用單層架構,單層架構在高度抽象和采用代碼生成器的情況下,開發(fā)效率猶如火箭直沖宵漢。
也談加班
這兩天園子里討論加班的很多,我也說幾句。
不加班有幾個條件:
- 計劃合理
- 應用框架強大
- 開發(fā)人員平均水平較高
第一點最困難,哪怕你們開發(fā)人員水平再高,框架也很強大,如果老板要求你2個月完成8個月的工作,你不加班是不可能的。
如果計劃合理,框架很強大,你一天用半天時間來開發(fā),半天時間休息都綽綽有余。
結束語
本文分享了我在EF和代碼生成器上的一些看法,不見得正確,那只是我摸索的一些經(jīng)驗,你應該找出最合適你們團隊和項目的方法,并持續(xù)改進。
.Net應用程序框架交流QQ群: 386092459,歡迎有興趣的朋友加入討論。
.Net Easyui開發(fā)交流QQ群(本群僅限Easyui開發(fā)者,非Easyui開發(fā)者勿進):157809322
謝謝大家的持續(xù)關注,我的博客地址:http://www.rzrgm.cn/xiadao521/

浙公網(wǎng)安備 33010602011771號