
ERP 系統往往面對高度變動的流程與資料結構。傳統以強型別類別綁定資料表的 ORM(Object Relational Mapping)模式,對於「版本快速迭代」與「客製化需求」顯得僵化。BeeNET 採用 DR-Map(Definition Relational Mapping) 模型,以「定義(Definition)」為核心,在執行階段動態建立資料關聯,形成 Definition-Driven Architecture(定義驅動架構),可在不重新編譯程式的情況下即時調整欄位與關聯。
| 面向 | ORM(Object Relational Mapping) | DR-Map(Definition Relational Mapping) |
|---|---|---|
| 關聯來源 | 強型別類別 | FormDefine 結構化定義(XML) |
| 綁定方式 | 編譯期 | 執行期動態綁定 |
| 模型更新 | 需重新編譯程式 | 更新定義即可生效 |
| 擴充性 | 限於程式碼層 | 支援外部配置與快取重載 |
| 適用場景 | 固定資料結構 | 多變動態表單與企業系統 |
💡 ORM 注重「類別與資料表」的靜態映射;DR-Map 注重「定義與關聯」的動態生成,讓系統能在執行階段依定義調整資料結構。
BeeNET 的 DR-Map 架構將「資料表結構、欄位、關聯」集中於 FormDefine 定義檔中。每個 FormDefine 描述一個表單的資料模型,包括欄位(Field)、關聯(Relation)、參照(Reference)。透過 RelationProgId 屬性,FormDefine 可以描述與其他表單的關聯。系統於執行階段解析這些定義後,會動態生成 SQL 指令,實現靈活的資料查詢與關聯運作。

