P/PromptTools
Instruction asset / dal-instructions

DAL.instructions.md

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

共用指令內容

使用中

---

applyTo: 'App_Code/DAL/**/*.cs'

---

DAL (Data Access Layer) 開發規範

> 文件版本: 1.0.0

> 最後更新: 2026-01-26

> 適用範圍: App_Code/DAL/**/*.cs

---

1. 概述

1.1 層級定位

DAL (資料存取層) 為三層式架構中的最底層,唯一職責是與資料庫進行互動。DAL 必須完全隔離資料庫實作細節,使上層 (BLL) 無需關心底層資料庫類型。

1.2 設計原則

| 原則 | 說明 |

|:-----|:-----|

| 單一職責 | 僅負責 CRUD 操作與資料列映射,禁止包含業務邏輯 |

| 資料庫無關性 | 透過 DbHelperSQL 抽象層操作,禁止直接建立連線 |

| 參數化查詢 | 所有 SQL 必須使用參數化,防止 SQL Injection |

| 資源管理 | 實作 Dispose 模式,確保資源正確釋放 |

1.3 回傳型別策略

DAL 支援兩種 SELECT 回傳模式,依據使用情境選擇:

| 模式 | 回傳型別 | 適用情境 | BLL 端處理 |

|:-----|:---------|:---------|:-----------|

| DTO 映射模式 (主流) | IEnumerable<DtoXxx> | 一般業務 CRUD 查詢;查詢結果需轉 JSON 回傳前端 | BLL 呼叫 .ToList() 取得 List<DtoXxx> |

| DataSet 直通模式 | DataSet | 系統基礎設施查詢 (權限、員工);需跨層 DataRow 直接操作;報表 / Excel 匯出用途 | BLL 自行遍歷 DataRow 做映射或直接操作 |

> 決策原則:優先使用 DTO 映射模式。僅在資料結構不固定、多用途查詢、或系統基礎設施類別時使用 DataSet 直通模式。

---

2. 類別結構規範

2.1 命名規則

| 項目 | 規則 | 範例 |

|:-----|:-----|:-----|

| 類別名稱 | Dbo{實體名稱}Dbo{實體名稱}{資料庫類型} | DboEmployee, DboEmployeeMSSQL |

| 資料表常數 | TableName (internal const) | internal const String TableName = "employee"; |

| 方法名稱 | 動詞 + 名詞,使用 PascalCase | SelectById, Insert, Update, Delete |

2.2 必要繼承

所有 DAL 類別必須繼承自 CHMC.DB 命名空間下的 DbHelperSQL


// Sybase ASE 資料庫

using CHMC.DB.AseClient;

public class DboEmployee : DbHelperSQL { }



// Microsoft SQL Server

using CHMC.DB.SqlClient;

internal partial class DboEmployeeMSSQL : DbHelperSQL { }

2.3 標準類別結構


using System;

using System.Collections.Generic;

using System.Data;

using System.Text;

using CHMC;

using CHMC.DB.AseClient;  // 或 CHMC.DB.SqlClient

using Sybase.Data.AseClient;  // 或 System.Data.SqlClient



/// <summary>

/// [資料表描述] 資料存取類別

/// </summary>

public class Dbo{EntityName} : DbHelperSQL

{

    #region ========== 常數與欄位 ==========

    

    /// <summary>

    /// 資料表名稱

    /// </summary>

    internal const String TableName = "{table_name}";

    

    // 私有欄位 - 統一宣告於此

    private DataSet dsReturn = null;

    private StringBuilder sbSQLCriteria = null;

    private List<AseParameter> param = null;  // 或 List<SqlParameter>

    private AseParameter[] parameters = null;  // 或 SqlParameter[]

    private String strSQL = String.Empty;

    private String strSQLCriteria = String.Empty;

    

    #endregion

    

    #region ========== 建構子 ==========

    

    /// <summary>

    /// 預設建構子

