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

C# 發現之旅第十二講:基於反射與動態編譯的高效能 ORM 框架(上)

     編輯:關於C#

動態編譯技術核心原理

所謂動態編譯技術,是指應用程式在執行期由程式內部自動產生 C# 程式碼,隨即呼叫 .NET Framework 內建的 C# 編譯器生成臨時組件,並將其載入應用程式域中,透過反射機制動態呼叫其中的物件模組。這項技術將傳統開發階段手動使用代碼產生器的「靜態編譯」流程完全自動化,並將代碼生成與編譯的時機延後至軟體執行期間。

動態編譯兼具彈性與效能:產生的臨時組件與開發工具編譯出的組件無異,執行速度同樣極快。.NET Framework 本身即大量採用此技術,例如 XML 序列化/反序列化、ASP.NET 處理 ASPX 頁面等皆是典型應用。

動態編譯標準流程

  1. 應用程式收集參數,呼叫動態編譯模組。
  2. 模組檢查全域臨時組件快取,若命中則直接回傳組件物件。
  3. 未命中時,呼叫內建代碼產生器依參數產出 C# 原始碼字串。
  4. 透過 Microsoft.CSharp.CSharpCodeProvider 編譯原始碼,產生臨時組件(過程中會在磁碟產生臨時檔案,受 .NET 安全性設定影響)。
  5. 將組件存入快取並回傳,應用程式再以反射方式呼叫其功能。

動態編譯流程圖

技術適用性與風險評估

動態編譯適合「功能複雜且多變、靜態代碼產生器難以涵蓋」的情境。若功能單純,靜態編譯即足夠;若功能過於複雜無法以代碼產生器表達,則亦不適用。須注意:編譯過程會產生磁碟臨時檔,受 .NET 安全性原則限制;且在原始碼生成與編譯之間存在時間窗,惡意程式理論上可竄改原始碼植入惡意邏輯,此為技術固有風險。

高效能 ORM 框架整體架構設計

本框架承接上一講的 ORM 基礎,導入動態編譯技術大幅提升執行效能。核心設計仍以 BindTableAttribute 與 BindFieldAttribute 標記實體類別與資料表、欄位的對應關係,範例實體 DB_Employees 完整保留。

架構核心組件

框架架構圖

  • 實體類型註冊列表:儲存實體類型與對應的 RecordORMHelper 物件。應用程式啟動時批次註冊所有實體類型,註冊動作不會立即觸發代碼生成與編譯。
  • 抽象基類 RecordORMHelper:定義實體與資料庫對應的核心介面,包含從 IDataReader 讀取資料建立實體、初始化新增/修改/刪除的 IDbCommand 等抽象方法。資料存取模組統一透過此介面操作。
  • 代碼產生器:掃描註冊列表中未處理的實體類型,解析特性中的對應資訊,為每個實體產生繼承自 RecordORMHelper 的具體類別代碼,單次編譯可產出多個衍生類別。
  • C# 編譯器:編譯產生的代碼,參考 mscorlib.dll、System.dll、System.Data.dll 及框架本身所在組件(甚至可為 EXE),產出包含多個 Helper 類別的臨時組件。
  • 控制模組:接收應用程式請求,檢查註冊列表,若缺少 Helper 則觸發動態編譯流程,載入組件、以反射建立 Helper 實例並填回快取,最後呼叫介面完成 ORM 操作。

效能優化策略

建議於框架初始化階段一次性註冊所有可能用到的實體類型,使框架僅執行一次動態編譯。若陸續註冊,框架將多次編譯產生多個臨時組件,增加啟動開銷。

核心抽象類別 RecordORMHelper 深度解析

此類別位於框架底層,定義標準化對應操作合約:

  • TableName:回傳實體對應的資料表名稱(由 BindTableAttribute 指定)。
  • RecordFieldNames:回傳所有綁定欄位名稱陣列(由 BindFieldAttribute 指定)。
  • ReadRecord/ReadRecords:封裝資料讀取流程,內部呼叫 GetFieldIndexs 取得欄位索引映射表,再呼叫 InnerReadRecord(需衍生類實作)逐筆填充實體。
  • FillInsertCommand/FillUpdateCommand/FillDeleteCommand:初始化對應 SQL 命令與參數,回傳參數數量。
  • GetFieldIndexs:核心映射邏輯,比對 IDataReader 欄位名稱與實體屬性綁定名稱(不區分大小寫、支援「資料表.欄位」格式),建立索引陣列,避免使用低效的 GetValue(string)。
  • 內建多型轉換輔助方法:ConvertToInt32、ConvertToString、ConvertToDateTime 等,統一處理 DBNull 與預設值。

