P/
PromptTools
選單
資產庫
指令組合
產生指令包
管理資料
Instruction asset
編輯一般指令
共用內容只保存一份;工具差異放在專用補充,下載時才組合。
基本資料
01
名稱
唯一代稱
小寫英文、數字與連字號;建立後仍可修改但不可重複。
簡短說明
主要分類
未分類
預設排序
標籤
尚無標籤,可先到「分類與標籤」建立。
共用 Markdown 與即時預覽
02
共用指令內容
--- applyTo: 'App_Code/Model/**/*.cs' --- # DTO (Data Transfer Object) 開發規範 > **文件版本**: 1.0.0 > **最後更新**: 2026-01-26 > **適用範圍**: `App_Code/Model/**/*.cs` --- ## 1. 概述 ### 1.1 層級定位 DTO (資料傳輸物件) 為三層式架構中的**跨層資料載體**,負責在各層之間傳遞資料。DTO 是「貧血模型」(Anemic Model),僅包含資料屬性,不包含行為。 ### 1.2 核心原則 | 原則 | 說明 | |:-----|:-----| | **純資料容器** | DTO 的唯一職責是封裝資料,在層與層之間傳遞 | | **零邏輯** | 🚫 **絕對禁止**包含業務邏輯、資料存取程式碼、複雜計算 | | **可序列化** | 必須支援 JSON/XML 序列化,供 WebService 傳輸使用 | | **不可變性** | 屬性應只做簡單的 get/set,不做額外處理 | ### 1.3 DTO vs. 其他物件 | 物件類型 | 用途 | 所在層 | |:---------|:-----|:-------| | **DTO** | 跨層資料傳輸 | Model (共用) | | **Entity** | 對應資料庫表結構 | DAL | | **ViewModel** | 呈現層專用資料 | Presentation | --- ## 2. 命名規範 ### 2.1 類別命名 **格式**: `Dto{功能模組}{子用途}` | 用途 | 命名模式 | 範例 | |:-----|:---------|:-----| | 主要實體 | `Dto{Entity}` | `DtoEmployee`, `DtoPatient` | | 搜尋條件 | `Dto{Entity}Search` | `DtoEmployeeSearch` | | 列表項目 | `Dto{Entity}ListItem` | `DtoEmployeeListItem` | | 下拉選項 | `Dto{Entity}Option` | `DtoEmployeeOption` | | 操作結果 | `Dto{Operation}Result` | `DtoLoginResult` | ### 2.2 全域唯一性 由於 `App_Code` 不使用命名空間,類別名稱**必須**在全專案中唯一: ```csharp // ✅ 正確:具描述性的唯一名稱 public class DtoPatientBasicInfo { } public class DtoPatientMedicalRecord { } // ❌ 避免:過於通用的名稱 public class DtoInfo { } // 可能與其他模組衝突 public class DtoData { } // 意義不明確 ``` ### 2.3 屬性命名 | 資料庫欄位風格 | DTO 屬性風格 | 說明 | |:---------------|:-------------|:-----| | `employee_id` | `EmployeeId` | 使用 PascalCase | | `patient_name` | `PatientName` | 移除底線 | | `is_enabled` | `IsEnabled` | 布林值加 Is/Has 前綴 | --- ## 3. 類別結構規範 ### 3.1 標準 DTO 結構 ```csharp using System; using System.Collections.Generic; /// <summary> /// [實體描述] 資料傳輸物件 /// </summary> public class Dto{EntityName} { #region ========== 屬性 ========== /// <summary> /// [屬性描述] (主鍵) /// </summary> public Int32 Id { get; set; } /// <summary> /// [屬性描述] /// </summary> public String Name { get; set; } /// <summary> /// [屬性描述] (可空) /// </summary> public DateTime? CreateDate { get; set; } /// <summary> /// [屬性描述] (集合) /// </summary> public List<DtoChildEntity> Items { get; set; } #endregion #region ========== 建構子 ========== /// <summary> /// 預設建構子 /// </summary> public Dto{EntityName}() { // 初始化集合屬性,避免 NullReferenceException Items = new List<DtoChildEntity>(); } #endregion } ``` ### 3.2 完整範例 ```csharp using System; using System.Collections.Generic; /// <summary> /// 病患基本資料 資料傳輸物件 /// </summary> public class DtoPatient { #region ========== 屬性 ========== /// <summary> /// 病歷號 (主鍵) /// </summary> public String PatientId { get; set; } /// <summary> /// 病患姓名 /// </summary> public String PatientName { get; set; } /// <summary> /// 性別 (M: 男, F: 女) /// </summary> public String Sex { get; set; } /// <summary> /// 生日 (可空) /// </summary> public DateTime? Birthday { get; set; } /// <summary> /// 身分證字號 /// </summary> public String IdNumber { get; set; } /// <summary> /// 聯絡電話 /// </summary> public String Phone { get; set; } /// <summary> /// 是否為 VIP /// </summary> public Boolean IsVip { get; set; } /// <summary> /// 過敏記錄列表 /// </summary> public List<String> Allergies { get; set; } #endregion #region ========== 建構子 ========== /// <summary> /// 預設建構子 /// </summary> public DtoPatient() { Allergies = new List<String>(); } #endregion } ``` --- ## 4. 屬性規範 ### 4.1 存取修飾詞 所有屬性**必須**為 `public`,且同時具備 `get` 與 `set`: ```csharp // ✅ 正確 public String Name { get; set; } // ❌ 禁止:private set 會導致反序列化失敗 public String Name { get; private set; } // ❌ 禁止:唯讀屬性無法被序列化框架設值 public String Name { get; } ``` ### 4.2 資料型別選用 | 場景 | 建議型別 | 說明 | |:-----|:---------|:-----| | 字串 | `String` | 使用 `String` 而非 `string` (專案風格) | | 整數 | `Int32` | 使用 `Int32` 而非 `int` | | 布林 | `Boolean` | 使用 `Boolean` 而非 `bool` | | 日期 | `DateTime` 或 `DateTime?` | 可空日期使用 Nullable | | 金額 | `Decimal` | 避免浮點數精度問題 | | 集合 | `List<T>` | 不使用 `IList<T>` 或 `IEnumerable<T>` | ### 4.3 可空型別 (Nullable) 當資料庫欄位允許 NULL 時,DTO 屬性**必須**宣告為 Nullable: ```csharp /// <summary> /// 最後登入時間 (可空:未登入過時為 null) /// </summary> public DateTime? LastLoginDate { get; set; } /// <summary> /// 年齡 (可空:未填寫時為 null) /// </summary> public Int32? Age { get; set; } /// <summary> /// 是否已審核 (可空:待審核時為 null) /// </summary> public Boolean? IsApproved { get; set; } ``` ### 4.4 集合屬性初始化 集合屬性**必須**在建構子中初始化,防止 `NullReferenceException`: ```csharp public class DtoOrder { /// <summary> /// 訂單明細列表 /// </summary> public List<DtoOrderDetail> Details { get; set; } /// <summary> /// 標籤列表 /// </summary> public List<String> Tags { get; set; } public DtoOrder() { // ✅ 必須初始化 Details = new List<DtoOrderDetail>(); Tags = new List<String>(); } } ``` --- ## 5. 欄位名稱常數 (選用) 當需要在多處引用欄位名稱時,可定義常數: ```csharp public class DtoPatient { #region ========== 屬性 ========== public String PatientId { get; set; } public String PatientName { get; set; } #endregion #region ========== 欄位名稱常數 ========== /// <summary> /// 資料庫欄位名稱:patient_id /// </summary> public const String ColumnPatientId = "patient_id"; /// <summary> /// 資料庫欄位名稱:patient_name /// </summary> public const String ColumnPatientName = "patient_name"; #endregion } ``` --- ## 6. 資料列映射方法 — `GetSingleData(DataRow)` ### 6.1 用途與定位 `GetSingleData(DataRow)` 是 DTO 的標準映射方法,由 **DAL 層的 `yield return` 迴圈**呼叫,將 `DataRow` 中的欄位值填充至 DTO 屬性。此方法是 DTO 映射模式 (`IEnumerable<T>` + `yield return`) 的核心搭配。 > **重要**:此方法**僅做欄位對應**,不包含任何業務邏輯或計算。 ### 6.2 標準實作範本 ```csharp /// <summary> /// 從 DataRow 載入單筆資料 /// </summary> /// <param name="dr">資料列</param> public void GetSingleData(DataRow dr) { if (dr != null) { // 字串欄位 — 檢查欄位存在 + IsNull if (dr.Table.Columns.Contains("serial_num") && !dr.IsNull("serial_num")) Serial_num = dr["serial_num"].ToString(); else Serial_num = null; // 可空 DateTime 欄位 if (dr.Table.Columns.Contains("connection_dt") && !dr.IsNull("connection_dt")) Connection_dt = Convert.ToDateTime(dr["connection_dt"].ToString()); else Connection_dt = null; // 可空 Byte/TinyInt 欄位 if (dr.Table.Columns.Contains("connection_count") && !dr.IsNull("connection_count")) Connection_count = Convert.ToByte(dr["connection_count"].ToString()); else Connection_count = null; // 不可空字串欄位 (有預設值) if (dr.Table.Columns.Contains("status") && !dr.IsNull("status")) Status = dr["status"].ToString(); else Status = String.Empty; } } ``` ### 6.3 NULL 防護規則 | 檢查方式 | 說明 | |:---------|:-----| | `dr.Table.Columns.Contains("col")` | 確認欄位存在(防止不同查詢的 SELECT 欄位不一致) | | `!dr.IsNull("col")` | 確認欄位值非 `DBNull`(等效於 `dr["col"] != DBNull.Value`) | > **專案慣例**:統一使用 `dr.IsNull("col")` 而非 `dr["col"] == DBNull.Value`。兩者等效,但前者為專案一致風格。 ### 6.4 型別轉換對照 | 資料庫型別 | DTO 屬性型別 | 轉換方式 | |:-----------|:------------|:---------| | `CHAR`, `VARCHAR` | `String` | `dr["col"].ToString()` | | `DATETIME` | `DateTime?` | `Convert.ToDateTime(dr["col"].ToString())` | | `TINYINT` | `Byte?` | `Convert.ToByte(dr["col"].ToString())` | | `INT` | `Int32?` | `Convert.ToInt32(dr["col"].ToString())` | | `NUMERIC` | `Decimal?` | `Convert.ToDecimal(dr["col"].ToString())` | ### 6.5 繼承與覆寫 當子 DTO 繼承父 DTO 時,可使用 `virtual` / `override` 擴展映射: ```csharp // 父 DTO public class DtoCase { public virtual void GetSingleData(DataRow dr) { if (dr != null) { // 映射共用欄位... } } } // 子 DTO — 覆寫並擴展 public class DtoCaseDetail : DtoCase { public override void GetSingleData(DataRow dr) { base.GetSingleData(dr); // 先呼叫父類別映射 if (dr != null) { // 映射子類別專有欄位... } } } ``` > **注意**: 此方法應保持簡單,僅做欄位對應。複雜的轉換邏輯應在 BLL 層處理。 --- ## 7. 禁止事項 (Anti-Patterns) ### 7.1 🚫 禁止包含業務邏輯 ```csharp // ❌ 禁止:計算屬性包含業務邏輯 public class DtoOrder { public Decimal UnitPrice { get; set; } public Int32 Quantity { get; set; } // ❌ 這是業務邏輯,應在 BLL 計算 public Decimal TotalPrice { get { return UnitPrice * Quantity * 1.05m; } // 含稅計算 } } // ✅ 正確:所有屬性都是純資料 public class DtoOrder { public Decimal UnitPrice { get; set; } public Int32 Quantity { get; set; } public Decimal TotalPrice { get; set; } // 由 BLL 計算後設值 } ``` ### 7.2 🚫 禁止包含資料存取 ```csharp // ❌ 禁止:DTO 不應知道如何存取資料庫 public class DtoEmployee { public String EmployeeId { get; set; } // ❌ 絕對禁止 public void Save() { DboEmployee dbo = new DboEmployee(); dbo.Update(this); } } ``` ### 7.3 🚫 禁止包含驗證邏輯 ```csharp // ❌ 禁止:驗證是 BLL 的職責 public class DtoUser { private String _email; public String Email { get { return _email; } set { // ❌ 驗證邏輯不應在 DTO if (!value.Contains("@")) { throw new ArgumentException("Invalid email"); } _email = value; } } } // ✅ 正確:純粹的屬性 public class DtoUser { public String Email { get; set; } } ``` ### 7.4 🚫 禁止繼承實體類別 ```csharp // ❌ 禁止:DTO 不應繼承資料庫實體 public class DtoEmployee : EmployeeEntity { } // ✅ 正確:獨立的 DTO 類別 public class DtoEmployee { // 定義所需的屬性 } ``` --- ## 8. 檔案組織規範 ### 8.1 檔案命名 | 規則 | 說明 | |:-----|:-----| | 單一實體 | 每個主要 DTO 一個檔案:`DtoPatient.cs` | | 相關群組 | 相關 DTO 可合併:`DtoOrder.cs` (含 `DtoOrder`, `DtoOrderDetail`, `DtoOrderSearch`) | | 共用 DTO | 通用物件獨立檔案:`DtoCommon.cs`, `DtoResult.cs` | ### 8.2 檔案結構範例 ``` App_Code/ └── Model/ ├── DtoPatient.cs # 病患相關 DTO ├── DtoEmployee.cs # 員工相關 DTO ├── DtoOrder.cs # 訂單相關 DTO (含 Detail, Search) ├── DtoNavbar.cs # 導覽列 DTO ├── DtoPermission.cs # 權限相關 DTO └── DtoCommon.cs # 共用 DTO (Result, PageInfo 等) ``` --- ## 9. 程式碼審查檢核表 在提交程式碼前,請確認以下項目: ### 命名規範 - [ ] 類別名稱符合 `Dto{Entity}{SubType}` 格式 - [ ] 類別名稱在專案中唯一 - [ ] 屬性名稱使用 PascalCase ### 屬性規範 - [ ] 所有屬性為 `public` 且有 `get; set;` - [ ] 可空欄位使用 Nullable 型別 (`DateTime?`, `Int32?`) - [ ] 集合屬性在建構子中初始化 - [ ] 使用 `String`, `Int32`, `Boolean` 而非 `string`, `int`, `bool` ### 零邏輯原則 - [ ] 無業務邏輯程式碼 - [ ] 無資料存取程式碼 - [ ] 無驗證邏輯程式碼 - [ ] 無計算屬性 (除非是純粹的格式轉換) ### GetSingleData 映射方法 - [ ] 方法簽章為 `public void GetSingleData(DataRow dr)` - [ ] 所有欄位讀取前先檢查 `dr.Table.Columns.Contains()` 與 `!dr.IsNull()` - [ ] 可空型別欄位在 `else` 分支賦值為 `null` - [ ] 若有繼承,使用 `virtual` / `override` 並呼叫 `base.GetSingleData(dr)` ### 文件化 - [ ] 類別有 XML 摘要註解 - [ ] 所有屬性有 XML 摘要註解 - [ ] 可空屬性在註解中說明何時為 null ### 序列化相容 - [ ] 無 private set 屬性 - [ ] 無唯讀屬性 - [ ] 有預設建構子 (無參數)
格式化預覽
工具與套用範圍
03
適用工具
GitHub Copilot
Codex
Claude Code
Cursor
Antigravity
套用範圍
整個專案
指定路徑
指定路徑模式
每行一筆。當次下載仍可覆寫,不會修改此正式設定。
工具專用補充
04
只填差異,不要複製整份共用內容。
GitHub Copilot
Codex
Claude Code
Cursor
Antigravity
修改說明
選填;會保存在這次建立的新版本中。
保存並建立新版本
取消