    /// </summary>

    internal Dbo{EntityName}()

    { }

    

    /// <summary>

    /// 建構子 - 接收連線字串

    /// </summary>

    /// <param name="connectionString">資料庫連線字串</param>

    internal Dbo{EntityName}(String connectionString)

        : base(connectionString)

    { }

    

    /// <summary>

    /// 建構子 - 接收交易物件 (用於跨表交易)

    /// </summary>

    /// <param name="tran">交易物件</param>

    internal Dbo{EntityName}(DbTransaction tran)

        : base(tran)

    { }

    

    #endregion

    

    #region ========== 解構與資源釋放 ==========

    

    /// <summary>

    /// 解構子

    /// </summary>

    ~Dbo{EntityName}()

    {

        Dispose(false);

    }

    

    /// <summary>

    /// 釋放資源

    /// </summary>

    /// <remarks>

    /// ⚠ 重要:此方法使用 <c>new</c> 隱藏父類別方法(非 override),

    /// 因此 BLL 層必須用 try/finally + 明確呼叫 dal.Dispose(),

    /// 禁止用 using 語句(using 透過 IDisposable 介面只呼叫父類別 Dispose,

    /// 子類別的 DataSet / param 清理邏輯永遠不會執行)。

    /// </remarks>

    public new void Dispose()

    {

        base.Dispose();

        

        if (dsReturn != null)

        {

            dsReturn.Dispose();

            dsReturn = null;

        }

        

        if (sbSQLCriteria != null)

        {

            sbSQLCriteria.Clear();

            sbSQLCriteria = null;

        }

        

        if (param != null)

        {

            param.Clear();

            param = null;

        }

        

        parameters = null;

    }

    

    #endregion

    

    #region ========== 公開方法 ==========

    

    // CRUD 方法實作於此區塊

    

    #endregion

    

    #region ========== 私有方法 ==========

    

    // 輔助方法實作於此區塊

    

    #endregion

}

---

3. 方法實作規範

3.1 DbHelperSQL 標準方法

> ⛔ 嚴重警告DbHelperSQL 的方法不接受省略參數的呼叫。以下簽章是唯一合法的呼叫方式,少傳或多傳任何參數都會導致 CS1501 / CS7036 編譯錯誤。

| 方法 | 完整方法簽章 | 用途 | 回傳值 |

|:-----|:-----|:-----|:-----|

| FillDataSet | base.FillDataSet(string strQuery, CommandType execCommandType, IDataParameter[] parameters, string tableName) | SELECT 查詢 | DataSet |

| ExecuteNonQuery | base.ExecuteNonQuery(string strQuery, CommandType execCommandType, IDataParameter[] parameters) | INSERT / UPDATE / DELETE | int (影響筆數) |

| ExecuteScalar | base.ExecuteScalar(string strQuery, CommandType execCommandType, IDataParameter[] parameters) | 取得單一值 (COUNT, MAX 等) | object |

呼叫範例速查


// ✅ SELECT 無參數查詢 — parameters 傳 null,tableName 傳 TableName

dsReturn = base.FillDataSet(sbSQLCriteria.ToString(), CommandType.Text, null, TableName);



// ✅ SELECT 有參數查詢

dsReturn = base.FillDataSet(sbSQLCriteria.ToString(), CommandType.Text, parameters, TableName);



// ✅ INSERT / UPDATE / DELETE

return base.ExecuteNonQuery(sbSQLCriteria.ToString(), CommandType.Text, parameters);

❌ 以下呼叫方式全部禁止(會導致編譯錯誤)


// ❌ 只傳 1 個參數 → CS1501 錯誤

base.FillDataSet(sbSQLCriteria.ToString());



// ❌ 只傳 2 個參數 (sql + parameters) → CS1501 錯誤

base.FillDataSet(sbSQLCriteria.ToString(), parameters);



// ❌ 只傳 2 個參數 (sql + parameters) → CS7036 錯誤