實體類型註冊與 Helper 取得機制

框架主類別 MyFastORMFramework 以 Hashtable 實作註冊列表 myRecordHelpers,Key 為實體類型,Value 為 Helper 實例(未就緒時為 null)。

public void RegisterType(Type t) {
    if (!myRecordHelpers.ContainsKey(t)) {
        this.GetBindProperties(t); // 驗證實體合法性
        myRecordHelpers[t] = null;
    }
}

取得 Helper 時採雙重檢查模式:

private RecordORMHelper GetHelper(Type RecordType) {
    RecordORMHelper helper = null;
    if (myRecordHelpers.ContainsKey(RecordType)) {
        helper = (RecordORMHelper)myRecordHelpers[RecordType];
        if (helper != null) return helper;
    } else {
        this.GetBindProperties(RecordType);
        myRecordHelpers[RecordType] = null;
    }
    BuildHelpers(null); // 觸發動態編譯
    helper = (RecordORMHelper)myRecordHelpers[RecordType];
    if (helper == null) throw new ArgumentException("初始化失敗: " + RecordType.FullName);
    return helper;
}

此設計確保頻繁呼叫 GetHelper 時僅首次觸發編譯,後續直接從快取取得,大幅降低延遲。

代碼產生器實作細節

代碼產生器是動態編譯的心臟,採用反射解析實體結構,配合 IndentTextWriter(支援縮排的文字寫入器)產出可讀性高、利於除錯的 C# 代碼。

命名參數模式切換

框架提供 NamedParameter 屬性控制 SQL 參數風格:true 產出 @ParamName(如 SQL Server、Oracle),false 產出 ?(如 Access、MySQL 舊版),提升跨資料庫相容性。

資料讀取代碼生成(InnerReadRecord)

產生邏輯迭代實體所有可寫且標記 BindFieldAttribute 的屬性,依序輸出:

protected override object InnerReadRecord(IDataReader reader, int[] FieldIndexs) {
    MyORM.DB_Employees record = new MyORM.DB_Employees();
    int index = 0;
    index = FieldIndexs[0]; // EmployeeID
    if (index >= 0) record.EmployeeID = ConvertToInt32(reader.GetValue(index), 0);
    index = FieldIndexs[1]; // LastName
    if (index >= 0) record.LastName = ConvertToString(reader.GetValue(index), null);
    // ... 其他欄位
    return record;
}

映射流程:GetFieldIndexs 建立「屬性序號 → DataReader 欄位索引」映射表,生成代碼據此以 GetValue(int) 高速讀取並轉型賦值。

屬性驗證與綁定資訊提取(GetBindProperties)

嚴格驗證實體類別須為:public、非抽象、具無參數建構函式、屬性型別為 CLR 基礎型別/string/DateTime、屬性不可為索引子。違規即拋出異常。

private PropertyInfo[] GetBindProperties(Type RecordType) {
    // 檢查類別條件...
    var properties = new ArrayList();
    foreach (PropertyInfo p in RecordType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) {
        BindFieldAttribute fa = (BindFieldAttribute)Attribute.GetCustomAttribute(p, typeof(BindFieldAttribute));
        if (fa != null) {
            // 檢查屬性條件...
            properties.Add(p);
        }
    }
    if (properties.Count == 0) throw new ArgumentException("無任何綁定欄位");
    return (PropertyInfo[])properties.ToArray(typeof(PropertyInfo));
}

插入命令代碼生成(FillInsertCommand)

生成邏輯:略過值等於預設值的屬性,收集欄位名與值,依 NamedParameter 產出 INSERT INTO Table (F1,F2) VALUES (@V1,@V2) 或 (?,?) 形式,並同步建立 IDbDataParameter 陣列。

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