關於orm框架設計,還有必要說的或許就是Model解析了,也是重要的一個環節,在實現上還是相對比較簡單的.
Model解析,主要用到的技術是反射了,即:把類的屬性與表的字段做映射. 把自己的設計及實現思路寫出來也希望能有人給很好的優化建議,同時也給新手一點啟發吧.
首先先給Model屬性定義特性,先普及一下"特性"的概念和為什麼用特性(Attribute).
簡單來說,特性是給一個類,或方法,或屬性 打上一個標記(或者叫附加信息),具體理解還是看例子比較好吧,
在做類與表之間映射時,我們需要知道某字段的是什麼類型,長度,是否主鍵,自增長,非空,等信息,最簡單較直觀的方法或許就是特性(Attribute)了,
首先我們定義一個特性,它就是一個類而已,它必須繼承自Attribute,我所寫的orm比較輕量級,僅幾個比較關鍵屬性,
代碼如下:
public class ModelAttribute : Attribute { /// <summary> /// 是否主鍵 /// </summary> public bool IsPrimaryKey { set; get; } /// <summary> /// 主鍵是否自動增長 /// </summary> public bool IsIdentity { set; get; } /// <summary> /// 是否非空字段 /// </summary> public bool IsNotNull { set; get; } /// <summary> /// 列名 /// </summary> public string ColumnName { set; get; } } View Code
下面是一個實體類使用特性的例子,它指明了Id的列名是:"Id",不允許為空的,是自增長的,是主鍵:
public class Test1 : ModelBase { [ModelAttribute(IsPrimaryKey = true, IsIdentity = true, IsNotNull = false, ColumnName = "Id")] public int Id { set; get; } public string Name { set; get; } public string Age { set; get; } public string Remark { set; get; } }
下面是通過反射把Model特性解析出來,先把核心代碼貼出來:
/// <summary> /// 通過解析獲得Model的對象的參數,Key:為類的屬性名 /// </summary> /// <param name="model">model對象</param> /// <returns>返回model參數</returns> protected override Dictionary<string, ModelAttribute> GetModelParam<TModel>() { var list = new Dictionary<string, ModelAttribute>(); PropertyInfo[] pros = ReflectionHelper.GetPropertyInfo<TModel>(); foreach (PropertyInfo item in pros) { var attr = ReflectionHelper.GetCustomAttribute<ModelAttribute>(item); if (attr == null) { //如果實體沒定義屬性則創建一個新的 attr = new ModelAttribute(); attr.ColumnName = item.Name; } else { //如果列名沒有賦值,則將列名定義和屬性名一樣的值 if (string.IsNullOrEmpty(attr.ColumnName)) { attr.ColumnName = item.Name; } } list.Add(item.Name, attr); } return list; }
因考慮反射應該是共同方法,不僅限於Model解析,所以把反射相關的方法提出來了,以下是根據"類型T"獲取自定義屬性的兩個方法:
/// <summary> /// 獲得指定成員的特性對象 /// </summary> /// <typeparam name="T">要獲取屬性的類型</typeparam> /// <param name="pInfo">屬性原型</param> /// <returns>返回T對象</returns> public static T GetCustomAttribute<T>(PropertyInfo pInfo) where T : Attribute, new() { Type attributeType = typeof(T); Attribute attrObj = Attribute.GetCustomAttribute(pInfo, attributeType); T rAttrObj = attrObj as T; return rAttrObj; } View Code /// <summary> /// 獲得對象的所有公共屬性信息 /// </summary> /// <typeparam name="T">類型</typeparam> /// <param name="obj">獲得的對象</param> /// <returns>返回屬性信息</returns> public static PropertyInfo[] GetPropertyInfo<T>() where T : class { Type t = typeof(T); PropertyInfo[] proInfo = t.GetProperties(); return proInfo; } View Code解析特性我們不需要知道該類的具體實例,所以這裡用了泛型,只需要知道Model類型即可,我的框架僅限於類的屬性,這裡只獲取屬性的"特性對象".
返回類型Dictionary<string,ModelAttribute> Key:為屬性名,ModelAttribute 對象,
到這裡解析的實現其實就完成,後面我又做了一些優化,我們想到反射時通常會聯想到效率問題,而且既然是解析一個類的特性,那麼我們並不關心它的實例對象,
這裡把解析出來的對象放到了緩存,即:只有第一次對該類進行反射,以後都是直接訪問緩存數據.
解析Model是一個類,那麼需要做到全局緩存,我這裡用到了一個靜態變量,該變量是不允許被外部更改的,所以設置為私有的了.
代碼如下:
static object _LockObj1 = new object(); static object _LockObj2 = new object(); /// <summary> /// 實體類緩存,靜態變量是保存為了減少反射次數 /// </summary> static Dictionary<Type, Dictionary<string, ModelAttribute>> _ModelAttributeCache; /// <summary> /// 實體類緩存,靜態變量是保存為了減少反射次數 /// </summary> protected Dictionary<Type, Dictionary<string, ModelAttribute>> ModelAttributeCache { get { if (_ModelAttributeCache == null) { lock (_LockObj1) { if (_ModelAttributeCache == null) { _ModelAttributeCache = new Dictionary<Type, Dictionary<string, ModelAttribute>>(); } } } return _ModelAttributeCache; } } /// <summary> /// 獲取Model的屬性對象,獲取第一次後會放入一個緩存列表中 /// 即只反射一次 /// </summary> public Dictionary<string, ModelAttribute> GetModelAttribute<T>() where T : ModelBase, new() { Type t = typeof(T); if (!ModelAttributeCache.ContainsKey(t)) { lock (_LockObj2) { if (!ModelAttributeCache.ContainsKey(t)) { var attrs = GetModelParam<T>(); ModelAttributeCache.Add(t, attrs); } } } return ModelAttributeCache[t]; }
這裡緩存列表為: Dictionary<Type, Dictionary<string, ModelAttribute>> ,Type即Model類的類型.
解釋一下加LockObj的意義,
我先聲明一下,這個orm框架雖然比較輕量級,但我也不是共享的一個設計階段或者或測試階段的代碼,也是經過幾個小項目使用磨合過的.
_LockObj 是在一次多線程操作時發現的bug,當多個線程訪問一個"全局對象"時,不加鎖會訪問沖突的問題.
Model解析類的路徑:ZhCun.Framework.Common.Models.TableModel
下載了代碼的可以去看下具體實現的詳細方法.
在設計DalBase 時考慮了它應依賴抽象的理念,雖然沒有想好關於Model解析除了反射還是否會有其它方法,但還是把它定義成了抽象.
到這已經完成了Model解析的功能.會再生成sql語句的時候用到它.
有了以下方法示例,估計sql文的生成就能實現了吧.
//得到Model對象(第一次會反射,再次調用時是從緩存獲取) Dictionary<string, ModelAttribute> modelAttr = _ModelAnaly.GetModelAttribute<T>(); //key:字段名(屬性名) foreach (string item in modelAttr.Keys) { //得到列名(如果特性沒有指定ColumnName值,則與屬性名一樣) string colName = modelAttr[item].ColumnName; //是否字增長 bool isIdentity = modelAttr[item].IsIdentity; //是否主鍵 bool isPrimaryKey = modelAttr[item].IsPrimaryKey; }
關於Model解析類的實現 相對設計來說比較簡單.
如果有大神有啥好的建議,或有什麼不足,希望能 探討,指正 .