base.ExecuteNonQuery(sbSQLCriteria.ToString(), parameters);

3.2 SELECT 方法範本 — DTO 映射模式 (主流)

> 此模式透過 IEnumerable<T> + yield return 延遲列舉,搭配 DTO 的 GetSingleData(DataRow) 方法完成資料列映射。此為專案中最常使用的查詢回傳模式。

#### 範本 A:IEnumerable + yield return — 動態條件查詢


/// <summary>

/// 查詢連絡記錄 (動態條件)

/// </summary>

/// <param name="dto">查詢條件 DTO</param>

/// <returns>符合條件的記錄列舉</returns>

internal IEnumerable<DtoContactRecord> RetrieveContactRecord(DtoContactRecord dto)

{

    sbSQLCriteria = new StringBuilder();

    param = new List<AseParameter>();



    // 動態組合 WHERE 條件 (前置 AND 風格)

    if (!String.IsNullOrEmpty(dto.Serial_num))

    {

        sbSQLCriteria.Append(" AND serial_num = @serial_num");

        param.Add(new AseParameter("@serial_num", AseDbType.Char));

        param[param.Count - 1].Value = dto.Serial_num;

    }



    if (!String.IsNullOrEmpty(dto.Citizen_id))

    {

        sbSQLCriteria.Append(" AND citizen_id = @citizen_id");

        param.Add(new AseParameter("@citizen_id", AseDbType.VarChar));

        param[param.Count - 1].Value = dto.Citizen_id;

    }



    // 處理 WHERE 子句 (移除開頭的 AND)

    if (sbSQLCriteria.Length > 0)

    {

        strSQLCriteria = " WHERE " + sbSQLCriteria.ToString().Substring(4);

        strSQLCriteria += " ORDER BY sort_order ";

    }



    parameters = param.ToArray();



    strSQL = "SELECT col1, col2, col3 FROM " + TableName + strSQLCriteria;



    // ✅ FillDataSet 完整 4 參數呼叫

    dsReturn = base.FillDataSet(strSQL, CommandType.Text, parameters, TableName);

    

    // ✅ Sybase CP850 編碼轉換 (中文欄位必加)

    Utility.CP850TransUnicode(dsReturn, TableName);

    

    // ✅ 透過 DTO.GetSingleData() 映射並 yield return

    foreach (DataRow dr in dsReturn.Tables[TableName].Rows)

    {

        DtoContactRecord item = new DtoContactRecord();

        item.GetSingleData(dr);

        yield return item;

    }

}

#### 範本 B:IEnumerable + yield return — 固定條件查詢


/// <summary>

/// 依主鍵查詢單筆資料

/// </summary>

/// <param name="id">主鍵值</param>

/// <returns>查詢結果列舉 (0 或 1 筆)</returns>

internal IEnumerable<DtoEntity> RetrieveById(String id)

{

    sbSQLCriteria = new StringBuilder();

    param = new List<AseParameter>();



    sbSQLCriteria.Append(" AND id = @Id");

    param.Add(new AseParameter("@Id", AseDbType.VarChar));

    param[param.Count - 1].Value = id;



    if (sbSQLCriteria.Length > 0)

    {

        strSQLCriteria = " WHERE " + sbSQLCriteria.ToString().Substring(4);

    }



    parameters = param.ToArray();

    strSQL = "SELECT col1, col2, col3 FROM " + TableName + strSQLCriteria;



    dsReturn = base.FillDataSet(strSQL, CommandType.Text, parameters, TableName);

    Utility.CP850TransUnicode(dsReturn, TableName);



    foreach (DataRow dr in dsReturn.Tables[TableName].Rows)

    {

        DtoEntity item = new DtoEntity();

        item.GetSingleData(dr);

        yield return item;

    }

}

> 重要:使用 yield return 的方法不可包覆 try-catch,因為 yield return 不允許出現在 try-catch 區塊中。例外處理由 BLL 層的 try-catch 負責捕獲。

