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

C# CodeDom 實戰:動態生成資料存取層原始碼完整教學

     編輯:關於C語言

System.CodeDom 命名空間提供語言無關的程式碼物件模型,讓你在執行期以物件圖構建原始碼,再由特定語言提供者 (CSharpCodeProvider/VBCodeProvider) 輸出對應語言檔案。本文完整示範如何用 CodeDom 自動生成一個含連線管理、參數化查詢的 PowerDataAccess 類別,並解析關鍵節點與擴展策略。

目標生成代碼


using System;
using System.Data;
using System.Data.SqlClient;

namespace Power.Cuike519
{
    public class PowerDataAccess
    {
        private SqlConnection m_connection;

        public PowerDataAccess() { }

        public virtual SqlConnection Connection
        {
            get => m_connection;
            set => m_connection = value;
        }

        public virtual DataSet GetAllAuthors(string s_State)
        {
            try
            {
                if (m_connection != null)
                {
                    var da = new SqlDataAdapter(
                        string.Format("select * from authors where state like '{0}'", s_State),
                        m_connection);
                    var ds = new DataSet();
                    da.Fill(ds);
                    return ds;
                }
                return null;
            }
            catch (System.ApplicationException ex) { throw ex; }
            finally { m_connection.Close(); }
        }
    }
}

CodeDom 核心物件模型對照

目標結構CodeDom 類別命名空間
編譯單元/usingCodeSnippetCompileUnit / CodeNamespaceImportSystem.CodeDom
命名空間CodeNamespaceSystem.CodeDom
類別宣告CodeTypeDeclarationSystem.CodeDom
欄位CodeMemberFieldSystem.CodeDom
屬性CodeMemberProperty (GetStatements/SetStatements)System.CodeDom
建構函式CodeConstructorSystem.CodeDom
方法CodeMemberMethodSystem.CodeDom
參數CodeParameterDeclarationExpressionSystem.CodeDom
try/catch/finallyCodeTryCatchFinallyStatementSystem.CodeDom
if 條件CodeConditionStatementSystem.CodeDom
區域變數CodeVariableDeclarationStatementSystem.CodeDom
方法呼叫CodeMethodInvokeExpressionSystem.CodeDom
return/throwCodeMethodReturnStatement / CodeThrowExceptionStatementSystem.CodeDom
this 參考CodeThisReferenceExpressionSystem.CodeDom
欄位參考CodeFieldReferenceExpressionSystem.CodeDom
屬性值參考CodePropertySetValueReferenceExpressionSystem.CodeDom

完整生成器實作


using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;

public class DalCodeGenerator
{
    public void Generate(string outputPath = "PowerDataAccess.cs")
    {
        // 1. 建立輸出流與 C# 提供者
        using (var stream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
        using (var writer = new StreamWriter(stream))
        {
            var provider = new CSharpCodeProvider();
            var options = new CodeGeneratorOptions
            {
                BracingStyle = "C",          // 大括號換行
                IndentString = "    ",       // 4 空格縮排
                BlankLinesBetweenMembers = true
            };

            // 2. 建立編譯單元:using 指令
            var compileUnit = new CodeCompileUnit();
            var ns = new CodeNamespace("Power.Cuike519");
            compileUnit.Namespaces.Add(ns);

            ns.Imports.Add(new CodeNamespaceImport("System"));
            ns.Imports.Add(new CodeNamespaceImport("System.Data"));
            ns.Imports.Add(new CodeNamespaceImport("System.Data.SqlClient"));

            // 3. 類別宣告
            var cls = new CodeTypeDeclaration("PowerDataAccess")
            {
                IsClass = true,
                TypeAttributes = System.Reflection.TypeAttributes.Public
            };
            ns.Types.Add(cls);

            // 4. 私有欄位:SqlConnection m_connection
            cls.Members.Add(new CodeMemberField
            {
                Name = "m_connection",
                Type = new CodeTypeReference("SqlConnection"),
                Attributes = MemberAttributes.Private
            });

            // 5. 預設建構函式
            cls.Members.Add(new CodeConstructor
            {
                Attributes = MemberAttributes.Public
            });

            // 6. 屬性:Connection (虛擬、公開、get/set)
            var prop = new CodeMemberProperty
            {
                Name = "Connection",
                Type = new CodeTypeReference("SqlConnection"),
                Attributes = MemberAttributes.Public | MemberAttributes.Virtual
            };
            // get { return this.m_connection; }
            prop.GetStatements.Add(new CodeMethodReturnStatement(
                new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection")));
            // set { this.m_connection = value; }
            prop.SetStatements.Add(new CodeAssignStatement(
                new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection"),
                new CodePropertySetValueReferenceExpression()));
            cls.Members.Add(prop);

            // 7. 方法:GetAllAuthors
            var method = new CodeMemberMethod
            {
                Name = "GetAllAuthors",
                ReturnType = new CodeTypeReference("DataSet"),
                Attributes = MemberAttributes.Public | MemberAttributes.Virtual
            };
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "s_State"));

            // try 區塊
            var tryBlock = new CodeTryCatchFinallyStatement();

            // if (this.m_connection != null)
            var ifStmt = new CodeConditionStatement
            {
                Condition = new CodeBinaryOperatorExpression(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection"),
                    CodeBinaryOperatorType.IdentityInequality,
                    new CodePrimitiveExpression(null))
            };

            // 真分支:建立 SqlDataAdapter、DataSet、Fill、return
            // SqlDataAdapter da = new SqlDataAdapter(string.Format(...), this.m_connection);
            var daVar = new CodeVariableDeclarationStatement
            {
                Name = "da",
                Type = new CodeTypeReference("SqlDataAdapter"),
                InitExpression = new CodeObjectCreateExpression(
                    new CodeTypeReference("SqlDataAdapter"),
                    new CodeMethodInvokeExpression(
                        new CodeTypeReferenceExpression("string"), "Format",
                        new CodePrimitiveExpression("select * from authors where state like '{0}'"),
                        new CodeVariableReferenceExpression("s_State")),
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection"))
            };
            ifStmt.TrueStatements.Add(daVar);

            // DataSet ds = new DataSet();
            var dsVar = new CodeVariableDeclarationStatement
            {
                Name = "ds",
                Type = new CodeTypeReference("DataSet"),
                InitExpression = new CodeObjectCreateExpression(new CodeTypeReference("DataSet"))
            };
            ifStmt.TrueStatements.Add(dsVar);

            // da.Fill(ds);
            ifStmt.TrueStatements.Add(new CodeExpressionStatement(
                new CodeMethodInvokeExpression(
                    new CodeVariableReferenceExpression("da"), "Fill",
                    new CodeVariableReferenceExpression("ds"))));

            // return ds;
            ifStmt.TrueStatements.Add(new CodeMethodReturnStatement(
                new CodeVariableReferenceExpression("ds")));

            // 假分支:return null;
            ifStmt.FalseStatements.Add(new CodeMethodReturnStatement(
                new CodePrimitiveExpression(null)));

            tryBlock.TryStatements.Add(ifStmt);

            // catch (ApplicationException ex) { throw ex; }
            var catchClause = new CodeCatchClause("ex", new CodeTypeReference("System.ApplicationException"));
            catchClause.Statements.Add(new CodeThrowExceptionStatement(
                new CodeVariableReferenceExpression("ex")));
            tryBlock.CatchClauses.Add(catchClause);

            // finally { this.m_connection.Close(); }
            tryBlock.FinallyStatements.Add(new CodeExpressionStatement(
                new CodeMethodInvokeExpression(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection"),
                    "Close")));

            method.Statements.Add(tryBlock);
            cls.Members.Add(method);

            // 8. 生成代碼
            provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);
            writer.Flush();
        }

        Console.WriteLine($"Generated: {Path.GetFullPath(outputPath)}");
    }
}

關鍵技術細節解析

1. CodeSnippetCompileUnit vs CodeCompileUnit

