首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 發現之旅第十一講:使用反射與特性構建 ORM 框架(上)

     編輯:關於C#

ORM 背景:關聯式資料庫與物件導向的鴻溝

關聯式資料庫以成熟的二維表格模型存儲結構化資料;物件導向程式設計則以封裝、繼承、多態處理複雜領域模型。兩者範式差異導致資料在邊界交換時極不順暢,傳統手工映射如「獨木橋」般低效且難維護。ORM(Object-Relational Mapping)框架如「高速公路大橋」般建立自動映射機制,大幅提升開發效率。雖純物件資料庫可徹底解決阻抗失配,但遷移成本極高,現階段 ORM 仍是務實選擇。

技術基石:反射與特性

反射:.NET 的程式基因分析術

反射是 CallByName 的 .NET 強化版。所有 .NET 型別繼承自 object,透過 GetType() 取得 System.Type 即為反射入口。可列舉型別所有成員(含私有)、取得欄位型別、方法簽章、事件委派等完整中繼資料,並支援動態建立實例、調用方法、讀寫屬性。

特性:宣告式中繼資料標記

特性繼承自 System.Attribute,以 [AttributeName] 語法附加於類別、屬性、方法等程式單元,不影響執行邏輯,但可被反射讀取。常見用法如 [STAThread] 標記進入點。自訂特性可定義建構函數與屬性,支援 AttributeUsage 限制適用目標與重複性。

輕量級 ORM 框架設計藍圖

僅處理常見簡單映射:一類別對一資料表,一屬性對一欄位。定義兩大核心特性:

  • BindTableAttribute:標記類別對應資料表名(Name 屬性,預設為類別名)。
  • BindFieldAttribute:標記屬性對應欄位名(Name、Key 關鍵欄位、ReadFormat/WriteFormat 格式化)。

映射資訊直接保存在代碼中(特性),優點是集中維護、重構友善;缺點是映射變更需重新編譯部署。XML 配置檔雖分離關注點但引入三方同步複雜度,本框架為示範反射與特性故採代碼內建方式。

框架核心功能清單

  1. 查詢:依指定 SQL 與型別查詢,映射欄位至屬性回傳物件陣列。
  2. 新增:反射取得表名與欄位綁定,組裝 INSERT 參數化 SQL 執行。
  3. 修改:組裝 UPDATE ... SET ... WHERE 關鍵欄位=值,需至少一 Key=true 屬性。
  4. 刪除:組裝 DELETE FROM ... WHERE 關鍵欄位=值 執行。

核心類別實作詳解

