ORM數據層框架的設計熱點:更新指定的列的幾種設計方案


ORM框架的定義:對象-關系映射(Object/Relation Mapping,簡稱ORM)

常見的是:數據庫結構=》映射Object(實體屬性)=》基於實體類的操作。

還有一種:數據庫結構=》映射Object(內存表結構)=》基於內存表的操作。

當然,如果你有創意,你還能創造出更多的映射載體來實現ORM。


避免思維定式: 

由於思維定式,很多開發者,只有見到基於實體類映射,才會認為是一種ORM框架,於是很少人去思考其它映射載體來實現ORM。

這個思維定式,和早期在ASP.NET MVC沒出來之前,把WebForm框架當成ASP.NET一樣,於是很少人會去創造另一種開發框架。

不過要避免思維定式,有時候的確不是件容易的事~~~這需要太多知識的沉淀和積累,這個就先不扯了,下面來正題。

 

ORM生成SQL:

一般的數據庫下,都是基於SQL語句解析執行的,所以ORM最終都避不開生成SQL再交還ADO.NET去執行,從而返回結果。

由於ORM存在映射關系,最簡單的是(字段名稱+數據類型)的實體映射,因此,通常只要遍歷實體的屬性就可以拿到所有字段名稱,從而組合SQL去執行了。

而反射的應用,對於實體型映射的ORM無疑是佳方案,節省大量代碼;通過反射,在處理時動態獲得指定對象的類型、字段名稱、字段類型、或者特性描述等信息,從而構造出SQL語句。

如果是基於內存表(MDataTable)映射的,則無需反射,因為映射的時候,相關結構已提前預約好了,直接遍歷獲取即可。


ORM的兩種實體型實現方式:充血和貧血

這里先不說基於內存表的映射,說說基於實體映射的設計方案:

充血模式的示例:

     public  class Users : CYQ.Data.Orm.OrmBase
    {
         public Users()
        {
             base.SetInit( this);
        }
         public  int ID {  getset; }
         public  string UserName {  getset; }
         public DateTime CreateTime {  getset; }
    }

這種方式,增刪改查,都由基類處理了,而基類一般需要三個參數:

1:子類的對象,用於反射類型及屬性的需要。
2:表名(可選,如果不寫,則從反射中拿到類名當表名)
3:數據庫鏈接(可選,如果不寫,則會默認約定一個配置名稱的數據庫鏈接)

最終的操作方式類似:

             using (Users u =  new Users())
            {
                u.UserName =  " u1 ";
                u.Update(1);
            }

如果按常規,我們可能會循環所有字段,全部更新;很明顯在這里我們只需要更新UserName。

於是,在設計上,我們需要額外多出一個集合,來存儲字段對應的狀態,這個集合怎么設計,這個大伙自行發揮了。

這里的難點,在於,如何設計獲取狀態改變上。

充血模式的兩種更新指定列的設計方案: 

1: 把動作交給實體,這種方案比較常規,動動腦就想的出來:

直接切入實體的Set屬性,如:

         public  string UserName
        {
             get;
             set
            {
                 base.SetState(value);
            }
        }

然后事情就交給基類的SetState方法去修正對應實體的狀態,遍歷屬性的時候,再比較狀態,取得只需要更新的字段去組合即可。

 

2:把動作交給基類:利用AOP動態攔截屬性,這種方案實現相對復雜

這種方案,優點是:可以保持實體類的相對簡潔,通過在基類利用AOP攔截子類的Set方法,從而動態的調用SetState方法。

缺點是:實現有點難度,另外是由於AOP基類ContextBoundObject的限制, 內部無法使用泛型。

即你不能實現:Select<T>()類似的方法,所以最終的表達式可能需要借第三個類的ToList()方法來返回,代碼類似:

using(Users u =  new Users())

     List<Users> list= u.Select().ToList();

具體的方案實現可以見我以前寫的這兩篇文章:

 

C# Aop簡單掃盲及ORM實體類屬性攔截示例

 

Aop RealProxy 千年遇BUG

 

 

貧血模式的示例:

     public  class Users 
    {
         public  int ID {  getset; }
         public  string UserName {  getset; }
         public DateTime CreateTime {  getset; }
    }

這種方式,實體就類似數據載體,本身不具備增刪改查功能,類似把基類獨立開來操作。

最終的調用方式可能類似:

Users user= DBFast.Find<Users>( 1); // 查詢記錄。

Users u= new Users();
u.UserName= " u1 ";
DBFast.Update<Users>(u, 1 ); // 更新。 


貧血模式的兩種更新指定列的設計方案:

對於貧血模式,也有兩種對應的設計方案:

1:實體還是原生態的:

     public  class Users 
    {
         public  int ID {  getset; }
         public  string UserName {  getset; }
         public DateTime CreateTime {  getset; }
    }

由於沒有基類,所以狀態的變化,無法很好的集成的,因此,這種情況的設計,通常需要多一行額外的代碼來傳遞信息。

例如:

Users u= new Users();
u.UserName= " u1 ";
DBFast.SetState<Users>( " UserName ",State .ForUpdate);//類似的多了一行來指定需要更新的一個或多個列的狀態。
DBFast.Update<Users>(u, 1); // 更新。


2:可以Nullable的實體:

     public  class Users 
    {
         public  int? ID {  getset; }
         public  string UserName {  getset; }
         public DateTime? CreateTime {  getset; }
    }

對於這種模式,可以讓值類型的默認值也為Null,因此可以通過減免值為Null的列,來實現更新值不為Null的值。

不過對於這種方式,由於DBNull.Value只能給引用類型賦值,因此值類型的字段無法重置為Null。

所以,如果要實現對值類型賦Null值,可能需要增加一行代碼來對指定的行指定狀態,配合着使用;或者直接操作SQL語句。 


總結回憶: 

記得前些日子和騰訊的架構師面試的時候,好像也交流到了這個指定列的更新的話題上,不過那時候的話題,上升到分布式話題了:

問題:大體是有一份數據載體情況下,用戶更新了某些字段,如何只更新某些字段的話題:

1:先是說前端的過濾與傳遞只需要更新的數據(對方:這個先假設沒有做)。

2:服務端可以做緩存,然后比較(對方:假設服務器有很多,做了負載,如何保證)。

3:全局共享緩存或用分布式緩存(對方:假設沒有分布式緩存,只有Web服務器緩存,只有一份副本)

4:通過某種算法,讓用戶的請求的數據對應到相對的副本的服務器。(對方:算法怎么實現)

5:。。。此處省略600字了。。。

 


不知不覺又寫了好幾個小時了,今天就介紹到這里了~~~謝謝大伙~~~~

最后給貼一下今天各大群熱傳的勵志文:月領1萬2小IT職員五年在北京買500萬房子~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM