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

C# XSLT 代碼生成器實戰(上):多資料庫結構抽取與 XML 序列化架構

     編輯:關於C#

前言:代碼生成器——軟體開發的「複製貼上」自動化

代碼生成器屬於輔助工具軟體,核心價值:根據固定資訊(資料庫結構),機械化大量生成結構規律的樣板代碼(實體類、DAL、SQL、HTML…),釋放開發者專注於業務邏輯。本專案採用XSLT 驅動架構:

資料庫結構 → XML 文檔 (中介模型) → XSLT 模板 → 目標代碼 (C#/Java/VB/SQL/HTML...)

優勢:中介 XML 解耦資料來源與輸出格式,新增資料庫類型只需擴展抽取器,新增目標語言只需撰寫 XSLT。

軟體架構設計

資料來源:跨資料庫結構抽取策略

資料庫系統物件/方法關鍵資訊
Access 2000OleDbConnection.GetOleDbSchemaTableColumns、Indexes Schema;無系統表,靠 OLE DB 規範
SQL Serversysobjects、syscolumns、systypes、sp_helpindex系統表 + 預存程序取索引/主鍵
OracleCOL 視圖、USER_IND_COLUMNS 視圖資料字典視圖
PowerDesignerPDM XML 檔案XML 格式,直接 XPath 查詢

統一介面:DataBaseInfo.LoadFromAccess2000/SQLServer/Oracle/PDMXML 填充同一物件模型。

核心物件模型:三層樹狀結構

  • DataBaseInfo:資料庫容器,Name、Tables 集合、多種 LoadFrom* 方法
  • TableInfo:資料表,Name、Remark、Fields 集合、索引鍵集合
  • FieldInfo:欄位,Name、FieldType、FieldWidth、Nullable、PrimaryKey、Indexed、ValueTypeName (CLR 型別) 等

集合類別繼承 CollectionBase,提供強型別索引器 this[string name]。

代碼生成模板:XSLT 檔案約定

  • 置於應用程式目錄,檔名 _*.xslt
  • 啟動時掃描目錄動態載入選單,支援熱插拔

程式進入點:單一 ASPX 頁面

  • 下拉選單:資料表名稱(含「所有表」)、XSLT 模板名稱
  • 按鈕:重新整理系統、生成代碼
  • 顯示區:Label 控件直接輸出 HTML/代碼

核心類別 DataBaseInfo 深度剖析

1. Access 2000 結構抽取:GetOleDbSchemaTable

public int LoadFromAccess2000(string mdbPath) {
    using (OleDbConnection conn = new OleDbConnection(
        "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + mdbPath)) {
        conn.Open();
        // 1. 抽取欄位定義
        using (DataTable schema = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, null)) {
            foreach (DataRow row in schema.Rows) {
                string tbl = row["TABLE_NAME"].ToString();
                if (tbl.StartsWith("MSys")) continue; // 略過系統表
                TableInfo table = GetOrCreateTable(tbl);
                FieldInfo field = new FieldInfo();
                field.Name = row["COLUMN_NAME"].ToString();
                field.Nullable = Convert.ToBoolean(row["IS_NULLABLE"]);
                OleDbType dt = (OleDbType)Convert.ToInt32(row["DATA_TYPE"]);
                field.FieldType = (dt == OleDbType.WChar) ? "Char" : dt.ToString();
                field.FieldWidth = row["CHARACTER_MAXIMUM_LENGTH"].ToString();
                if (row["DESCRIPTION"] != DBNull.Value) field.Remark = row["DESCRIPTION"].ToString();
                table.Fields.Add(field);
            }
        }
        // 2. 抽取索引/主鍵
        using (DataTable idxSchema = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Indexes, null)) {
            foreach (DataRow row in idxSchema.Rows) {
                string tbl = row["TABLE_NAME"].ToString();
                TableInfo table = Tables[tbl];
                if (table != null) {
                    FieldInfo field = table.Fields[row["COLUMN_NAME"].ToString()];
                    if (field != null) {
                        field.Indexed = true;
                        field.PrimaryKey = Convert.ToBoolean(row["PRIMARY_KEY"]);
                    }
                }
            }
        }
    }
    return totalFieldCount;
}

關鍵:GetOleDbSchemaTable 為標準 OLE DB 介面,參數 OleDbSchemaGuid.Columns/Indexes 決定回傳結構描述資訊。