特性定義:BindTableAttribute 與 BindFieldAttribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class BindTableAttribute : Attribute {
    public BindTableAttribute() {}
    public BindTableAttribute(string name) { strName = name; }
    private string strName = null;
    public string Name { get { return strName; } }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class BindFieldAttribute : Attribute {
    public BindFieldAttribute() {}
    public BindFieldAttribute(string name) { strName = name; }
    private string strName = null;
    public string Name { get { return strName; } }
    private bool bolKey = false;
    public bool Key { get { return bolKey; } set { bolKey = value; } }
    private string strReadFormat = null;
    public string ReadFormat { get { return strReadFormat; } set { strReadFormat = value; } }
    private string strWriteFormat = null;
    public string WriteFormat { get { return strWriteFormat; } set { strWriteFormat = value; } }
}

內部綁定資訊模型:TableBindInfo / FieldBindInfo

特性適合標記,不適合高頻查詢,故定義輕量 POCO 物件緩存解析結果:

  • TableBindInfo:含 TableName、ObjectType、Attribute、FieldBindInfo[] Fields、FieldNameList(逗號分隔欄位字串)。
  • FieldBindInfo:含 FieldName、FieldIndex(DataReader 索引)、PropertyInfo、ValueType、DefaultValue、Attribute,並封裝 ToDataBase()/FromDataBase() 雙向轉換邏輯(支援格式化、DBNull、型別轉換)。

綁定資訊解析與緩存:GetBindInfo

private static Hashtable myBufferedInfos = new Hashtable();
private TableBindInfo GetBindInfo(Type ObjectType) {
    if (ObjectType == null) throw new ArgumentNullException("ObjectType");
    TableBindInfo info = (TableBindInfo)myBufferedInfos[ObjectType];
    if (info != null) return info; // 快取命中

    BindTableAttribute ta = (BindTableAttribute)Attribute.GetCustomAttribute(ObjectType, typeof(BindTableAttribute));
    if (ta == null) return null; // 未標記表特性

    TableBindInfo NewInfo = new TableBindInfo();
    NewInfo.ObjectType = ObjectType; NewInfo.Attribute = ta; NewInfo.TableName = ta.Name;
    if (string.IsNullOrEmpty(NewInfo.TableName)) NewInfo.TableName = ObjectType.Name;

    StringBuilder myFieldList = new StringBuilder();
    ArrayList fields = new ArrayList();
    foreach (PropertyInfo p in ObjectType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) {
        BindFieldAttribute fa = (BindFieldAttribute)Attribute.GetCustomAttribute(p, typeof(BindFieldAttribute));
        if (fa != null) {
            FieldBindInfo f = new FieldBindInfo();
            f.Attribute = fa; f.FieldName = fa.Name;
            if (string.IsNullOrEmpty(f.FieldName)) f.FieldName = p.Name;
            if (myFieldList.Length > 0) myFieldList.Append(",");
            myFieldList.Append(FixFieldName(f.FieldName));
            f.Property = p; f.ValueType = p.PropertyType; f.DefaultValue = GetDefaultValue(p);
            fields.Add(f);
        }
    }
    if (fields.Count == 0) throw new ArgumentException("無綁定欄位");
    NewInfo.Fields = (FieldBindInfo[])fields.ToArray(typeof(FieldBindInfo));
    NewInfo.FieldNameList = myFieldList.ToString();
    myBufferedInfos[ObjectType] = NewInfo; // 寫入快取
    return NewInfo;
}

採 Hashtable 緩存型別綁定資訊,避免重複反射解析,顯著提升框架效能。

資料查詢核心:ReadObjects 與 InnerReadValues

public object[] ReadObjects(string strSQL, Type ObjectType, int MaxObjectCount) {
    // 參數檢查、連線檢查...
    using (IDbCommand cmd = myConnection.CreateCommand()) {
        cmd.CommandText = strSQL;
        IDataReader reader = cmd.ExecuteReader(MaxObjectCount == 1 ? CommandBehavior.SingleRow : CommandBehavior.SingleResult);
        ArrayList list = new ArrayList();
        TableBindInfo table = GetBindInfo(ObjectType);
        lock (table) {
            // 預計算 FieldIndex:比對 DataReader 欄位名與綁定欄位名(不分大小寫)
            foreach (FieldBindInfo f in table.Fields) f.FieldIndex = -1;
            for (int i=0; i 0) list.Add(obj);
                if (MaxObjectCount > 0 && list.Count >= MaxObjectCount) break;
            }
        }
        reader.Close();
        return list.ToArray();
    }
}

private int InnerReadValues(object ObjInstance, TableBindInfo info, IDataReader reader) {
    int FieldCount = 0;
    foreach (FieldBindInfo field in info.Fields) {
        if (!field.Property.CanWrite) continue;
        if (field.FieldIndex < 0) continue;
        object v = reader.GetValue(field.FieldIndex);
        v = field.FromDataBase(v); // 處理 DBNull、格式化、型別轉換
        field.Property.SetValue(ObjInstance, v, null);
        FieldCount++;
    }
    return FieldCount;
}

關鍵優化:預先計算 FieldIndex 將「名稱比對」轉為「索引直接存取」,並以 Activator.CreateInstance 動態建立實體,PropertyInfo.SetValue 動態賦值,完整實現資料列到物件的自動映射。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved