1. 程式人生 > >應用程式框架實戰三十六:CRUD實戰演練介紹

應用程式框架實戰三十六:CRUD實戰演練介紹

  從本篇開始,本系列將進入實戰演練階段。

  前面主要介紹了一些應用程式框架的概念和基類,本來想把所有概念介紹完,再把框架內部實現都講完了,再進入實戰,這樣可以讓初學者基礎牢靠。不過我的精力很有限,文章進度越來越慢,所以準備切換一下介紹順序,把實戰演練提前,以方便你閱讀程式碼。

實戰演練介紹

  本系列實戰演練共分兩個部分。

  實戰演練第一部分介紹如何快速解決CRUD機械操作,這一部分我將手把手帶領各位同學從搭建VS環境開始,建立程式集及各程式集間的依賴關係,以及引入依賴的外部DLL,並手工完成程式碼示例中Application的三種介面操作。當你熟悉了手工操作方式後,你會發現這些工作枯燥乏味,效率低下,且容易出錯。為解決該問題,我將為你介紹PowerDesigner

(PD)和CodeSmith兩大工具,分享PD及資料建模技巧,併發布配套的CodeSmith模板,你將體驗到高質量完成機械程式碼的最佳實踐。

  實戰演練第一部分介紹的內容很有用,它將幫助你完成大量體力活,但這個示例太簡單,體現不出領域模型的威力,實戰演練第二部分以許可權模組為例,演示如何開發具有一定業務邏輯的模組。

  當然,在介紹實戰演練第二部分之前,我需要先把框架內部重要程式碼講解完,本系列大致構成如下:

  1. 應用程式框架介紹
  2. 實戰演練1:CRUD開發
  3. 框架程式碼詳解
  4. 實戰演練2:許可權開發

示例程式碼閱讀建議

  很多同學反映,閱讀我的示例程式碼非常困難,經過了解,我發現大多是閱讀方法有問題,在此給出一些建議。

  對於別人的程式碼,你閱讀起來很痛苦是正常現象,因為程式設計習慣和風格不同,另外一個原因是對程式碼的意圖不瞭解。

  我提供了Managements和Util兩個VS解決方案,Managements是簡單管理系統的程式碼示例,Util是框架程式碼,它是基礎設施層的一部分,被分離出來的原因是讓業務專案更簡單、編譯更快。

  閱讀程式碼需要從簡單的東西入手,這裡就是Managements解決方案,它裡面包含了一個叫Application的CRUD操作,它是許可權系統中的應用程式模組,具體功能先不要考慮。Application模組是一個單表操作,非常簡單,幾乎沒有業務邏輯,很適合用來入門。EasyUi提供了三種資料錄入方式,即表單操作、表格操作、行內操作,我使用Application模組演示了這幾種操作方式。

  有些同學下載程式碼後,直接看的Util程式碼,它主要包含一些基類和公共操作類,看起來困難就在所難免了。

  為了減少程式集數量,我把一些第三方開源框架的程式碼直接放入Util解決方案中,有些同學直接就看到這裡面去了,然後告訴我異常複雜。不要洩氣,說明你是一個正常人,對於第三方開源框架的程式碼,我也害怕,嘿嘿。我的程式碼風格很容易辨認,就是每個方法都具有中文註釋,當你發現程式碼沒有註釋或註釋全是英文,那一定不是我寫的,我的英文水平很菜。

  當你把Application看熟以後,可以自己動手建立與Application類似的表,並手工完成三種CRUD操作。

  當你對CRUD相關的類和配置熟悉之後,就可以檢視基類實現,這時候帶著問題去看Util程式碼,會容易理解得多。

  下一步就是把這些程式碼逐步移植到你自己的專案中,只有把它們變成你的東西,才能發揮更大作用。這也是我寫這個系列的目的,不僅授之以魚,更要授之以漁。

  建議你至少能夠擴充套件之後,才把我的東西用到你的專案上,不然坑很多,風險高。

