所謂動態編譯技術,是指應用程式在執行期由程式內部自動產生 C# 程式碼,隨即呼叫 .NET Framework 內建的 C# 編譯器生成臨時組件,並將其載入應用程式域中,透過反射機制動態呼叫其中的物件模組。這項技術將傳統開發階段手動使用代碼產生器的「靜態編譯」流程完全自動化,並將代碼生成與編譯的時機延後至軟體執行期間。
動態編譯兼具彈性與效能:產生的臨時組件與開發工具編譯出的組件無異,執行速度同樣極快。.NET Framework 本身即大量採用此技術,例如 XML 序列化/反序列化、ASP.NET 處理 ASPX 頁面等皆是典型應用。
Microsoft.CSharp.CSharpCodeProvider 編譯原始碼,產生臨時組件(過程中會在磁碟產生臨時檔案,受 .NET 安全性設定影響)。
動態編譯適合「功能複雜且多變、靜態代碼產生器難以涵蓋」的情境。若功能單純,靜態編譯即足夠;若功能過於複雜無法以代碼產生器表達,則亦不適用。須注意:編譯過程會產生磁碟臨時檔,受 .NET 安全性原則限制;且在原始碼生成與編譯之間存在時間窗,惡意程式理論上可竄改原始碼植入惡意邏輯,此為技術固有風險。
本框架承接上一講的 ORM 基礎,導入動態編譯技術大幅提升執行效能。核心設計仍以 BindTableAttribute 與 BindFieldAttribute 標記實體類別與資料表、欄位的對應關係,範例實體 DB_Employees 完整保留。

RecordORMHelper 物件。應用程式啟動時批次註冊所有實體類型,註冊動作不會立即觸發代碼生成與編譯。RecordORMHelper:定義實體與資料庫對應的核心介面,包含從 IDataReader 讀取資料建立實體、初始化新增/修改/刪除的 IDbCommand 等抽象方法。資料存取模組統一透過此介面操作。RecordORMHelper 的具體類別代碼,單次編譯可產出多個衍生類別。mscorlib.dll、System.dll、System.Data.dll 及框架本身所在組件(甚至可為 EXE),產出包含多個 Helper 類別的臨時組件。建議於框架初始化階段一次性註冊所有可能用到的實體類型,使框架僅執行一次動態編譯。若陸續註冊,框架將多次編譯產生多個臨時組件,增加啟動開銷。
RecordORMHelper 深度解析此類別位於框架底層,定義標準化對應操作合約:
TableName:回傳實體對應的資料表名稱(由 BindTableAttribute 指定)。RecordFieldNames:回傳所有綁定欄位名稱陣列(由 BindFieldAttribute 指定)。ReadRecord/ReadRecords:封裝資料讀取流程,內部呼叫 GetFieldIndexs 取得欄位索引映射表,再呼叫 InnerReadRecord(需衍生類實作)逐筆填充實體。FillInsertCommand/FillUpdateCommand/FillDeleteCommand:初始化對應 SQL 命令與參數,回傳參數數量。GetFieldIndexs:核心映射邏輯,比對 IDataReader 欄位名稱與實體屬性綁定名稱(不區分大小寫、支援「資料表.欄位」格式),建立索引陣列,避免使用低效的 GetValue(string)。ConvertToInt32、ConvertToString、ConvertToDateTime 等,統一處理 DBNull 與預設值。框架主類別 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 陣列。