2. SQL Server 結構抽取:系統表聯查 + sp_helpindex

string sql = @"
select so.name as TableName, sc.name as ColName, st.name as TypeName,
       sc.length as ColLength, sc.isnullable, so.type as ObjType
from syscolumns sc
join sysobjects so on sc.id = so.id
join systypes st on sc.xusertype = st.xusertype
where (so.type='U' or so.type='V') and st.name not in ('_default_','sysname')
order by so.name, sc.name";

using (IDataReader rd = cmd.ExecuteReader()) {
    while (rd.Read()) {
        string tbl = rd.GetString(0).Trim();
        if (lastTable == null || lastTable.Name != tbl) {
            lastTable = new TableInfo { Name = tbl, Tag = rd.GetValue(5).ToString() }; // Tag 存 ObjType
            Tables.Add(lastTable);
        }
        FieldInfo f = new FieldInfo {
            Name = rd.GetString(1), FieldType = rd.GetString(2),
            FieldWidth = rd[3].ToString(),
            Nullable = !rd.IsDBNull(4) && rd.GetInt32(4) == 1
        };
        lastTable.Fields.Add(f);
    }
}
// 逐表呼叫 sp_helpindex 取主鍵/索引
foreach (TableInfo t in Tables) {
    if (t.Tag == "U") { // 僅使用者表
        cmd.CommandText = "sp_helpindex \"" + t.Name + "\"";
        using (IDataReader rd = cmd.ExecuteReader()) {
            while (rd.Read()) {
                string idxName = rd.GetString(0), idxDesc = rd.GetString(1), idxCols = rd.GetString(2);
                bool isPK = idxDesc.ToLower().IndexOf("primary") >= 0;
                foreach (string col in idxCols.Split(',')) {
                    FieldInfo f = t.Fields[col.Trim()];
                    if (f != null) { f.Indexed = true; f.PrimaryKey = isPK; }
                }
            }
        }
    }
}

技巧:TableInfo.Tag 暫存 sysobjects.type(U=使用者表、V=檢視表),後續篩選。sp_helpindex 回傳索引描述含「PRIMARY」關鍵字判斷主鍵。

3. Oracle 結構抽取:資料字典視圖

// 1. COL 視圖:表名、欄名、型別、寬度
string sql = "Select TName, CName, coltype, width From Col Order by TName, CName";
// ... 迴圈建立 TableInfo/FieldInfo ...

// 2. USER_IND_COLUMNS:索引欄位
sql = "select table_name, column_name, index_name from user_ind_columns order by table_name, column_name";
// ... 迴圈設定 Indexed/PrimaryKey (index_name 以 PK 開頭視為主鍵) ...

4. PowerDesigner PDM XML 解析:XPath + 命名空間

XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);
nsm.AddNamespace("a", "attribute");
nsm.AddNamespace("c", "collection");
nsm.AddNamespace("o", "object");

XmlNode root = doc.SelectSingleNode("/Model/o:RootObject/c:Children/o:Model", nsm);
dbName = ReadXMLValue(root, "a:Name", nsm);

foreach (XmlNode tblNode in root.SelectNodes("c:Tables/o:Table", nsm)) {
    TableInfo table = new TableInfo();
    table.Name = ReadXMLValue(tblNode, "a:Code", nsm);      // 代碼名
    table.Remark = ReadXMLValue(tblNode, "a:Name", nsm);    // 顯示名
    // 主鍵 ID 反查
    string pkRef = ReadXMLValue(tblNode, "c:PrimaryKey/o:Key/@Ref", nsm);
    StringCollection pkCols = new StringCollection();
    if (pkRef != null) {
        foreach (XmlNode kn in tblNode.SelectNodes("c:Keys/o:Key[@Id='" + pkRef + "']/c:Key.Columns/o:Column/@Ref", nsm))
            pkCols.Add(kn.Value);
    }
    foreach (XmlNode colNode in tblNode.SelectNodes("c:Columns/o:Column", nsm)) {
        FieldInfo f = new FieldInfo();
        f.Name = ReadXMLValue(colNode, "a:Code", nsm);
        f.Remark = ReadXMLValue(colNode, "a:Name", nsm);
        f.Description = ReadXMLValue(colNode, "a:Comment", nsm);
        string dt = ReadXMLValue(colNode, "a:DataType", nsm);
        if (dt != null) { int i = dt.IndexOf('('); if (i > 0) dt = dt.Substring(0, i); }
        f.FieldType = dt;
        f.FieldWidth = ReadXMLValue(colNode, "a:Length", nsm);
        if (pkCols.Contains(colNode.Attributes["Id"].Value)) f.PrimaryKey = true;
        table.Fields.Add(f);
    }
    Tables.Add(table);
}