---

3.3 SELECT 方法範本 — DataSet 直通模式

> 此模式適用於系統基礎設施查詢 (權限、員工)、報表匯出、或查詢結構不固定的場景。

#### 範本 A:無參數查詢(使用 StringBuilder.Append 風格)


/// <summary>

/// 取得所有上架中的資料(前台用)

/// </summary>

/// <returns>查詢結果 DataSet</returns>

internal DataSet SelectActive()

{

    sbSQLCriteria = new StringBuilder();

    sbSQLCriteria.Append("SELECT col1, col2, col3 ");

    sbSQLCriteria.AppendFormat("FROM {0} ", TableName);

    sbSQLCriteria.Append("WHERE is_active = 'Y' ");

    sbSQLCriteria.Append("ORDER BY sort_order ASC ");



    // ✅ 無參數時 parameters 傳 null,最後一個參數必須傳 TableName

    dsReturn = base.FillDataSet(sbSQLCriteria.ToString(), CommandType.Text, null, TableName);

    return dsReturn;

}

#### 範本 B:有參數查詢(使用 StringBuilder.Append 風格)


/// <summary>

/// 依主鍵查詢單筆資料

/// </summary>

/// <param name="id">主鍵值</param>

/// <returns>查詢結果 DataSet,失敗時回傳 null</returns>

internal DataSet SelectById(Int32 id)

{

    sbSQLCriteria = new StringBuilder();

    sbSQLCriteria.Append("SELECT col1, col2, col3 ");

    sbSQLCriteria.AppendFormat("FROM {0} ", TableName);

    sbSQLCriteria.Append("WHERE id = @Id ");



    param = new List<AseParameter>();

    param.Add(new AseParameter("@Id", AseDbType.Integer) { Value = id });

    parameters = param.ToArray();



    // ✅ 完整 4 參數呼叫

    dsReturn = base.FillDataSet(sbSQLCriteria.ToString(), CommandType.Text, parameters, TableName);

    return dsReturn;

}

#### 範本 C:傳統 strSQL 字串串接風格(亦合法)


internal DataSet SelectById(String id)

{

    sbSQLCriteria = new StringBuilder();

    param = new List<AseParameter>();

    

    // 組合 WHERE 條件

    sbSQLCriteria.Append(" AND column_name = @ParamName");

    param.Add(new AseParameter("@ParamName", AseDbType.VarChar));

    param[param.Count - 1].Value = id;

    

    // 處理 WHERE 子句 (移除開頭的 AND)

    if (sbSQLCriteria.Length > 0)

    {

        strSQLCriteria = " WHERE " + sbSQLCriteria.ToString().Substring(5);

    }

    

    parameters = param.ToArray();

    strSQL = "SELECT column1, column2, column3 FROM " + TableName + strSQLCriteria;

    

    try

    {

        dsReturn = base.FillDataSet(strSQL, CommandType.Text, parameters, TableName);

        return dsReturn;

    }

    catch (Exception ex)

    {

        Log.WriteLog(ex);

        return null;

    }

}

3.4 INSERT 方法範本


/// <summary>

/// 新增一筆資料

/// </summary>

/// <param name="dto">資料傳輸物件</param>

/// <returns>成功回傳 true,失敗回傳 false</returns>

internal Boolean Insert(DtoEntity dto)

{

    param = new List<AseParameter>();

    

    param.Add(new AseParameter("@Column1", AseDbType.VarChar));

    param[param.Count - 1].Value = dto.Column1;

    

    param.Add(new AseParameter("@Column2", AseDbType.Integer));

    param[param.Count - 1].Value = dto.Column2;

    

    parameters = param.ToArray();

    

    strSQL = "INSERT INTO " + TableName + " (column1, column2) "

           + "VALUES (@Column1, @Column2)";

    

    try

    {

        return base.ExecuteNonQuery(strSQL, CommandType.Text, parameters) > 0;

    }

    catch (Exception ex)

    {

        Log.WriteLog(ex);

        return false;

    }

}