  • CodeSnippetCompileUnit:直接寫入原始字串 (如 using System;),不支援語言無關轉換
  • CodeCompileUnit + CodeNamespaceImport:標準物件模型,**支援多語言輸出** (C#/VB/F#)

2. 運算式樹構建邏輯

CodeDom 採用表達式導向:每個運算 (new、呼叫、二元運算、索引) 都是節點,遞迴組合形成 AST。優點:語言無關;缺點:冗長、需大量 new CodeXXXExpression()。

3. 字串插值/Format 的處理


// 產生:string.Format("select * from authors where state like '{0}'", s_State)
new CodeMethodInvokeExpression(
    new CodeTypeReferenceExpression("string"), "Format",
    new CodePrimitiveExpression("select * from authors where state like '{0}'"),
    new CodeVariableReferenceExpression("s_State"))

4. 例外處理最佳實踐

  • 原範例 catch { throw ex; } 會**重設堆疊追蹤**,建議改 throw; (CodeDom 用 CodeThrowExceptionStatement(null) 表示 rethrow)
  • finally 中關閉連線前應檢查 State != ConnectionState.Closed

擴展方向:從 Hard-code 到元資料驅動

層次資料來源生成目標
Level 1硬編碼 (本文)單一 DAL 類別
Level 2XML/JSON 組態多實體 CRUD 類別
Level 3資料庫 Schema (INFORMATION_SCHEMA)完整 ORM 實體 + Repository
Level 4Roslyn Syntax API增量原始碼生成器 (Source Generator)

進階替代方案:T4 / Roslyn Source Generator

  • T4 (Text Template Transformation Toolkit):Visual Studio 內建、模板語法直觀、設計期生成
  • Roslyn Source Generator (C# 9+):編譯期介入、型別安全、IDE 即時反饋、零執行期開銷
  • CodeDom 現定位為舊版相容/動態編譯場景,新專案優先考慮上述方案

常見問題排查

問題原因解決
生成代碼縮排混亂CodeGeneratorOptions 未設定設定 IndentString、BracingStyle
缺少 using未加 CodeNamespaceImport在 CodeNamespace.Imports 加入
型別參考錯誤CodeTypeReference 字串拼寫錯誤使用 typeof(T).FullName 或完整命名空間
編譯生成的代碼報錯CodeDom 邏輯與目標語言語法不符先生成檔案再手動編譯驗證,調整節點結構

結語:CodeDom 雖屬較舊技術,但其語言無關 AST 建模思想值得深入理解。掌握 CodeDom,等於掌握了「以程式碼寫程式碼」的元編程核心能力。現代專案建議轉向 Roslyn Source Generator,享受編譯期型別安全與零開銷優勢;但若需執行期動態組裝並編譯 (如規則引擎、腳本系統),CodeDom 仍是可靠選擇。

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