OSS.Core基於Dapper封裝(表達式解析+Emit)倉儲層的構思及實現

    最近趁着不忙,在構思一個搭建一個開源的完整項目,至於緣由以及整個項目框架後邊文章我再說明。既然要起一個完整的項目,那麼數據倉儲訪問就必不可少,這篇文章我主要介紹這個新項目(OSS.Core)中我對倉儲層的簡單思考和實現過程(當前項目還處在搭建階段),主要集中在如下幾個方面:git

1. 數據倉儲層的需求github

2. ORM框架選擇sql

3. OSS.Core倉儲層設計實現數據庫

4. 調用示例緩存

   下邊的實現部分中可能須要你對.NET的 泛型,委託,擴展,表達式等有一個基礎瞭解。正是由於這些語言特性,方便咱們對操做共性的抽取統一。app

一. 數據倉儲層需求框架

  既然是一個完整的項目,數據訪問是其最基本的部分,同時,數據訪問也是整個項目最容易出現瓶頸的地方。在個人劃分中,其承擔的角色是負責整個數據的輸入輸出,不只僅是針對單數據庫(有時甚至多庫),有時還須要完成一級緩存的實現,給邏輯層提供最基礎的數據支撐。函數

   業務永遠是在變化的,那麼項目也要具有快速演進的能力,因此我但願數據層可以保持相對的簡單,在結構上儘可能減小複雜的耦合查詢,在性能上儘可能減小沒必要要的消耗,例如反射的大量使用。同時針對每一個業務對象完成數據庫層面基本的CRUD統一封裝實現。若是有須要的時候還能在最少的改動下加入緩存的更新。(對於如何實現不一樣模塊不一樣緩存存儲策略,像Redis,Memcached會在後邊文章介紹)性能

  同時,對於一個稍微有點規模的項目來講,解決數據庫訪問的最快速作法就是實現讀寫分離,因此,我但願這個框架可以在一開始在底層就實現了讀寫分離的支持,以免後期再重頭對業務代碼的大量修改。  this

二. ORM 框架選擇

  固然,若是爲了簡單和性能,直接ADO.NET鏈接理論上來講是比較高效的作法,不過這樣會形成大量的重複操做邏輯代碼,同時也會形成代碼的散亂,增長維護複雜度。做爲技術人員,不只須要解決業務問題提升效率,同時也要提升本身的效率,因此我會選擇一個ORM框架來完成部分基礎工做。

  當前在.NET體系下,開源的ORM框架不少,如:Entityframework,NHibernate,iBATIS.NET,Dapper等等,各有特點,基於前面我說的,保證效率的同時,兼顧簡單還能最大程度減小性能的損耗,而且提供.net standard標準庫下的支持。這裏對比以後我選擇Dapper這個半自動化的ORM做爲倉儲層的基礎框架,選擇緣由以下:

  1. 其結構簡單,整個封裝主要集中Dapper.cs文件中,體積很小

      2. 封裝功能簡單強大,對原生SQL的支持上很靈活

    這點幾乎完勝其餘框架,無需任何多餘的設置,同時基本上你可調用全部原生ADO.NET的功能,sql語句徹底本身掌控,卻又無需關心command的參數賦值,以及結果實體轉換等。

  3. 性能上的高效

    不少ORM的實體映射經過反射來完成,這點上Dapper再次展示其魅力,在Commond參數賦值,以及實體轉換等關鍵模塊,使用了Reflection.Emit功能,間接實現了MSIL編譯層面的賦值實現,之因此說間接,是由於其自己代碼還須要編譯器生成IL代碼。在運行時根據類型屬性動態建立賦值委託方法。

 

三. OSS.Core倉儲層設計實現

   經過Dapper能夠實如今數據庫訪問部分一層簡單的封裝,不過我依然須要手動編寫很多的sql語句,同時還要進行參數化的處理,包括數據的讀寫分離等。那麼這些功能的實現我將在OSS.Core.RepDapper中完成,爲了方便理解,先貼出一個簡單的封裝後的方法調用傳輸流程:

  在這個圖裏展現一個簡單的方法調用流程,圍繞這張圖的幾個核心部分,我分別介紹下:

  1. 接口設計

  由於我但願這個是完整的示例項目,因此後邊但願可以兼容不一樣數據庫,所以對外的倉儲訪問都基於接口調用。固然若是你的項目根本沒有切換數據庫的需求,我更建議去掉這一環節,直接在基類中實現單例模式,業務邏輯層直接調用。

  圖中能夠看到接口層獨立於實現部分,我將具體業務實體模型和接口 單獨放在了OSS.Core.DomainMos 類庫中,一方面是爲了實體模型在各模塊中的共用,另外一方面解耦業務邏輯層(Services)和倉儲層(Reps)之間的依賴關係。

  同時一個項目中數據庫訪問代碼多數都會以CRUD爲主,因此這裏我定義了一個基礎接口(IBaseRep),其包含的方法主要有(表達式部分在後邊介紹):

  具體的業務數據接口繼承至基礎接口就好,其中表達式部分是我本身作了一個封裝,後邊會簡單介紹。

 

  2. 倉儲基類實現(BaseRep

  首先,如圖所示,咱們實現了讀寫分離的兩個擴展,其實最終都會通過Excute方法,那麼這裏展現下方法的具體實現:

  能夠看到在這個方法提供了一個針對IDbConnection的委託,提供調用層自由使用Dapper方法的同時,統一了數據訪問方法入口,便於日誌記錄,和排查。

 

  其次,在不少項目中會出現用戶和訂單在不一樣庫中的這類狀況,由於涉及到分庫的狀況,因此須要子類中能有修改鏈接串能力,那麼這裏我經過構造函數的形式,提供了兩個可空參數:

  能夠看到,若是子類中定義了本身的鏈接串,則以子類自定義爲主,不然走默認的鏈接信息。

  

  最後,咱們也實現了針對基礎接口方法的具體實現,舉一示例:

  同時,爲了保證子類中可以加入緩存處理,因此採用了虛方法(virtual)的形式,保證子類可以重寫。

 

  3. 基於Connection的擴展

  這個地方主要分爲兩個部分,a. 表達式的解析,以及參數化的處理   b. 擴展Connection的Insert,Update...等Dapper沒有擴展的方法:

  a. 熟悉Expression表達式的朋友應該比較瞭解,表達式自己是一個樹形接口,根據不一樣的類型,能夠不斷的解析其子表達式,直到不具有繼續解析的可能。因此這個就很簡單就是遞歸的不斷迭代,根據其不一樣的NodeType能夠組裝不一樣的sql元素,由於代碼較長,能夠參見github下的SqlExpressionVisitor.cs類,其中參數的賦值部分,沒有采用反射,而是使用的反射發射,代碼詳見SqlParameterEmit.cs

  b. 有了表達式的擴展以後,就能夠獲取對應的sql和參數,經過this擴展Connection方法便可,代碼見ConnoctionExtention.cs

  

四. 調用示例

  1. 咱們定義一個簡單UserInfoMo實體(包含mobile等屬性)

  2. 定義接口  IUserInfoRep: IBaseRep

  3. 定義實現類  UserInfoRep : BaseRep, IUserInfoRep

  在不添加其餘代碼的基礎上,咱們就能夠完成下面的調用:

 

當前項目還在搭建中,若是有興趣的同窗也歡迎參與進來,代碼詳見Github下OSS.Core

 

=============================

若是你還有其餘問題,歡迎關注公衆號(OSSCoder)

相關文章
相關標籤/搜索