關聯式資料庫以成熟的二維表格模型存儲結構化資料;物件導向程式設計則以封裝、繼承、多態處理複雜領域模型。兩者範式差異導致資料在邊界交換時極不順暢,傳統手工映射如「獨木橋」般低效且難維護。ORM(Object-Relational Mapping)框架如「高速公路大橋」般建立自動映射機制,大幅提升開發效率。雖純物件資料庫可徹底解決阻抗失配,但遷移成本極高,現階段 ORM 仍是務實選擇。
反射是 CallByName 的 .NET 強化版。所有 .NET 型別繼承自 object,透過 GetType() 取得 System.Type 即為反射入口。可列舉型別所有成員(含私有)、取得欄位型別、方法簽章、事件委派等完整中繼資料,並支援動態建立實例、調用方法、讀寫屬性。
特性繼承自 System.Attribute,以 [AttributeName] 語法附加於類別、屬性、方法等程式單元,不影響執行邏輯,但可被反射讀取。常見用法如 [STAThread] 標記進入點。自訂特性可定義建構函數與屬性,支援 AttributeUsage 限制適用目標與重複性。
僅處理常見簡單映射:一類別對一資料表,一屬性對一欄位。定義兩大核心特性:
BindTableAttribute:標記類別對應資料表名(Name 屬性,預設為類別名)。BindFieldAttribute:標記屬性對應欄位名(Name、Key 關鍵欄位、ReadFormat/WriteFormat 格式化)。映射資訊直接保存在代碼中(特性),優點是集中維護、重構友善;缺點是映射變更需重新編譯部署。XML 配置檔雖分離關注點但引入三方同步複雜度,本框架為示範反射與特性故採代碼內建方式。
INSERT 參數化 SQL 執行。UPDATE ... SET ... WHERE 關鍵欄位=值,需至少一 Key=true 屬性。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、型別轉換)。GetBindInfoprivate 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 與 InnerReadValuespublic 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 動態賦值,完整實現資料列到物件的自動映射。