3.5 UPDATE 方法範本


/// <summary>

/// 更新一筆資料

/// </summary>

/// <param name="dto">資料傳輸物件</param>

/// <returns>成功回傳 true,失敗回傳 false</returns>

internal Boolean Update(DtoEntity dto)

{

    param = new List<AseParameter>();

    

    // SET 欄位

    param.Add(new AseParameter("@Column1", AseDbType.VarChar));

    param[param.Count - 1].Value = dto.Column1;

    

    // WHERE 條件 (主鍵)

    param.Add(new AseParameter("@Id", AseDbType.Integer));

    param[param.Count - 1].Value = dto.Id;

    

    parameters = param.ToArray();

    

    strSQL = "UPDATE " + TableName + " SET column1 = @Column1 WHERE id = @Id";

    

    try

    {

        return base.ExecuteNonQuery(strSQL, CommandType.Text, parameters) > 0;

    }

    catch (Exception ex)

    {

        Log.WriteLog(ex);

        return false;

    }

}

3.6 DELETE 方法範本


/// <summary>

/// 刪除一筆資料

/// </summary>

/// <param name="id">主鍵值</param>

/// <returns>成功回傳 true,失敗回傳 false</returns>

internal Boolean Delete(Int32 id)

{

    param = new List<AseParameter>();

    

    param.Add(new AseParameter("@Id", AseDbType.Integer));

    param[param.Count - 1].Value = id;

    

    parameters = param.ToArray();

    

    strSQL = "DELETE FROM " + TableName + " WHERE id = @Id";

    

    try

    {

        return base.ExecuteNonQuery(strSQL, CommandType.Text, parameters) > 0;

    }

    catch (Exception ex)

    {

        Log.WriteLog(ex);

        return false;

    }

}

---

4. 錯誤處理規範

4.1 例外處理原則

| 規則 | 說明 |

|:-----|:-----|

| 必須捕獲 | 所有對 base 方法的呼叫必須包覆於 try-catch |

| 必須記錄 | 使用 Log.WriteLog(ex) 記錄所有例外 |

| 安全回傳 | 查詢失敗回傳 null,異動失敗回傳 false-1 |

| 禁止吞掉 | 禁止空的 catch 區塊 |

4.2 錯誤處理範例


internal DataSet SelectAll()

{

    strSQL = "SELECT * FROM " + TableName;

    

    try

    {

        dsReturn = base.FillDataSet(strSQL, CommandType.Text, null, TableName);

        return dsReturn;

    }

    catch (Exception ex)

    {

        // ✅ 正確:記錄錯誤並回傳安全值

        Log.WriteLog(ex);

        return null;

    }

}

---

5. 禁止事項 (Anti-Patterns)

5.1 🚫 禁止手動建立資料庫連線


// ❌ 絕對禁止

using (AseConnection conn = new AseConnection(connectionString))

{

    conn.Open();

    AseCommand cmd = new AseCommand(sql, conn);

    // ...

}



// ✅ 必須使用 base 方法

dsReturn = base.FillDataSet(strSQL, CommandType.Text, parameters, TableName);

5.2 🚫 禁止在 DAL 中包含業務邏輯


// ❌ 禁止:業務邏輯應在 BLL

internal Boolean InsertIfValid(DtoUser user)

{

    if (user.Age < 18)  // 這是業務規則,不應在 DAL

        return false;

    // ...

}



// ✅ 正確:DAL 只負責資料操作

internal Boolean Insert(DtoUser user)

{

    // 純粹的 INSERT 操作

}

5.3 🚫 禁止字串串接 SQL 參數


// ❌ 嚴重安全漏洞 - SQL Injection

strSQL = "SELECT * FROM users WHERE id = '" + userId + "'";



