###### tags: `企業及軟體架構模式` # 9.3 Table Module (表格模組) > 使用一個「單一執行個體」,它處理某個資料庫資料表或檢視表中所有資料列的「商業邏輯」,例如 CRUD、加總等行為 ## 前言 + 物件導向的關鍵特性:將【資料】與【行為】結合在一起使用。假如我們有一個 Employee 類別,它的每一個執行個體都會對應到一個特定的員工,我們就可以對這員工進行讀取、更新資料,或是其他行為 ```csharp= public class Employee { public string Name { get; set; } public int JobGrade { get; set; } public int GetSalary() { if (JobGrade > 8) { return 32000; } return 22000; } } var employee = new Employee(); employee.Name = 'kyo'; employee.JobGrade = 7; Console.WriteLine(employee.GetSalary()); ``` + Domain Model 的問題點在於:通常你需要受過大量程式訓練才能==在 domain 物件與 table 物件這兩種表示形式轉換自如。將 table 資料取出轉換為 domain 物件,或是將 domain 物件轉為 table 物件存入資料庫== ```csharp= public class User //table 物件 { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime CreatedOn { get; set; } } public class UserInfo //domain 物件 { public int Id { get; set; } public string FullName { get; set; } } ``` + Table Module 使用資料庫的每個 table 或 view 來對應一個類別,來組織【領域邏輯】 + Domain Model 與 Table Module 最大的不同在於,如果你有多筆訂單 + Domain Model: 每張訂單都是一個物件(編按:不一定XD) + Table Module: 擁有一個物件處理所有訂單 ## 9.3.1 運作原理 + 【資料】與【行為】打包在一起 + 與物件最大的差別在於:無法直接取得物件屬性。 + 例如:取得 Employee 的 Address,必須仰賴 employeeModule.getAddress(long employeeID),而不是 employee.address + Table Module 的資料通常是 SQL 呼叫的結果 + Table Module 雖然有 Table 一詞,但實際上不一定依賴於資料庫的 Table,更多是取決於 application 需要的虛擬表格,包括 table, view、特定 SQL 查詢結果,甚至是程式處理後的資料集合 + 通常需要來自多個 Table Module 才能一些實際有用的工作 + 另一種方法是 Table Data Gateway,將來自不同來源的資料包裝成單一 Table Module,所以也有可能不需要連線資料庫 + 例如:我向 Table Data Gateway 要求該 Employee 出席紀錄,出席紀錄表面上看起來是單一 Table Module,但背後可能來自多個 Table Module 或其他資料來源。 ```csharp= // From chatgpt public class OrderModule { private string _connectionString; public OrderModule(string connectionString) { _connectionString = connectionString; } public void CreateOrder(int customerId, int productId, int quantity, DateTime orderDate) { // 在這裡插入創建訂單的 SQL 代碼 } public DataTable GetOrder(int orderId) { // 在這裡插入讀取訂單的 SQL 代碼 return new DataTable(); } public void UpdateOrder(int orderId, int customerId, int productId, int quantity, DateTime orderDate) { // 在這裡插入更新訂單的 SQL 代碼 } public void DeleteOrder(int orderId) { // 在這裡插入刪除訂單的 SQL 代碼 } public decimal GetTotalRevenue(DateTime startDate, DateTime endDate) { // 在這裡插入計算總收入的 SQL 代碼和業務邏輯 return 0; } } ``` ## 9.3.2 使用時機 + Table Module 是基於表格導向的資料,當你使用 Record Set (or DataRows) 存取表格形式資料時,顯然可以使用 Table Module。 + Table Module 無法組織複雜的邏輯,也無法建立各類別的關聯,這時 Domain Model 會是更好的選擇 ## 9.3.3 範例 + 目前有 Products, Contracts, RevenueRecognitions 三個 Table Module,我想知道某合約訂單的收入。 ```csharp= //pseudo code public class ContractModule { //...initial ds... var table = ds.Tables['Contract']; public DataRow GetContract(int contractId) { return DataRow contractRow = table.first(x => x.Id == contractId); } public string GetProductId(int contractId) { //取得 ProductId } public void CalculateRecognitions(int contractId) { DataRow contractRow = GetContract(contractId); Decimal amount = (Decimal)contractRow["amount"]; RevenueRecognitions rr = new RevenueRecognitions(table.DataSet); Product prod = new Product(table.DataSet); int prodId = GetProductId(contractId); if (prod.GetProductType(prodId) == "DB") { rr.Insert(contractId, amount, DateTime.Now()); } else { ... } } } public class RevenueRecognitions { // DataTable 換成 List 應該也適用 public int Insert(int contractId, Decimal amount, Decimal date) { DataRow newRow = table.NewRow(); lond id = GetNextID(); newRow["ID"] = id; newRow["contractId"] = contractId; newRow["amount"] = amount; newRow["date"] = date; table.Rows.Add(newRow); //注意: 存在記憶體,而非資料庫 return id; } public Decimal RecognizedRevenue(int contractId) { DataRow[] rows = table.where(x => x.contractId == contractId); Decimal result = 0m; foreach (DataRow row in rows) { result += (Decimal)row["amount"]; } return result; } } ```