與傳統 ORM 操作「資料表 (Table) 關聯」不同,BeeNET DR-Map 操作的是「表單 (Form) 關聯」。
| 對比面向 | 傳統 ORM | BeeNET DR-Map |
|---|---|---|
| 關聯層級 | 資料表(Table-Level) | 表單(Form-Level) |
| 關聯目標 | 關心資料表的外鍵與欄位 | 關心表單與表單之間的邏輯關係 |
| 設計觀點 | 資料庫導向 (Database-Centric) | 業務導向 (User/Process-Centric) |
| 關聯深度 | 多層遞迴(Table → Table → Table) | 永遠單階(Form → Form) |
| 使用思維 | 理解欄位結構 | 定義要取回的表單欄位即可 |
在 DR-Map 架構中,每個表單(Form)代表一個完整的業務資料集合(如「專案」、「部門」、「員工」)。關聯永遠為「單階 (One-Level)」,開發者不必理解被關聯表單的內部結構,只需描述「表單與表單之間」的邏輯關聯以及要取回的欄位。
例如,專案表單(Project)可關聯到「員工(Employee)」作為專案經理,而員工又可關聯到「部門(Department)」作為其所屬單位。
📄 FormDefine 範例連結:
<FormDefine ProgId="Project">
<FormTable TableName="Project" DbTableName="ft_project" DisplayName="專案">
<Fields>
<FormField FieldName="pm_rowid" RelationProgId="Employee">
<RelationFieldMappings>
<FieldMapping SourceField="sys_name" DestinationField="ref_pm_name"/>
<FieldMapping SourceField="ref_dept_name" DestinationField="ref_pm_dept_name"/>
</RelationFieldMappings>
</FormField>
</Fields>
</FormTable>
</FormDefine>
<FormDefine ProgId="Employee">
<FormTable TableName="Employee" DbTableName="ft_employee" DisplayName="員工">
<Fields>
<FormField FieldName="dept_rowid" RelationProgId="Department">
<RelationFieldMappings>
<FieldMapping SourceField="sys_name" DestinationField="ref_dept_name"/>
</RelationFieldMappings>
</FormField>
</Fields>
</FormTable>
</FormDefine>
<FormDefine ProgId="Department">
<FormTable TableName="Department" DbTableName="ft_department" DisplayName="部門">
<Fields>
<FormField FieldName="manager_rowid" RelationProgId="Employee">
<RelationFieldMappings>
<FieldMapping SourceField="sys_name" DestinationField="ref_manager_name"/>
</RelationFieldMappings>
</FormField>
</Fields>
</FormTable>
</FormDefine>
💬 使用者只需思考:「專案要顯示專案經理與部門名稱」,而不需理解員工或部門的底層欄位結構。這種「表單導向 (Form-Centric)」設計,讓 DR-Map 關聯模型更貼近業務語意,開發直覺、維護輕鬆。
存放於 samples/Define/FormDefine/ 目錄中,包含表單結構(Table)、欄位(Field)、關聯設定(RelationProgId)、參照欄位(RelationField)。系統在執行階段讀取 FormDefine 後,會將其載入至記憶體快取中,以減少重複解析與 I/O 開銷。
FormDefine 採用 使用頻率導向(frequency-based caching) 與 檔案相依(file-dependent caching) 的複合快取策略。當某個 FormDefine 被載入後,系統會根據下列原則自動管理其快取生命週期:
此設計可確保 常用定義持續存在於快取中,而變更過的定義能即時更新,達到「即時性」與「穩定性」兼具的效果。
💡 由於 FormDefine 屬於定義性資料,變更頻率低、讀取頻率高,因此這種快取機制能顯著降低重複解析成本並提升整體系統效能。
IFormCommandBuilder 定義以 FormDefine 為基礎的 SQL 建構介面,負責動態產生 Select/Insert/Update/Delete 語法。針對 SQL Server,提供 SqlFormCommandBuilder 實作,執行 BuildSelectCommand() 方法,系統會依據 Select、Where、Sort 條件,自動判斷所需 JOIN 結構,生成對應 SQL。
/// <summary>
/// 建立 Select 語法的資料庫命令。
/// </summary>
/// <param name="tableName">資料表名稱。</param>
/// <param name="selectFields">要取得的欄位集合字串,以逗點分隔欄位名稱,空字串表示取得所有欄位。</param>
/// <param name="filter">過濾條件。</param>
/// <param name="sortFields">排序欄位集合。</param>
DbCommandSpec BuildSelectCommand(string tableName, string selectFields, FilterNode filter = null, SortFieldCollection sortFields = null);
以下示範 BuildSelectCommand() 在不同查詢條件下如何依據 FormDefine 自動決定 JOIN 結構。
這些案例對應單元測試 BuildSelectCommandTests,涵蓋從單純查詢到多重參考的完整範圍。
var builder = new SqlFormCommandBuilder("Project");
var command = builder.BuildSelectCommand("Project", "sys_id,sys_name");
📘 說明:
僅選取Project主檔欄位,不包含任何參考欄位,因此不產生 JOIN。
產生 SQL 範例:
SELECT
A.[sys_id],
A.[sys_name]
FROM [ft_project] A
var filter = new FilterCondition("ref_pm_name", ComparisonOperator.StartsWith, "張");
var command = builder.BuildSelectCommand("Project", "sys_id,sys_name", filter);
📘 說明:
雖然僅選取主檔欄位,但 Where 條件使用了ref_pm_name(專案經理姓名),系統會自動 JOIN 員工表(ft_employee)。
產生 SQL 範例:
SELECT
A.[sys_id],
A.[sys_name]
FROM [ft_project] A
LEFT JOIN [ft_employee] B ON A.[pm_rowid] = B.[sys_rowid]
WHERE B.[sys_name] LIKE @p0
var sortFields = new SortFieldCollection();
sortFields.Add(new SortField("ref_pm_dept_name", SortDirection.Asc));
var command = builder.BuildSelectCommand("Project", "sys_id,sys_name", null, sortFields);
📘 說明:
雖然查詢欄位僅來自Project,但排序欄位使用ref_pm_dept_name,因此會自動加入與員工表(ft_employee)及部門表(ft_department)的 JOIN。
產生 SQL 範例:
SELECT
A.[sys_id],
A.[sys_name]
FROM [ft_project] A
LEFT JOIN [ft_employee] B ON A.[pm_rowid] = B.[sys_rowid]
LEFT JOIN [ft_department] C ON B.[dept_rowid] = C.[sys_rowid]
ORDER BY C.[sys_name] ASC
var command = builder.BuildSelectCommand("Project", "sys_id,sys_name,ref_owner_dept_name,ref_pm_dept_name");
📘 說明:
若選取欄位包含多個參考欄位(例如專案負責人部門與專案經理部門),DR-Map 會依據各欄位的 RelationProgId 自動建立多層 JOIN。
產生 SQL 範例:
SELECT
A.[sys_id],
A.[sys_name],
B.[sys_name] AS [ref_owner_dept_name],
D.[sys_name] AS [ref_pm_dept_name]
FROM [ft_project] A
LEFT JOIN [ft_department] B ON A.[owner_dept_rowid] = B.[sys_rowid]
LEFT JOIN [ft_employee] C ON B.[pm_rowid] = C.[sys_rowid]
LEFT JOIN [ft_department] D ON C.[dept_rowid] = D.[sys_rowid]
var filterGroup = FilterGroup.All(
FilterCondition.Contains("sys_name", "專案"),
FilterCondition.Equal("ref_pm_name", "張三")
);
var sortFields = new SortFieldCollection
{
new SortField("sys_id", SortDirection.Asc)
};
var command = builder.BuildSelectCommand("Project", "sys_id,sys_name", filterGroup, sortFields);
📘 說明:
FilterGroup包含多個條件:其中一個是主檔欄位 (sys_name),另一個為參考欄位 (ref_pm_name)。
系統只會針對參考欄位自動建立 JOIN,避免不必要的多餘關聯。
產生 SQL 範例:
SELECT
A.[sys_id],
A.[sys_name]
FROM [ft_project] A
LEFT JOIN [ft_employee] B ON A.[pm_rowid] = B.[sys_rowid]
WHERE (A.[sys_name] LIKE @p0 AND B.[sys_name] = @p1)
ORDER BY A.[sys_id] ASC
🧠 重點說明:
DR-Map 的 SQL 組成完全取決於查詢需求:
- 僅主檔欄位 → 無 JOIN
- 使用參考欄位(Select/Where/Order) → 自動加入必要 JOIN
- 多參考欄位 → 多層關聯自動解析
這讓開發者僅需描述「要什麼資料」,而不需思考「怎麼 JOIN」,實現真正的 Definition-Driven Query 模式。
ERP 系統的資料結構與邏輯受 使用者操作、參數、客製設定 高度影響。若仍以強型別 ORM 綁定,容易造成「型別爆炸」與「效能瓶頸」。
| 特性 | 說明 |
|---|---|
| 動態欄位 | 欄位會依角色、權限、公司別或版本動態出現/隱藏。 |
| 客製化需求 | 不同客戶可自訂欄位、邏輯與檢核條件。 |
| 多樣回傳結構 | 同一表單的查詢結果,可能依參數產生不同欄位集。 |
| 動態關聯來源 | 例如報價單可依業務情境關聯不同資料來源(Customer/Lead/Partner)。 |
在這樣的情況下,若仍使用 ORM 強型別類別:
BeeNET 的 DR-Map(Definition Relational Mapping) 不僅是 ORM 的替代方案,更是企業級系統動態資料架構的核心設計思想。DR-Map 是 BeeNET 資料層(Bee.Db)與定義層(Bee.Define)協作的關鍵實作,使整體架構由靜態類別轉為動態定義驅動的系統核心。
DR-Map 讓 ERP 系統真正落實「以定義驅動」的理念,實現不中斷服務、不重編程的即時調整能力,為企業帶來靈活、穩定且可持續演進的資料關聯架構。
📘 HackMD 原文筆記:
👉 https://hackmd.io/@jeff377/drmap
📢 歡迎轉載,請註明出處
📬 歡迎追蹤我的技術筆記與實戰經驗分享
Facebook | HackMD | GitHub | NuGet