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

C# 發現之旅第 12 講:基於反射與動態編譯的快速 ORM 框架實戰 (下) — 代碼生成器、動態編譯管線、CRUD 運作與部署

     編輯:關於C#

承接上集實體定義與特性設計,本文完整實現 **快速 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 執行。

代碼生成器:GenerateCode() 完整實現

為每個實體類型動態生成一個衍生自 RecordORMHelper 的具體類別 (如 DB_EmployeesORMHelper),覆寫四大核心方法:

1. FillInsertCommand — 智慧過濾空字串


// 生成邏輯片段
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 或 位置參數 ?

2. FillDeleteCommand — 僅靠 Key 欄位建構 WHERE


// 掃描 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 (命名參數) 或 ?
    // 執行時依序加入參數值
}

3. FillUpdateCommand — SET 所有欄位 + WHERE Key 條件


// SET Col1=@Val1, Col2=@Val2...
// 參數順序: 所有綁定屬性值 + Key 屬性值 (給 WHERE 用)
// 日期/字串空值處理同 Insert
// 返回參數總數

4. InnerReadRecord — IDataReader 高效映射實體


// 依 RecordFieldNames 陣列順序讀取 reader
// 類型轉換: reader.GetInt32, GetString, GetDateTime...
// 字串 null 處理: reader.IsDBNull(i) ? null : reader.GetString(i)
// 返回填滿的實體物件

動態編譯管線:BuildHelpers()


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;
}

CRUD 高階 API:零反射、強型別、高效能


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();
}

實戰測試:WinForm 員工管理 Demo


// 實體類別 (可由工具自動生成)
[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 四個檔案至專案。
  • 編譯為 DLL:建立 Class Library 專案產出 FastOrm.dll,供 **C#/VB.NET/F#/PowerShell** 等任意 .NET 語言專案參考。
  • 無外部依賴:純 .NET BCL (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 (...), (...), ...。
  • LINQ 整合:提供 IQueryProvider 將 LINQ 運算式轉 SQL。
  • Roslyn 現代化:改用 Microsoft.CodeAnalysis.CSharp.Scripting 或 Source Generator (編譯期生成),移除運行時編譯依賴。

總結

本講完整構建了一個 **生產可用的快速 ORM 框架**:

  1. **屬性驅動**:BindTable/BindField 聲明式映射,零 XML/Fluid API。
  2. **代碼生成器**:針對每個實體生成強型別 RecordORMHelper 衍生類,覆寫 Insert/Update/Delete/Read 邏輯,**消除運行時反射**。
  3. **動態編譯管線**:CSharpCodeProvider 記憶體編譯、組件快取、委託快取,**首次編譯後零開銷**。
  4. **高階 CRUD API**:ReadObjects/InsertObject/UpdateObject/DeleteObject 簡潔易用,支援任意 ADO.NET Provider。
  5. **輕量部署**:四個核心檔案或單一 DLL,零配置、零依賴。

這展示了 **反射 (掃描結構) + 動態編譯 (生成執行邏輯)** 結合的經典模式,廣泛應用於序列化、Mapper、規則引擎、動態代理等高效能框架開發中。

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