// ✅ 必須使用參數化查詢

param.Add(new AseParameter("@UserId", AseDbType.VarChar));

param[param.Count - 1].Value = userId;

strSQL = "SELECT * FROM users WHERE id = @UserId";

5.4 🚫 禁止在 DAL 中回傳 List<Dto>

> DAL 允許透過 IEnumerable<T> + yield return 逐列映射 DTO(主流模式),但禁止在 DAL 內自行將整個 DataSet 轉換為 List<Dto> 後回傳。List<T> 的實體化由 BLL 呼叫 .ToList() 負責。


// ❌ 禁止:在 DAL 內部 new List 並回傳

internal List<DtoUser> SelectAllUsers()

{

    List<DtoUser> list = new List<DtoUser>();

    // ... 手動逐列 Add 到 list

    return list;

}



// ✅ 正確(主流):使用 yield return 回傳 IEnumerable

internal IEnumerable<DtoUser> SelectAllUsers()

{

    // ... FillDataSet + CP850 轉換

    foreach (DataRow dr in dsReturn.Tables[TableName].Rows)

    {

        DtoUser item = new DtoUser();

        item.GetSingleData(dr);

        yield return item;

    }

}



// ✅ 正確(基礎設施):直接回傳 DataSet

internal DataSet SelectAllUsers()

{

    // ...

    return dsReturn;

}

---

6. SQL 查詢設計規範

6.1 必要限制

| 規則 | 說明 |

|:-----|:-----|

| 二段式命名 | 使用 表名.欄位名別名.欄位名 格式;有 JOIN 關係時,欄位必須指明所屬的表 |

| WHERE 條件排序 | WHERE 條件設計應將索引欄位值擺前頭,辨識度越高越前 |

6.2 效能建議

| 建議 | 說明 |

|:-----|:-----|

| 索引欄位避免函式 | 避免在索引欄位上使用函式或計算 |

| 避免前置萬用字元 | 避免在 LIKE 條件前使用萬用字元(如 LIKE '%keyword') |

6.3 SQL 查詢範例


// ❌ 禁止:JOIN 時未指明欄位所屬表

strSQL = "SELECT order_id, customer_name FROM orders o JOIN customers c ON o.customer_id = c.id";



// ✅ 正確:二段式命名、表別名指明欄位來源

strSQL = "SELECT o.order_id, o.order_date, c.customer_name "

       + "FROM orders o "

       + "JOIN customers c ON o.customer_id = c.id "

       + "WHERE o.status = @Status";

---

7. 交易處理規範

7.1 交易設計原則

| 規則 | 說明 |

|:-----|:-----|

| 最小範圍 | 僅在需要更新時才開始交易,減少資源佔用時間 |

| 異常 Rollback | 應用程式端必須做好例外處理,確保異常時執行 Rollback |

| 避免大交易 | 禁止在單一交易中包含大範圍操作 |

| 限制 JOIN | 避免在交易中使用過多 JOIN |

| 分層設計 | 將資料庫操作分為上層邏輯與底層存取,提升效率 |

7.2 交易處理範例


// ✅ 正確:使用 DbTransaction 進行跨表交易

DbTransaction tran = null;

try

{

    tran = base.BeginTransaction();

    

    DboOrder dboOrder = new DboOrder(tran);

    DboOrderDetail dboDetail = new DboOrderDetail(tran);

    

    dboOrder.Insert(orderDto);

    dboDetail.Insert(detailDto);

    

    tran.Commit();

}

catch (Exception ex)

{

    if (tran != null)

        tran.Rollback();

    Log.WriteLog(ex);

    throw;

}

---

8. 編碼轉換處理

8.1 Sybase CP850 編碼處理

從 Sybase 讀取中文資料時,需進行 CP850 → Unicode 轉換:


internal DataSet SelectById(String id)

