P/
PromptTools
選單
資產庫
指令組合
產生指令包
管理資料
Instruction asset
編輯一般指令
共用內容只保存一份;工具差異放在專用補充,下載時才組合。
基本資料
01
名稱
唯一代稱
小寫英文、數字與連字號;建立後仍可修改但不可重複。
簡短說明
主要分類
未分類
預設排序
標籤
尚無標籤,可先到「分類與標籤」建立。
共用 Markdown 與即時預覽
02
共用指令內容
--- 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`: ```csharp // Sybase ASE 資料庫 using CHMC.DB.AseClient; public class DboEmployee : DbHelperSQL { } // Microsoft SQL Server using CHMC.DB.SqlClient; internal partial class DboEmployeeMSSQL : DbHelperSQL { } ``` ### 2.3 標準類別結構 ```csharp 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` | **呼叫範例速查**: ```csharp // ✅ 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); ``` **❌ 以下呼叫方式全部禁止(會導致編譯錯誤)**: ```csharp // ❌ 只傳 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 — 動態條件查詢 ```csharp /// <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 — 固定條件查詢 ```csharp /// <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 風格) ```csharp /// <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 風格) ```csharp /// <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 字串串接風格(亦合法) ```csharp 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 方法範本 ```csharp /// <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 方法範本 ```csharp /// <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 方法範本 ```csharp /// <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 錯誤處理範例 ```csharp 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 🚫 禁止手動建立資料庫連線 ```csharp // ❌ 絕對禁止 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 中包含業務邏輯 ```csharp // ❌ 禁止:業務邏輯應在 BLL internal Boolean InsertIfValid(DtoUser user) { if (user.Age < 18) // 這是業務規則,不應在 DAL return false; // ... } // ✅ 正確:DAL 只負責資料操作 internal Boolean Insert(DtoUser user) { // 純粹的 INSERT 操作 } ``` ### 5.3 🚫 禁止字串串接 SQL 參數 ```csharp // ❌ 嚴重安全漏洞 - 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()` 負責。 ```csharp // ❌ 禁止:在 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 查詢範例 ```csharp // ❌ 禁止: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 交易處理範例 ```csharp // ✅ 正確:使用 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 轉換: ```csharp internal DataSet SelectById(String id) { // ... 查詢邏輯 dsReturn = base.FillDataSet(strSQL, CommandType.Text, parameters, TableName); // 處理 Sybase 編碼轉換 Utility.CP850TransUnicode(dsReturn, TableName); return dsReturn; } ``` --- ## 9. 存取修飾詞規範 | 成員類型 | 修飾詞 | 說明 | |:---------|:-------|:-----| | 類別 | `public` 或 `internal` | 依可見範圍需求決定 | | 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_m` 無 `sort_order`、`is_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 中未列出的欄位
格式化預覽
工具與套用範圍
03
適用工具
GitHub Copilot
Codex
Claude Code
Cursor
Antigravity
套用範圍
整個專案
指定路徑
指定路徑模式
每行一筆。當次下載仍可覆寫,不會修改此正式設定。
工具專用補充
04
只填差異,不要複製整份共用內容。
GitHub Copilot
Codex
Claude Code
Cursor
Antigravity
修改說明
選填;會保存在這次建立的新版本中。
保存並建立新版本
取消