PDM XML 使用自訂命名空間(a:、c:、o:),必須註冊 XmlNamespaceManager 才能正確 XPath 查詢。主鍵資訊分離於 c:Keys 結構,需透過 @Ref 關聯。

XML 序列化:物件模型 → XML 文檔

ASPX GetXMLString 方法:

private string GetXMLString(object obj) {
    StringWriter sw = new StringWriter();
    XmlTextWriter xw = new XmlTextWriter(sw) { Indentation = 3, IndentChar = ' ', Formatting = Formatting.Indented };
    new XmlSerializer(obj.GetType()).Serialize(xw, obj);
    xw.Close();
    string xml = sw.ToString();
    int idx = xml.IndexOf("?>");
    if (idx > 0) xml = xml.Substring(idx + 2); // 移除 XML 宣告
    return xml.Trim();
}
  • XmlSerializer 自動處理公開屬性/欄位、集合遞迴
  • [XmlType("Table")] 將 TableInfo 序列化為 <Table> 而非 <TableInfo>
  • 輸出縮排易讀,移除 XML 宣告便於 XSLT 處理

ASPX 頁面邏輯:從使用者選擇到代碼生成

// Page_Load
if (!IsPostBack) RefreshSystem();

// RefreshSystem:填充下拉選單
DataBaseInfo info = GetInfo(); // Session 快取
cboTable.Items.Add(new ListItem("所有表", "所有表"));
foreach (TableInfo t in info.Tables) cboTable.Items.Add(new ListItem(t.Name, t.Name));
cboXSLT.Items.Add("XML代碼"); // 直接顯示 XML
foreach (string f in Directory.GetFiles(MapPath("."), "_*.xslt"))
    cboXSLT.Items.Add(new ListItem(Path.GetFileNameWithoutExtension(f), Path.GetFileNameWithoutExtension(f)));

// cmdCreate_Click:生成代碼核心
DataBaseInfo info = GetInfo();
string xml = (cboTable.SelectedIndex == 0) ? GetXMLString(info) : GetXMLString(info.Tables[cboTable.SelectedValue]);
if (cboXSLT.SelectedIndex <= 0) {
    html = "<textarea>" + xml + "</textarea>";
} else {
    XslTransform xslt = new XslTransform();
    xslt.Load(Server.MapPath(cboXSLT.SelectedValue) + ".xslt");
    StringWriter sw = new StringWriter();
    XmlDocument doc = new XmlDocument(); doc.LoadXml(xml);
    xslt.Transform(doc, null, sw, null);
    html = sw.ToString();
}
lblResult.Text = "<b>共生成 " + html.Length + " 字元</b><br/>" + html;

部署與執行要點

  1. IIS 建立虛擬目錄,指向專案資料夾
  2. 授予 ASPNET/IIS_IUSRS 目錄讀取/寫入權限(需存取 .mdb、.xslt、序列化快取)
  3. 瀏覽 xslcreatecode.aspx,選擇資料表與模板,點擊「創建代碼」
  4. 模板檔置於專案目錄,命名 _custom.xslt,點擊「重新整理系統」即可使用

本講小結與展望

本講完整實作了多資料庫結構抽取 → 統一物件模型 → XML 序列化 → XSLT 驅動代碼生成的端到端管線。關鍵技術點:

  • 跨資料庫 Schema 抽取的統一介面設計
  • DataBaseInfo/TableInfo/FieldInfo 三層物件模型與集合封裝
  • XmlSerializer + XmlTypeAttribute 控制 XML 形狀
  • XSLT 模板約定與熱插拔掃描機制
  • ASP.NET 單頁應用實現生成器 UI

下一講將深入剖析核心 XSLT 模板(_csharphashtable.xslt、_HTML.xslt)的轉換邏輯,展示條件分支、迴圈、變數、命名模板等進階 XSLT 技巧,並說明如何擴展生成 Python、Go 等新語言代碼。

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