背景是有一個遊戲服務器一直以來都是寫SQL的, 後來改過一段時間的redis, 用的是別的員工寫的類orm方式將實體類型映射成各類key-value對進行寫入, 可是仍有一個缺點就是須要在增\刪\改的時候顯式調用API, 更糟糕的是要註明刪\改的字段名, 否則就會整個實體重寫入. 實際使用中常常會出現寫錯字段名, 或是重命名字段以後忘記修改舊的字段名字符串參數, 甚至忘記調用update API致使數據沒有保存...(一樣的狀況也發生在寫SQL的時候)git
其實增/刪/改用到的SQL或是reids api都很是簡單, 均可以自動生成, 免去反覆書寫的麻煩也容易寫錯的問題. github
因而乎, 我計劃經過監視/攔截增/刪/改 方法以在數據修改的同時自動生成SQL/Redis調用, 來對改屬性值進行保存. 其實增刪比較簡單, 只要自定義一個集合類型繼承下IEnumerable<T>接口, 而後實現增刪方法便可, 對原代碼的修改量也是極小的. redis
可是改就有點麻煩了, 須要攔截屬性的set方法, 這是之前沒有怎麼用過的AOP領域.api
一些調查以後, 以爲有兩種方式比較適合.服務器
一是靜態織入, 就是用相似代碼模板的方式, 先寫實體類型模板, 而後生成真正的實體類型類庫, 在生成的時候進行修改在屬性的set方法中注入PropertyChangedNotify事件, 服務中使用的是生成後的實體類庫. PostSharp貌似不錯, 可是不想引入更多的開銷就罷了. 用T4或者上Emit或許不錯.ide
二是動態, 大體上就是生成一個代理類型, 擁有原類型的全部接口, 可是從新給實現了一遍. 能夠是組合的方式, 將原類型實例做爲私有成員保存, Emit生成全部原類型的開放方法; 也能夠是繼承的方式, 重載. 考慮到對原代碼的修改量, 我以爲繼承重載不錯. 函數
好比原實體類型以下:工具
public class Person { public string Id{get;set;} public string Name{get;set;} }
對這個類實現修改保存的託管, 那麼直接使用這個類型是不行的了(除非方案1靜態修改注入), 只有使用動態生成的代理實體類, 而若是使用組合方式的代理類型, 在OO的狀況下很彆扭, 因此我選擇用繼承重載的方式. 這就須要對這個實體類型進行改造, 將全部須要監視的屬性修改爲virutal聲明 ctrl+H搞定. 學習
而後, 由於是增刪改, 因此Key是必不可少的, 那麼實體須要用attribute標記出Key屬性, 支持多屬性組合Key. 由於原代碼用過一段時間的entityframework, 因此這個改造很是順利. this
最後的類型定義以下:
[DataContract] public class Person { [Key] public string Id{get;set;} public virtual string Name{get;set;} }
首先用attribute標記Person爲須要代理的類型, 便於在實際使用中糾錯和預加載, 而後標記Id屬性爲Key, 而且不須要重載, 其餘屬性聲明爲virtual表示須要重載進行監視.
而後手寫一個代理類型:
public class PersonProxy : Person { public override string Name { set { base.Name= value; Interceptor.Invoke(this, "Name", value); } } } public static class Interceptor { public static void Invoke(object instance, string name, object value) { //log } }
再定義個Factory的靜態類, 實現靜態泛型函數Create<T>()來建立Person的代理類型PersonProxy. 其實後面調用的都是代理類型, 只是使用方法和Person一致, 兼容舊代碼.
只是這樣的話, 還不如使用靜態織入了. 因此上面手寫的這個PersonProxy須要動態生成, 這樣經過泛型來支持全部自定義class, 減小頻繁修改. 因而Emit登場了.
不過我對IL和Emit認識淺薄, 個人辦法是使用VS自帶工具 -- ildasm, 用它打開剛纔生成的PersonProxy查看IL代碼, 反覆對比總結以後終於將這個PersonProxy類型的動態生成用Emit實現成功並修改爲泛型通用. 這套特殊的IL學習方法論真是屢試不爽. 方法大體如此拋磚引玉, 因此這裏也就不貼emit的代碼了. Invoke織入也能夠作各類進化, 以達成更高級的功能, 好比invoke在賦值前, 寫入失敗回退throw exception等等...
這樣ProxyFactory.Create<T>()函數就寫好了.
在Invoke裏生成須要SQL或是redis set方法就能夠了.
最後再實現增刪代理的集合類型, 在Add的時候直接將插入的對象經過ProxyFactory.Create<T>()轉成proxy子類對象, 爲了不Add以後繼續使用傳入的原對象而丟失對屬性set的監控, 修改Add的聲明爲Add(ref T obj)強行修改引用爲代理對象. 從入口處根絕, 完美.
理論Ok, 因而封裝成類庫.
最後放上代碼地址 : https://github.com/Roytin/SmartDb