{

    // ... 查詢邏輯

    

    dsReturn = base.FillDataSet(strSQL, CommandType.Text, parameters, TableName);

    

    // 處理 Sybase 編碼轉換

    Utility.CP850TransUnicode(dsReturn, TableName);

    

    return dsReturn;

}

---

9. 存取修飾詞規範

| 成員類型 | 修飾詞 | 說明 |

|:---------|:-------|:-----|

| 類別 | publicinternal | 依可見範圍需求決定 |

| TableName 常數 | internal const | 允許 BLL 存取表名 |

| 建構子 | internal | 僅允許同組件存取 |

| CRUD 方法 | internal | 僅允許 BLL 呼叫 |

| 私有欄位 | private | 類別內部使用 |

---

10. 程式碼審查檢核表

在提交程式碼前,請確認以下項目:

  • [ ] 類別繼承自 DbHelperSQL
  • [ ] 類別名稱符合 Dbo{EntityName} 格式
  • [ ] 提供接收 connectionString 的建構子
  • [ ] 實作 Dispose() 方法並釋放所有資源
  • [ ] 所有 SQL 使用參數化查詢
  • [ ] 所有 base 方法呼叫包覆於 try-catch
  • [ ] catch 區塊使用 Log.WriteLog(ex) 記錄錯誤
  • [ ] 方法回傳值符合規範 (失敗時回傳 null / false / -1)
  • [ ] 無手動建立資料庫連線的程式碼
  • [ ] 無業務邏輯程式碼
  • [ ] 所有公開方法皆有 XML 註解

---

11. ⚠️ SQL 欄位名稱必須與 data-model.md 完全一致

> 教訓來源:2026-04 實際踩坑

> ewcs_policies_m 的標題欄位是 policy_name,但 DAL 寫成 title → 執行時 Invalid column name 'title'

> ewcs_financial_rpts_msort_orderis_active 欄位,但 DAL 包含這兩欄 → INSERT/UPDATE 報錯。

11.1 強制執行原則

撰寫任何 SELECT / INSERT / UPDATE SQL 之前必須先對照

specs/002-ewcs-welfare-portal/data-model.md 確認每個欄位的實際名稱與是否存在。

11.2 本專案常見陷阱(非通用名稱的欄位)

| 資料表 | ⚠️ 正確欄位名稱 | ❌ 容易誤用的錯誤名稱 |

|:---|:---|:---|

| ewcs_policies_m | policy_name | title, name |

| ewcs_download_forms_m | form_name | title, name |

| ewcs_financial_rpts_m | report_name | title, name |

| ewcs_regulations_m | reg_title | title |

11.3 本專案欄位「不存在」的常見陷阱

| 資料表 | 不存在的欄位(禁止使用) |

|:---|:---|

| ewcs_regulations_m | sort_order |

| ewcs_financial_rpts_m | sort_order, is_active |

| ewcs_album_photos_m | is_active |

11.4 NOT NULL 欄位必須包含於 INSERT

> 若 DB 欄位宣告為 NOT NULL 且無 DEFAULT 值,INSERT 時不提供該欄位會導致「Attempt to insert NULL value into column」錯誤。

| 資料表 | 必含於 INSERT 的 NOT NULL 欄位 |

|:---|:---|

| ewcs_regulations_m | update_dt(需傳入日期或使用 GETDATE()) |

| ewcs_download_forms_m | update_dt(需傳入日期或使用 GETDATE()) |

11.5 撰寫新 DAL 前的欄位核對清單

  • [ ] SELECT 清單中的每個欄位名稱與 data-model.md 完全一致
  • [ ] INSERT 包含所有 NOT NULL 欄位(包括 update_dt 等輔助時間欄)
  • [ ] UPDATE SET 子句使用正確欄位名稱
  • [ ] DtoXxx.GetSingleData()dr["欄位名"] 與實際 DB 欄位名稱一致
  • [ ] 不使用 data-model.md 中未列出的欄位

版本紀錄

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