CRUD概述

  CRUD是Create、Retrieve(Read)、Update、Delete的縮寫,中文名:增刪改查。不論哪家的應用框架,都特別關照它幾兄弟,為什麼?

  對於一般的中小型專案,業務邏輯複雜的模組只佔很小的比例,一半以上的模組都比較簡單。這些簡單模組大致會通過一個介面或裝置介面把資料收集上來,基本不經過中間處理(業務邏輯),直接存入資料庫,在有需要的時候會把這些資料展示出來,或者為複雜模組提供基礎支援。

  這些簡單模組工作量大,技術含量低,通過手工的方式編寫效率低下。

  當採用了分層架構,特別是DDD分層架構之後,更是雪上加霜。

  對於採用了DDD這種複雜分層架構,哪怕業務邏輯很複雜,還是存在不少機械工作,主要是建立各層的構造型別,比如領域實體及屬性、DTO及屬性對映、EF對映等,這些工作是必須的,但很枯燥乏味。

  可以看到,只要是資訊系統,不論簡單還是複雜,都存在機械工作,不同性質的專案機械工作所佔比例不一樣而已。

  後面我將用CRUD來指代開發中碰到的一切機械工作。

  對於機械工作,最好的辦法是依靠生成器自動建立程式碼,在討論生成器之前,先討論下EF相關的概念。

混亂的EF概念

  EF在剛出土的時候,提供一個叫實體資料模型的edmx檔案,開啟這個檔案,發現它是一個視覺化類圖設計器。

  在新建edmx檔案時,有“從資料庫生成”和“空模板”兩個選項。

  如果你選擇“從資料庫生成”,說明你自己先建立了資料庫,再通過edmx的反向生成工具生成程式碼,這就是所謂的db first,first是先行或優先的意思,db first就是資料庫先行,先建立資料庫,再讀取資料庫的元資料,生成程式碼。

  如果你選擇了“空模板”,你可以在edmx視覺化類圖設計器中建立一些類和關聯,edmx會自動幫你生成程式碼和資料庫。這種方式稱為model first,即模型先行,先有模型,後面再建立程式碼和資料庫。

  程式碼生成出來後,你會發現這些程式碼檔案被包含在edmx檔案中,包括領域實體和DbContext工作單元,還有一些T4模板,這有什麼影響?

  如果採用DDD分層架構,領域實體屬於領域層,而DbContext屬於基礎設施層,放到一起會導致高耦合以及分層不清,這是edmx的主要問題。另外一堆不相干的程式碼生成模板與領域實體放到一起,估計也讓你看著心煩。

  EF後續推出了更加輕量的使用方式,讓你可以拋棄edmx檔案,直接使用原生的DbContext,並支援了code first開發模式。code first即程式碼先行,先寫程式碼,再自動建立資料庫。code first開發模式能夠真正實現持久化無關,從而設計出更加純淨的領域模型,特別在採用TDD開發時,更加威猛。

  從上面可以看出,edmx和原生DbContext是兩種不同的EF技術,而db first、model first、code first則是不同的開發模式,但這些術語非常混亂,不同的人說同一個術語時可能指的是不同的東西。

  經常聽到有人說他用的是code first,但實際上他的開發方式是先建立資料庫,再生成程式碼,這屬於db first,他用code first指代原生DbContext技術。

  還有一些人害怕使用EF,因為他認為原生DbContext只能使用code first開發模式,而他想採用db first方式,但他又不喜歡edmx。

  我用原生DbContext這個詞的意思是,單獨使用DbContext這個基類,因為edmx也使用的是DbContext,以示區別。

  下面用一個圖來總結一下,如果說得不正確,請各位同學批評指正。

Code First還是Db First

  從上面分析得知,edmx不適合DDD分層架構,所以我們在EF技術上採用原生的DbContext,這個沒有什麼疑問了。那麼開發模式是否一定要採用code first呢?

  前面說了,code first可以獲得更純的領域模型,但你見過爐火純青的領域模型長什麼樣嗎?對於DDD架構初學者,在很長時間都難領悟到它的精髓,所以不論你以code first還是db first,其結果沒有顯著不同。

  其次,.net大部分專案都是中小專案,且不太複雜,CRUD機械工作佔很大篇幅,使用code first手工敲程式碼,效率十分低下,且工作量與表中的欄位數量成正比。在配合TDD的情況下,才可以和生成的程式碼質量媲美,否則BUG依舊。

  可以看到,雖然code first萬眾矚目,但卻只有DDD高手開發很複雜的業務才能真正發揮威力。很複雜的業務需求,可能邏輯非常複雜,僅簡單蒐集資料欄位,並不能很好的完成任務,這種場景基於DDD進行行為建模並配合TDD推進專案更有保障。

  根據我的專案實際情況,我採用了db first第一步用PD資料建模,第二步用PD生成建庫指令碼建立資料庫,第三步採用CodeSmith生成程式碼,第四步選擇性的複製程式碼並建立領域模型。

