P/
PromptTools
選單
資產庫
指令組合
產生指令包
管理資料
Instruction asset
編輯一般指令
共用內容只保存一份;工具差異放在專用補充,下載時才組合。
基本資料
01
名稱
唯一代稱
小寫英文、數字與連字號;建立後仍可修改但不可重複。
簡短說明
主要分類
未分類
預設排序
標籤
尚無標籤,可先到「分類與標籤」建立。
共用 Markdown 與即時預覽
02
共用指令內容
--- applyTo: 'App_Code/BLL/**/*.cs' --- # BLL (Business Logic Layer) 開發規範 > **文件版本**: 1.0.0 > **最後更新**: 2026-01-26 > **適用範圍**: `App_Code/BLL/**/*.cs` --- ## 1. 概述 ### 1.1 層級定位 BLL (業務邏輯層) 為三層式架構中的中間層,負責**所有業務規則的實作與驗證**。BLL 是 Presentation Layer (ASPX/ASMX) 與 DAL 之間的唯一橋樑。 ### 1.2 核心職責 | 職責 | 說明 | |:-----|:-----| | **業務規則驗證** | 實作所有業務邏輯,如資料驗證、權限檢查、計算規則 | | **流程協調** | 協調多個 DAL 物件完成複雜業務流程 | | **資料轉換** | 將 DAL 回傳的 `IEnumerable<Dto>` 透過 `.ToList()` 具現化為 `List<Dto>`;或將 `DataSet` 映射為強型別 DTO | | **交易管理** | 管理跨表或跨資料庫的交易一致性 | | **例外處理** | 統一捕獲並記錄下層例外,回傳安全結果給上層 | ### 1.3 設計原則 | 原則 | 說明 | |:-----|:-----| | **單一職責** | 每個 BLL 類別對應一個業務領域 | | **高內聚** | 相關業務邏輯集中於同一類別 | | **低耦合** | 透過 DTO 與其他層溝通,不直接依賴 DataSet 結構 | | **防禦性程式設計** | 所有輸入參數必須驗證,所有回傳必須安全 | --- ## 2. 類別結構規範 ### 2.1 命名規則 | 項目 | 規則 | 範例 | |:-----|:-----|:-----| | 類別名稱 | `Biz{業務領域}` | `BizEmployee`, `BizOrder`, `BizPermission` | | 命名空間 | `{專案名稱}.BLL` | `Template.BLL` | | 方法名稱 | 動詞 + 名詞,描述業務意圖 | `Authenticate`, `GetUserProfile`, `ValidateOrder` | ### 2.2 標準類別結構 ```csharp using System; using System.Collections.Generic; using System.Data; using System.IO; using CHMC; namespace {ProjectName}.BLL { /// <summary> /// [業務領域描述] 業務邏輯類別 /// </summary> public class Biz{EntityName} { #region ========== 建構子 ========== /// <summary> /// 預設建構子 /// </summary> public Biz{EntityName}() { } #endregion #region ========== 靜態屬性 ========== /// <summary> /// 關聯的資料表名稱 (供外部參考) /// </summary> public static String TableName { get { return Dbo{EntityName}.TableName; } } #endregion #region ========== 公開方法 ========== // 業務方法實作於此區塊 #endregion #region ========== 私有方法 ========== // 輔助方法實作於此區塊 #endregion } } ``` --- ## 3. DAL 互動規範 ### 3.1 DAL 回傳型別與 BLL 處理策略 DAL 有兩種回傳模式,BLL 需依據不同模式採用對應的處理方式: | DAL 回傳模式 | BLL 處理方式 | 適用場景 | |:------------|:------------|:---------| | `IEnumerable<DtoXxx>` (主流) | 呼叫 `.ToList()` 取得 `List<DtoXxx>` | 一般業務 CRUD 查詢 | | `DataSet` | 自行遍歷 `DataRow` 做映射或直接操作 | 系統基礎設施查詢 (權限、員工);報表匯出 | ### 3.2 模式 A — IEnumerable 模式 (主流) > 當 DAL 回傳 `IEnumerable<Dto>` 時,BLL 只需呼叫 `.ToList()` 即可取得完整清單。DTO 的映射已由 DAL 內的 `yield return` + `DTO.GetSingleData(DataRow)` 完成。 ```csharp /// <summary> /// 查詢連絡記錄 /// </summary> /// <param name="dto">查詢條件</param> /// <param name="_ReturnCode">回傳碼</param> /// <param name="_ReturnMsg">回傳訊息</param> /// <returns>連絡記錄清單</returns> public List<DtoContactRecord> RetrieveContactRecord(DtoContactRecord dto, ref Int32 _ReturnCode, ref String _ReturnMsg) { DboContactRecord _dboContactRecord = null; List<DtoContactRecord> lstReturn = null; try { // 1. 實例化 DAL,傳入連線字串 _dboContactRecord = new DboContactRecord(Utility.SybaseConnectionString); // 2. 呼叫 DAL 取得 IEnumerable<T>,立即 .ToList() 具現化 lstReturn = _dboContactRecord.RetrieveContactRecord(dto).ToList(); // 3. (選擇性) 業務後處理 // 例如:JSON 轉換、計算欄位填充等 _ReturnCode = Enumeration.ReturnCode.SUCCESS; _ReturnMsg = Enums.ReturnMsg.Select; } catch (Exception ex) { Log.WriteLog("「BizContactRecord.RetrieveContactRecord」發生錯誤,原因:" + ex.Message); } finally { if (_dboContactRecord != null) { _dboContactRecord.Dispose(); _dboContactRecord = null; } } return lstReturn; } ``` ### 3.3 模式 B — DataSet 模式 (基礎設施) > 當 DAL 回傳 `DataSet` 時 (例如權限查詢、員工查詢),BLL 需自行遍歷 `DataRow` 進行映射。 ```csharp public List<DtoEntity> GetAllEntities() { // 1. 宣告於方法開頭,初始化為 null Dbo{EntityName} dboEntity = null; List<DtoEntity> result = new List<DtoEntity>(); DataSet dsData = null; try { // 2. 在 try 區塊內實例化,傳入連線字串 dboEntity = new Dbo{EntityName}(Utility.SybaseConnectionString); // 3. 執行資料存取操作 dsData = dboEntity.SelectAll(); // 4. 資料轉換與業務邏輯 if (dsData != null && dsData.Tables.Count > 0 && dsData.Tables[0].Rows.Count > 0) { foreach (DataRow row in dsData.Tables[0].Rows) { result.Add(MapRowToDto(row)); } } } catch (Exception ex) { // 5. 例外處理 Log.WriteLog(ex); } finally { // 6. 資源釋放 - 必須在 finally 中執行 if (dsData != null) { dsData.Dispose(); dsData = null; } if (dboEntity != null) { dboEntity.Dispose(); dboEntity = null; } } return result; } ``` ### 3.4 DAL 物件生命週期管理 **強制規則**:所有 DAL 物件必須遵循以下模式: 1. 在方法開頭宣告並初始化為 `null` 2. 在 `try` 區塊內實例化,傳入連線字串 3. 在 `finally` 區塊呼叫 `Dispose()` 並設為 `null` > ⛔ **嚴重警告:禁止使用 `using` 語句包覆 DAL 物件** > **技術原因**:所有 DAL 子類別的 `Dispose()` 宣告為 `internal new void Dispose()`(使用 `new` **隱藏**父類別,而非 `override` **覆寫**)。 > C# 的 `using` 語句透過 `IDisposable` 介面呼叫 `Dispose()`,多型解析時會找到**父類別** `DbHelperSQL.Dispose()`,**完全跳過**子類別自訂的 DataSet / param 清理邏輯,造成記憶體洩漏。 > > ```csharp > // ❌ 禁止:using 只呼叫父類別 DbHelperSQL.Dispose(), > // 子類別的 DataSet/param 清理永遠不執行 > using (DboBooking dal = new DboBooking(_connStr)) > { > return dal.SelectAll(...); > } > > // ✅ 正確:明確呼叫子類別自訂的 Dispose() > DboBooking dal = new DboBooking(_connStr); > try > { > return dal.SelectAll(...); > } > finally > { > if (dal != null) > { > dal.Dispose(); // 呼叫 DboBooking.Dispose(),確實釋放 DataSet 與 param > dal = null; > } > } > ``` ### 3.2 連線字串使用 **強制**:透過 `Utility` 類別取得連線字串: ```csharp // ✅ 正確:使用 Utility 提供的連線字串 dboEmployee = new DboEmployeeAseClient(Utility.SybaseConnectionString); dboMember = new DboEmployeeMSSQL(Utility.CHMC1ConnectionString); // ❌ 禁止:硬編碼連線字串 dboEmployee = new DboEmployee("Data Source=server;..."); ``` --- ## 4. 資料映射規範 ### 4.1 映射策略選擇 | DAL 回傳型別 | 映射方式 | 說明 | |:------------|:---------|:-----| | `IEnumerable<DtoXxx>` | **不需映射** — 直接 `.ToList()` | DTO 映射已在 DAL 的 `yield return` + `DTO.GetSingleData()` 完成 | | `DataSet` | BLL 自行 `MapRowToDto()` 或逐欄讀取 | 系統基礎設施類別 (權限、員工等) 使用此模式 | > **重要**:大多數業務查詢使用 IEnumerable 模式,BLL **不需**手動映射。僅在 DAL 回傳 DataSet 時才需要以下映射邏輯。 ### 4.2 DataSet 轉 DTO 標準模式 (僅適用於 DataSet 直通模式) ```csharp /// <summary> /// 將 DataRow 映射為 DTO 物件 /// </summary> /// <param name="row">資料列</param> /// <returns>DTO 物件</returns> private DtoEmployee MapRowToDto(DataRow row) { DtoEmployee dto = new DtoEmployee(); // 字串欄位 - 使用 ToString() 並 Trim() dto.EmployeeId = row["employee_id"].ToString().Trim(); dto.EmployeeName = row["employee_name"].ToString().Trim(); // 可空值欄位 - 必須檢查 DBNull if (row["birth_date"] != DBNull.Value) { dto.BirthDate = Convert.ToDateTime(row["birth_date"]); } else { dto.BirthDate = null; } // 數值欄位 dto.DeptId = Convert.ToInt32(row["dept_id"]); // 布林欄位 dto.IsEnabled = Convert.ToBoolean(row["is_enabled"]); return dto; } ``` ### 4.2 DBNull 處理規則 | 欄位類型 | 處理方式 | |:---------|:---------| | `String` | 直接 `ToString()`,空值自動轉為 `""` | | `Int32?`, `DateTime?` | 必須先檢查 `DBNull.Value` | | `Boolean` | 使用 `Convert.ToBoolean()`,確保資料庫值為 0/1 | ```csharp // ✅ 正確的 DBNull 處理 dto.LastLoginDate = row["last_login_date"] == DBNull.Value ? (DateTime?)null : Convert.ToDateTime(row["last_login_date"]); // ✅ 欄位存在性檢查 (當欄位可能不存在時) if (row.Table.Columns.Contains("work_title") && row["work_title"] != DBNull.Value) { dto.JobTitle = row["work_title"].ToString().Trim(); } ``` --- ## 5. 業務邏輯實作規範 ### 5.1 輸入驗證 所有公開方法必須在開頭進行輸入驗證: ```csharp /// <summary> /// 建立新使用者 /// </summary> /// <param name="newUser">使用者資料</param> /// <returns>成功回傳 true,失敗回傳 false</returns> public Boolean CreateUser(DtoUser newUser) { // 驗證 1:Null 檢查 if (newUser == null) { return false; } // 驗證 2:必要欄位檢查 if (String.IsNullOrEmpty(newUser.UserName)) { return false; } if (String.IsNullOrEmpty(newUser.Email)) { return false; } // 驗證 3:格式驗證 if (!IsValidEmail(newUser.Email)) { return false; } // 驗證 4:業務規則驗證 if (IsUserNameExists(newUser.UserName)) { return false; } // 通過所有驗證後,執行資料存取 DboUser dboUser = null; try { dboUser = new DboUser(Utility.SybaseConnectionString); return dboUser.Insert(newUser); } catch (Exception ex) { Log.WriteLog(ex); return false; } finally { if (dboUser != null) { dboUser.Dispose(); dboUser = null; } } } ``` ### 5.2 複合業務流程 當業務流程涉及多個 DAL 操作時: ```csharp /// <summary> /// 員工身分驗證並取得完整資訊 /// </summary> public Boolean Authenticate(String userId, String password, out EmpInfo empInfo) { empInfo = null; Boolean isAuthenticated = false; DboEmployeeAseClient dboEmployee = null; DataSet dsEmployee = null; try { // 步驟 1:身分驗證 (呼叫外部服務) isAuthenticated = ValidateCredentials(userId, password); if (!isAuthenticated) { return false; } // 步驟 2:取得員工資料 dboEmployee = new DboEmployeeAseClient(Utility.SybaseConnectionString); dsEmployee = dboEmployee.RetrieveByEmp(userId, "1"); // 步驟 3:驗證資料完整性 if (dsEmployee == null || dsEmployee.Tables.Count == 0 || dsEmployee.Tables[0].Rows.Count != 1) { return false; } // 步驟 4:組裝結果物件 DataRow row = dsEmployee.Tables[0].Rows[0]; empInfo = new EmpInfo(); empInfo.EmpID = row["employee_id"].ToString().Trim(); empInfo.EmpName = row["employee_name"].ToString().Trim(); empInfo.DeptID = row["dept_id"].ToString().Trim(); empInfo.DeptName = row["subject_desc"].ToString().Trim(); return true; } catch (Exception ex) { Log.WriteLog(ex); return false; } finally { if (dsEmployee != null) { dsEmployee.Dispose(); dsEmployee = null; } if (dboEmployee != null) { dboEmployee.Dispose(); dboEmployee = null; } } } ``` --- ## 6. 錯誤處理規範 ### 6.1 例外處理原則 | 規則 | 說明 | |:-----|:-----| | **必須捕獲** | 所有 DAL 呼叫及外部服務呼叫必須包覆於 `try-catch` | | **必須記錄** | 使用 `Log.WriteLog(ex)` 記錄所有例外 | | **安全回傳** | 查詢類回傳空集合 `new List<Dto>()`,異動類回傳 `false` | | **禁止吞掉** | 禁止空的 `catch` 區塊,至少要記錄 | ### 6.2 錯誤回傳策略 ```csharp // 查詢方法 - 失敗時回傳空集合 public List<DtoEmployee> GetAllEmployees() { List<DtoEmployee> result = new List<DtoEmployee>(); try { // ... 業務邏輯 } catch (Exception ex) { Log.WriteLog(ex); // 回傳空集合,而非 null } return result; } // 異動方法 - 失敗時回傳 false public Boolean UpdateEmployee(DtoEmployee employee) { try { // ... 業務邏輯 return true; } catch (Exception ex) { Log.WriteLog(ex); return false; } } // 查詢單筆 - 失敗時回傳 null public DtoEmployee GetEmployeeById(String id) { try { // ... 業務邏輯 } catch (Exception ex) { Log.WriteLog(ex); } return null; } ``` ### 6.3 例外重新包裝 (僅在必要時) ```csharp // 當需要向上層提供更明確的錯誤訊息時 catch (Exception ex) { Log.WriteLog(ex); throw new Exception( String.Format("「{0}.{1}」發生錯誤,原因:{2}", this.GetType().Name, "MethodName", ex.Message), ex); } ``` --- ## 7. 禁止事項 (Anti-Patterns) ### 7.1 🚫 禁止在 BLL 直接存取資料庫 ```csharp // ❌ 絕對禁止 using (AseConnection conn = new AseConnection(connectionString)) { conn.Open(); // ... } // ✅ 必須透過 DAL DboEmployee dbo = new DboEmployee(Utility.SybaseConnectionString); DataSet ds = dbo.SelectById(id); ``` ### 7.2 🚫 禁止直接回傳 DataSet 給 Presentation Layer ```csharp // ❌ 禁止:破壞層級隔離 public DataSet GetEmployeeData(String id) { DboEmployee dbo = new DboEmployee(Utility.SybaseConnectionString); return dbo.SelectById(id); // 直接回傳 DataSet } // ✅ 正確 (主流):DAL 回傳 IEnumerable<Dto>,BLL 用 .ToList() 轉為 List public List<DtoEmployee> GetAllEmployees() { DboEmployee dbo = new DboEmployee(Utility.SybaseConnectionString); return dbo.RetrieveAll().ToList(); } // ✅ 正確 (基礎設施):取得 DataSet 後轉換為 DTO public DtoEmployee GetEmployee(String id) { // ... 取得 DataSet 後手動轉換為 DTO return dto; } ``` ### 7.3 🚫 禁止使用 `using` 語句或在 `finally` 外釋放 DAL 物件 **技術背景**:DAL 子類別的 `Dispose()` 宣告為 `internal new void Dispose()`(**隱藏**父類別,不是 `override`)。`using` 透過 `IDisposable` 介面進行多型解析,只會執行父類別 `DbHelperSQL.Dispose()`,子類別的 DataSet、param 等資源**不會**被釋放,造成記憶體洩漏。 ```csharp // ❌ 禁止:看似安全,實則 DataSet/param 未釋放 using (DboEmployee dbo = new DboEmployee(Utility.SybaseConnectionString)) { return dbo.SelectById(id); } // ❌ 危險:若中途發生例外,資源不會被釋放 DboEmployee dbo = new DboEmployee(Utility.SybaseConnectionString); DataSet ds = dbo.SelectById(id); dbo.Dispose(); // 可能永遠執行不到 // ✅ 正確:在 finally 中明確呼叫子類別的 Dispose() DboEmployee dbo = null; try { dbo = new DboEmployee(Utility.SybaseConnectionString); return dbo.SelectById(id); } finally { if (dbo != null) { dbo.Dispose(); // 呼叫 DboEmployee.Dispose()(子類別),確實清理 DataSet / param dbo = null; } } ``` --- ## 8. Sybase 編碼處理 ### 8.1 CP850 轉換時機 當從 Sybase 讀取中文資料時,轉換應在 **DAL 層** 完成。BLL 接收的 DataSet 應已是 Unicode 編碼。 若 DAL 未處理,BLL 需進行轉換: ```csharp // 取得資料 dsData = dboEmployee.SelectById(id); // 如果 DAL 未做編碼轉換,在 BLL 處理 if (dsData != null && dsData.Tables[0].Rows.Count > 0) { String strXml = StringHelper.CP850TransUnicode(dsData.GetXml()); StringReader sr = new StringReader(strXml); dsData = new DataSet(); dsData.ReadXml(sr); sr.Close(); sr.Dispose(); } ``` --- ## 9. 程式碼審查檢核表 在提交程式碼前,請確認以下項目: ### 類別結構 - [ ] 類別名稱符合 `Biz{EntityName}` 格式 - [ ] 使用正確的命名空間 `{Project}.BLL` - [ ] 所有公開方法皆有 XML 註解 ### DAL 互動 - [ ] 所有 DAL 物件使用 `Utility.SybaseConnectionString` 或 `Utility.CHMC1ConnectionString` - [ ] DAL 物件在 `finally` 區塊中呼叫 `Dispose()` 並設為 `null` - [ ] DataSet 在使用完畢後呼叫 `Dispose()` 並設為 `null` - [ ] IEnumerable 模式使用 `.ToList()` 具現化,DataSet 模式使用 `MapRowToDto()` 映射 ### 業務邏輯 - [ ] 所有輸入參數在使用前進行驗證 - [ ] 實作必要的業務規則驗證 - [ ] DataSet 轉 DTO 時正確處理 `DBNull` ### 錯誤處理 - [ ] 所有外部呼叫包覆於 `try-catch-finally` - [ ] `catch` 區塊使用 `Log.WriteLog(ex)` 記錄錯誤 - [ ] 查詢方法失敗時回傳空集合或 `null` - [ ] 異動方法失敗時回傳 `false` ### 禁止事項 - [ ] 無直接資料庫連線程式碼 - [ ] 無直接回傳 `DataSet` 給上層 - [ ] 無在 `finally` 區塊外釋放資源的程式碼 - [ ] **無使用 `using` 語句包覆 DAL 物件**(DAL 的 `Dispose()` 為 `new` 隱藏而非 `override`,`using` 只呼叫父類別,子類別 DataSet/param 不會被釋放)
格式化預覽
工具與套用範圍
03
適用工具
GitHub Copilot
Codex
Claude Code
Cursor
Antigravity
套用範圍
整個專案
指定路徑
指定路徑模式
每行一筆。當次下載仍可覆寫,不會修改此正式設定。
工具專用補充
04
只填差異,不要複製整份共用內容。
GitHub Copilot
Codex
Claude Code
Cursor
Antigravity
修改說明
選填;會保存在這次建立的新版本中。
保存並建立新版本
取消