P/PromptTools
Instruction asset / dto-instructions

DTO.instructions.md

從 Google Drive .github/instructions/DTO.instructions.md 匯入

共用指令內容

使用中

---

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 不使用命名空間,類別名稱必須在全專案中唯一:


// ✅ 正確:具描述性的唯一名稱

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 結構


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 完整範例


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,且同時具備 getset


// ✅ 正確

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 |

| 日期 | DateTimeDateTime? | 可空日期使用 Nullable |

| 金額 | Decimal | 避免浮點數精度問題 |

| 集合 | List<T> | 不使用 IList<T>IEnumerable<T> |

4.3 可空型別 (Nullable)

當資料庫欄位允許 NULL 時,DTO 屬性必須宣告為 Nullable:


/// <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


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. 欄位名稱常數 (選用)

當需要在多處引用欄位名稱時,可定義常數:


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 標準實作範本


/// <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 擴展映射:


// 父 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 🚫 禁止包含業務邏輯


// ❌ 禁止:計算屬性包含業務邏輯

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 🚫 禁止包含資料存取


// ❌ 禁止:DTO 不應知道如何存取資料庫

public class DtoEmployee

{

    public String EmployeeId { get; set; }

    

    // ❌ 絕對禁止

    public void Save()

    {

        DboEmployee dbo = new DboEmployee();

        dbo.Update(this);

    }

}

7.3 🚫 禁止包含驗證邏輯


// ❌ 禁止:驗證是 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 🚫 禁止繼承實體類別


// ❌ 禁止: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 屬性
  • [ ] 無唯讀屬性
  • [ ] 有預設建構子 (無參數)

版本紀錄

1 個不可變版本
v1
從 Google Drive 初次匯入
2026/07/28 02:59:46