承接上集實體定義與特性設計,本文完整實現 **快速 ORM 框架 (MyFastORMFramework)** 的核心引擎:代碼生成器 (針對 Insert/Update/Delete/Select 生成強型別 C# 代碼)、動態編譯管線 (Roslyn 前身 CSharpCodeProvider 記憶體編譯)、委託快取調用 (繞過反射開銷)、CRUD 高階 API、部署策略**,實現 **開發效率與運行效能雙贏**。
[BindTable("TableName")] 類別、`[BindField("ColName", Key=true, WriteFormat="yyyy-MM-dd")] 屬性。RecordORMHelper 定義虛擬方法 FillInsertCommand/Update/Delete/InnerReadRecord 等待代碼生成器覆寫。MyFastORMFramework 管理連線、實體註冊、代碼生成/編譯/快取、CRUD 執行。為每個實體類型動態生成一個衍生自 RecordORMHelper 的具體類別 (如 DB_EmployeesORMHelper),覆寫四大核心方法:
// 生成邏輯片段
foreach (var prop in bindProps)
{
if (prop.PropertyType == typeof(string))
{
// 字串非 null 且非空才加入參數
writer.WriteLine($"if (myRecord.{prop.Name} != null && myRecord.{prop.Name}.Length != 0)");
writer.WriteLine("{");
writer.WriteLine($" fieldNames.Add(\"{fieldName}\");");
writer.WriteLine($" values.Add(myRecord.{prop.Name});");
writer.WriteLine("}");
}
else if (prop.PropertyType == typeof(DateTime))
{
// 日期支援 WriteFormat 自訂格式
string fmt = fieldAttr?.WriteFormat ?? "yyyy-MM-dd HH:mm:ss";
writer.WriteLine($"fieldNames.Add(\"{fieldName}\");");
writer.WriteLine($"values.Add(myRecord.{prop.Name}.ToString(\"{fmt}\"));");
}
else
{
// 數值/布林/Guid 直接加入
writer.WriteLine($"fieldNames.Add(\"{fieldName}\");");
writer.WriteLine($"values.Add(myRecord.{prop.Name});");
}
}
// 後續組裝 SQL: INSERT INTO Table (cols) VALUES (@p0, @p1...)
// 參數化: 命名參數 @Value0 或 位置參數 ?
// 掃描 Key=true 的屬性
var keyProps = bindProps.Where(p => GetBindFieldAttr(p).Key).ToList();
if (keyProps.Count == 0)
writer.WriteLine("throw new NotSupportedException(\"FillDeleteCommand: 無 Key 欄位\");");
else
{
// WHERE KeyCol1 = @KeyProp1 AND KeyCol2 = @KeyProp2...
// 參數名: @Key + PropName (命名參數) 或 ?
// 執行時依序加入參數值
}
// SET Col1=@Val1, Col2=@Val2...
// 參數順序: 所有綁定屬性值 + Key 屬性值 (給 WHERE 用)
// 日期/字串空值處理同 Insert
// 返回參數總數
// 依 RecordFieldNames 陣列順序讀取 reader
// 類型轉換: reader.GetInt32, GetString, GetDateTime...
// 字串 null 處理: reader.IsDBNull(i) ? null : reader.GetString(i)
// 返回填滿的實體物件
private int BuildHelpers(string saveSourcePath = null)
{
// 1. 收集未建立 Helper 的實體類型
var pending = _recordHelpers.Where(kvp => kvp.Value == null).Select(kvp => kvp.Key).ToList();
if (pending.Count == 0) return 0;
// 2. 生成代碼
string ns = "Temp_" + Guid.NewGuid().ToString("N");
string source = GenerateCode(ns, saveSourcePath, pending);
// 3. 編譯參數
var cp = new CompilerParameters
{
GenerateExecutable = false,
GenerateInMemory = true,
IncludeDebugInformation = true,
ReferencedAssemblies = { "mscorlib.dll", "System.dll", "System.Data.dll" }
};
// 加入實體所在組件 + 框架自身組件
foreach (var t in pending) cp.ReferencedAssemblies.Add(t.Assembly.CodeBase.Replace("file:///", ""));
cp.ReferencedAssemblies.Add(GetType().Assembly.CodeBase.Replace("file:///", ""));
// 4. 編譯
var provider = new CSharpCodeProvider();
CompilerResults result;
#if NET20_OR_GREATER
result = provider.CompileAssemblyFromSource(cp, source); // .NET 2.0+
#else
result = provider.CreateCompiler().CompileAssemblyFromSource(cp, source); // .NET 1.1
#endif
// 5. 結果處理
_compilerOutput = string.Join(Environment.NewLine, result.Output.Cast());
if (result.Errors.HasErrors) return -1;
var asm = result.CompiledAssembly;
_assemblies.Add(asm); // 防止 GC 卸載動態組件
// 6. 實例化 Helper 並快取
foreach (var t in pending)
{
var helperType = asm.GetType($"{ns}.{t.Name}ORMHelper");
var helper = (RecordORMHelper)Activator.CreateInstance(helperType);
_recordHelpers[t] = helper;
Console.WriteLine($"FastORM 為 \"{t.FullName}\" 建立操作助手");
}
provider.Dispose();
return pending.Count;
}
public class MyFastORMFramework : IDisposable
{
private IDbConnection _conn;
private readonly Dictionary _helpers = new();
private readonly List _assemblies = new();
public MyFastORMFramework(IDbConnection conn) => _conn = conn ?? throw new ArgumentNullException(nameof(conn));
// 內部取得/建立 Helper
private RecordORMHelper GetHelper(Type t)
{
if (!_helpers.TryGetValue(t, out var h) || h == null)
{
_helpers[t] = null; // 佔位
BuildHelpers();
h = _helpers[t];
}
return h;
}
// 查詢: 任意 SQL → 實體清單
public ArrayList ReadObjects(string sql, Type recordType)
{
CheckConnection();
var helper = GetHelper(recordType);
using (var cmd = _conn.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
return helper.ReadRecords(reader, 0);
}
}
// 查詢單筆
public object ReadObject(string sql, Type recordType)
{
var list = ReadObjects(sql, recordType);
return list.Count > 0 ? list[0] : null;
}
// 查詢全表
public object[] ReadAllObjects(Type recordType)
{
var helper = GetHelper(recordType);
return ReadObjects($"SELECT * FROM {helper.TableName}", recordType).ToArray();
}
// 新增
public int InsertObject(object entity)
{
CheckConnection();
var helper = GetHelper(entity.GetType());
using (var cmd = _conn.CreateCommand())
{
if (helper.FillInsertCommand(cmd, entity) > 0)
return cmd.ExecuteNonQuery();
}
return 0;
}
// 批次新增
public int InsertObjects(IEnumerable entities)
{
int count = 0;
foreach (var e in entities) count += InsertObject(e);
return count;
}
// 更新
public int UpdateObject(object entity)
{
CheckConnection();
var helper = GetHelper(entity.GetType());
using (var cmd = _conn.CreateCommand())
{
if (helper.FillUpdateCommand(cmd, entity) > 0)
return cmd.ExecuteNonQuery();
}
return 0;
}
// 刪除
public int DeleteObject(object entity)
{
CheckConnection();
var helper = GetHelper(entity.GetType());
using (var cmd = _conn.CreateCommand())
{
if (helper.FillDeleteCommand(cmd, entity) > 0)
return cmd.ExecuteNonQuery();
}
return 0;
}
private void CheckConnection()
{
if (_conn == null) throw new InvalidOperationException("Connection is null");
if (_conn.State != ConnectionState.Open) throw new InvalidOperationException("Connection not open");
}
public void Dispose() => _conn?.Dispose();
}
// 實體類別 (可由工具自動生成)
[BindTable("Employees")]
public class DB_Employees
{
[BindField("EmployeeID", Key = true)] public int EmployeeID { get; set; }
[BindField("LastName")] public string LastName { get; set; }
[BindField("FirstName")] public string FirstName { get; set; }
[BindField("Title")] public string Title { get; set; }
[BindField("BirthDate", WriteFormat = "yyyy-MM-dd")] public DateTime BirthDate { get; set; }
// ... 其他欄位
public string FullName => $"{LastName} {FirstName}";
}
// Form 代碼片段
private MyFastORMFramework CreateFramework()
{
var conn = new OleDbConnection($@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={Application.StartupPath}\demo.mdb");
conn.Open();
return new MyFastORMFramework(conn);
}
private void btnRefresh_Click(object sender, EventArgs e)
{
using (var orm = CreateFramework())
{
var list = orm.ReadAllObjects(typeof(DB_Employees));
lstEmployees.DataSource = list;
lstEmployees.DisplayMember = "FullName";
}
}
private void btnInsert_Click(object sender, EventArgs e)
{
using (var dlg = new EmployeeDialog()) { dlg.Employee = new DB_Employees(); if (dlg.ShowDialog() == DialogResult.OK) { using (var orm = CreateFramework()) { if (orm.InsertObject(dlg.Employee) > 0) btnRefresh_Click(null, null); } } }
}
private void btnUpdate_Click(object sender, EventArgs e)
{
if (lstEmployees.SelectedItem is DB_Employees emp)
{
using (var dlg = new EmployeeDialog()) { dlg.Employee = emp; if (dlg.ShowDialog() == DialogResult.OK) { using (var orm = CreateFramework()) { if (orm.UpdateObject(emp) > 0) btnRefresh_Click(null, null); } } }
}
}
private void btnDelete_Click(object sender, EventArgs e)
{
if (lstEmployees.SelectedItem is DB_Employees emp && MessageBox.Show($"刪除 {emp.FullName}?", "確認", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
using (var orm = CreateFramework()) { orm.DeleteObject(emp); btnRefresh_Click(null, null); }
}
}
MyFastORMFramework.cs, RecordORMHelper.cs, BindTableAttribute.cs, BindFieldAttribute.cs 四個檔案至專案。FastOrm.dll,供 **C#/VB.NET/F#/PowerShell** 等任意 .NET 語言專案參考。System.Reflection, System.CodeDom.Compiler, System.Data),不依賴第三方庫。IDbConnection/IDbCommand/IDataReader,支援 SQL Server, Oracle, MySQL, PostgreSQL, SQLite, Access, ODBC 等任意 ADO.NET Provider。| 階段 | 傳統反射 ORM | 本框架 (動態編譯) |
|---|---|---|
| 首次啟動 | 快 (無編譯) | 稍慢 (動態編譯 ~幾十~百毫秒) |
| 後續 CRUD | 每次反射 PropertyInfo.GetValue/SetValue、MethodInfo.Invoke | **直接呼叫強型別委託/生成代碼**,零反射,接近手寫 ADO.NET 速度 |
| 記憶體 | 較低 | 動態組件常駐記憶體 (可接受) |
| 開發體驗 | 需大量映射配置/Fluent API | **純屬性標記**,代碼生成器自動處理 SQL/參數/映射 |
async Task FillInsertCommandAsync(...) ,框架提供 InsertObjectAsync。InsertObjects 合併單一 SQL 多值 INSERT INTO ... VALUES (...), (...), ...。IQueryProvider 將 LINQ 運算式轉 SQL。Microsoft.CodeAnalysis.CSharp.Scripting 或 Source Generator (編譯期生成),移除運行時編譯依賴。本講完整構建了一個 **生產可用的快速 ORM 框架**:
BindTable/BindField 聲明式映射,零 XML/Fluid API。RecordORMHelper 衍生類,覆寫 Insert/Update/Delete/Read 邏輯,**消除運行時反射**。CSharpCodeProvider 記憶體編譯、組件快取、委託快取,**首次編譯後零開銷**。ReadObjects/InsertObject/UpdateObject/DeleteObject 簡潔易用,支援任意 ADO.NET Provider。這展示了 **反射 (掃描結構) + 動態編譯 (生成執行邏輯)** 結合的經典模式,廣泛應用於序列化、Mapper、規則引擎、動態代理等高效能框架開發中。