ORM 核心環節:將 DataRow/DataTable 映射為強型別實體。本文提供基於反射的通用 Mapper 類,支援單行 ToEntity 與批次 ToList,處理型別轉換、DBNull、日期解析等細節。
public static class Mapper {
// 單行映射:非泛型
public static object ToEntity(DataRow row, Type entityType) {
if (entityType == null || row == null) return null;
var entity = Activator.CreateInstance(entityType);
CopyToEntity(entity, row);
return entity;
}
// 單行映射:泛型
public static T ToEntity<T>(DataRow row) where T : new() {
if (row == null) return new T();
var item = new T();
CopyToEntity(item, row);
return item;
}
// 核心複製邏輯
public static void CopyToEntity(object entity, DataRow row) {
if (entity == null || row == null) return;
var props = entity.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props) {
if (!CanSet(prop, row)) continue;
try {
var val = row[prop.Name];
if (val is DBNull) { prop.SetValue(entity, null); continue; }
SetValue(entity, row, prop);
} catch { /* 記錄日誌,跳過單屬性失敗 */ }
}
}
private static bool CanSet(PropertyInfo prop, DataRow row)
=> prop.CanWrite && row.Table.Columns.Contains(prop.Name);
private static void SetValue(object entity, DataRow row, PropertyInfo prop) {
var raw = row[prop.Name];
var targetType = prop.PropertyType;
// 處理 Nullable<DateTime> 與 DateTime
if (targetType == typeof(DateTime) || targetType == typeof(DateTime?)) {
if (DateTime.TryParse(raw.ToString(), out var dt))
prop.SetValue(entity, dt);
return;
}
// 可擴展:Guid、Enum、自定義 TypeConverter
prop.SetValue(entity, Convert.ChangeType(raw, targetType));
}
}
public static List<T> ToList<T>(DataTable table) where T : new() {
var list = new List<T>();
if (table == null) return list;
foreach (DataRow row in table.Rows)
list.Add(ToEntity<T>(row));
return list;
}
// 進階:支援欄位名與屬性名不一致(Attribute 映射)
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute { public string Name { get; set; } }
// 修改 CanSet/GetColumnName 邏輯讀取 Attribute
| 問題 | 方案 | 效果 |
|---|---|---|
反射每行調用 GetProperties() | 靜態緩存 PropertyInfo[] 按型別 | 減少 90%+ 反射開銷 |
SetValue 裝箱/拆箱 | 發動委派/Expression Tree 編譯 Setter | 接近手寫代碼速度 |
| DBNull 檢查分散 | 統一在 Convert.ChangeType 前處理 | 代碼簡潔、邏輯集中 |
| 日期格式文化差異 | 指定 CultureInfo.InvariantCulture | 跨伺服器穩定 |
connection.Query<T>(sql) 內建高效映射(IL 生成)。Mapper.Map<T>(row) 支援複雜映射規則、Profile 管理。ObjectReader 以委派高速讀寫成員。