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,且同時具備 get 與 set:
// ✅ 正確
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:
/// <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 屬性
- [ ] 無唯讀屬性
- [ ] 有預設建構子 (無參數)