程式碼生成器介紹

  很多同學一聽程式碼生成器,就會問哪種生成器最好,還有些同學則乾脆自己開發,畢竟大家都是程式設計師,要開發個生成器軟體有何難。

  技術人員總是對技術本身比較感興趣,容易忽略做一件事的真正目的。其實對於程式碼生成器來說,真正重要的不是生成器軟體,而是你需要獲得的最終程式碼,它是由你的模板決定的。

  要創建出一套高質量的模板,關鍵是不斷提純自己的程式碼,把重複的程式碼儘量提取到基類。

  對於採用哪種生成器軟體,根據自己的熟悉程度和喜好進行選擇,我採用的是CodeSmith

  CodeSmith是一個收費的程式碼生成器,不過大家都使用綠色環保版本。使用它的原因是功能比較強大,能夠與VS進行整合,編寫模板時具有程式碼提示,類似ASPX語法,學習成本低,另外官方提供了一套EF DbContext模板,我們只需要簡單修改,就可以用於實際開發中。

  需要建立哪些部分的程式碼呢?在最理想的情況下,所有機械程式碼全部生成,這樣你可以在最短時間內拿下大部分機械工作,為你能夠集中火力完成核心功能奠定基礎。

  下面討論幾個與生成器相關的問題。

程式碼生成器與資料建模的關係

  一般的程式碼生成器都是通過讀取資料庫元資料來生成程式碼。

  如何評價生成的程式碼質量高低?

  第一個特徵,所有程式碼是否具有準確的註釋。

  大部分程式設計師都不喜歡寫註釋,不知道是因為打字慢,還是覺得沒必要。哪怕你英文很牛X,你的命名非常標準,但你不能保證看你程式碼的人具有同樣的英文水平。何況大部分人的英文還是和我一樣菜,命名十分晦澀。在這種情況下,不要說給別人留條活路,那是給自己將來留的。

  如果你採用code first模式,手工編寫所有程式碼,相信能給全套程式碼寫全註釋的人不多,每個領域實體的屬性頭上都要加上註釋,而且還有大量相似類,比如Dto,不復制程式碼很難做到。

  第二個特徵是自動幫你生成EF導航屬性及相關對映配置,這是通過讀取外來鍵關係來建立的。

  很多.Net程式設計師不知道資料建模的價值,如果你問我一個專案裡,哪種文件最重要,我會毫不猶豫的告訴你——資料建模文件。

  為什麼資料建模這麼重要,如果你現在接手一個遺留系統,你最需要什麼?需求文件?類圖?序列圖?需求你不懂,還可以找使用者問,但資料庫中一個命名很晦澀的列,你要猜出它是什麼意思,則難如登天。而類圖和序列圖等UML建模,主要是前期幫助理解和設計領域模型,不一定能夠與程式碼同步更新,另外也不可能對每一個模組建立UML,完全沒有必要。

  通過PowerDesigner進行資料建模,你可以讓系統清晰度上升幾個層次,讓你看清表之間的關係,以及每個列的具體含義

  你可以在資料建模時,把每個列的註釋加上,用PD建立資料庫後,生成的程式碼中就具有良好的註釋了。

  當你在表之間用關聯線一拖,外來鍵關係就建立了,生成的程式碼就具有了導航屬性

  當然你可以直接在資料庫中建立表,並添加註釋,並手工建立外來鍵關係。但這並沒有讓你減輕工作量,反而工作量更大,使用資料建模,工作輕鬆高效,且對專案未來維護有深遠影響。

  生成高質量程式碼,除了你的模板外,另一個影響它的就是資料建模。

  我將在後面幾篇分享我整理的CodeSmith模板,對於簡單的CRUD操作,它可以生成全套程式碼,程式碼質量與我手工編寫無異。

是否批量生成程式碼

  對於從三層架構過來的朋友,很多都用過程式碼生成器。

  如果系統有100個表,他們會把這100個表先建好,然後一次生成出來,然後再花幾小時到幾天的時間來整理修改。

  這個開發效率看上去很誘人,對於比較簡單的三層架構和SQL操作可能是有效的,但對於EF+DDD分層架構卻不太吃香。

  對於EF的導航屬性,生成出來都是雙向導航,但為了降低複雜度,可能會手工調整為單向導航,這時候也需要手工修改對映程式碼。

  EF操作,我總是保持小步前進,前進太快,出現任何一個問題,都可能浪費更多時間。很多時候看EF異常提示很難定位到問題,甚至斷點除錯也不起作用。這種情況下,最好的辦法就是小步走,一出問題就可以迅速解決。

  用程式碼生成器建立DDD分層架構,一個弊端是導致一個表對應一個聚合,每個表都有一個倉儲,這把你又帶回了三層架構時代。不過對於新手來說,這沒有多大問題,每個人都有一個成長的過程,第一步把充血模型用起來就行了,下一步再考慮聚合。

  但對於達到一定經驗的人,直接用生成的程式碼就不合適了,因為聚合是DDD分層架構的核心,聚合使用得好,能顯著降低系統複雜性,並使業務邏輯更好的內聚。

  所以如果你具備一定經驗以後,不應該完全採用程式碼生成的老方式,更不能偷懶。應該選擇性的複製程式碼手工組織聚合結構,這樣一來,很多生成的程式碼都不需要了,比如某個倉儲操作的是聚合內部實體,系統複雜性會大幅降低。

  我的方法是,按依賴順序手工複製需要的程式碼,按聚合粒度複製並組織程式碼,一次操作一個聚合,把介面執行通過後再複製下一個。

  這樣可以讓你用db first模式開發出較高質量的領域模型,當然質量高低與水平成正比。

  對於很簡單的CRUD模組,大多都是單表結構,這種情況下,一個表本來就是一個聚合,程式碼直接COPY,你的主要工作是調整下介面。

  對於比較複雜的模組,根據自己的理解手工複製程式碼組織聚合,生成的程式碼一般都達不到要求,比如介面佈局比較複雜,這時候你會發現,生成的程式碼主要用於填充內容,你自己完成佈局等功能。

  更復雜的模組,可以先不生成程式碼,用TDD推進並模擬出業務邏輯後,再進行資料建模生成程式碼,並複製需要的檔案。

是否把程式碼生成器嵌入生產專案

  對於強大一點的生成器,都能夠嵌入VS,並一鍵生成。這個特性也很誘人,如果把生成器嵌入生產專案,就不需要COPY檔案了,這看起來能夠極大的提升開發效率。

  與上一個問題一樣,當你把生成器嵌入生產專案,生成的所有檔案都進入你的專案,不論你需不需要它,這導致每個表一個倉儲,增加了複雜性。

  我的方法是,把程式碼生成器與生產專案分離,手工複製相關檔案,雖然看上去效率低,但可以根據需要選擇程式碼和重新組織程式碼,質量將高得多。

資料庫增加一個欄位也要生成一下嗎

  採用程式碼生成器的一個問題是,每當資料庫增加一個欄位,程式碼上相關的位置都要同步更新,很多懶漢希望通過重新生成並全面覆蓋來解決這個問題。

  我的方法是僅在第一次生成全套程式碼,後面通過手工新增相關屬性,如果增加的欄位比較多,我可能先生成程式碼再手工將差異屬性複製過去。原因很簡單,專案上的程式碼不是完全生成的,有修改過的地方,重新生成並完全替換,可能會覆蓋已修改程式碼。

一鍵生成簡單程式碼,還是配置生成智慧程式碼

  很多人在生成器下了大量功夫,能夠支援複雜的配置,以生成出非常智慧的程式碼。

  這可能造成對程式碼生成器的高度依賴,我僅使用程式碼生成器解決機械的簡單工作,對於更智慧的手工完成

  我的方法是一鍵生成簡單程式碼。

分層構造元素是否可以簡化

  有人看見生成的程式碼中,很多類都直接從基類派生,裡面完全是空的,是否可以簡化掉。

  這些類中啥也沒有的原因是,基礎操作已抽象到基類,由於沒有什麼業務邏輯,所以是空的。

  一般來說不能簡化,因為對於稍複雜的模組,都需要往這些類中新增內容,如果沒有它們你的程式碼將變得混亂,這些構造很好的組織了程式碼。

  除非你能確定你的專案基本都是CRUD,這種情況下確實可以簡化,而且最好的辦法是採用單層架構,單層架構在高度抽象和採用程式碼生成器的情況下,開發效率猶如火箭直衝宵漢。

也談加班

  這兩天園子裡討論加班的很多,我也說幾句。

  不加班有幾個條件:

  1. 計劃合理
  2. 應用框架強大
  3. 開發人員平均水平較高

  第一點最困難,哪怕你們開發人員水平再高,框架也很強大,如果老闆要求你2個月完成8個月的工作,你不加班是不可能的。

  如果計劃合理,框架很強大,你一天用半天時間來開發,半天時間休息都綽綽有餘。

結束語

本文分享了我在EF和程式碼生成器上的一些看法,不見得正確,那只是我摸索的一些經驗,你應該找出最合適你們團隊和專案的方法,並持續改進。

.Net應用程式框架交流QQ群: 386092459,歡迎有興趣的朋友加入討論。

.Net Easyui開發交流QQ群(本群僅限Easyui開發者,非Easyui開發者勿進):157809322

謝謝大家的持續關注,我的部落格地址:http://www.cnblogs.com/xiadao521/