本專案為針對企業內部進銷存作業開發之桌面端 ERP 系統。系統採用三層式架構 (3-Tier Architecture) 進行模組化開發,並以 ADO.NET 作為底層資料存取技術,旨在提供穩定的資料庫 I/O 效能、多使用者併發控制,以及嚴謹的資料完整性檢核。
- IDE: Visual Studio 2022 (支援 .NET 8.0)
- Database: Microsoft SQL Server 2022 (Developer / Express 均可)
請透過 SSMS 依序執行 Database/ 目錄下的 SQL 腳本:
01_Schema.sql:建立資料表與 UDTT (使用者自訂資料表型別)。02_Constraints.sql:建立外部鍵 (FK)、CHECK 約束與過濾唯一索引。03_Programmability.sql:建立觸發程序 (Triggers) 與檢視表。04_SeedData.sql:寫入地理字典、RBAC 權限矩陣與系統預設帳號。
編譯並啟動 ERPLAB.UI 專案後,請使用以下預設帳號登入進行測試:
- ADMIN: 帳號 [
user26006/user26007] / 密碼 [1234] - DVANCED_USER: 帳號 [
user26001/user26010] / 密碼 [1234] - GENERAL_USER: 帳號 [
user26009] / 密碼 [1234]
本系統嚴格落實專案參考隔離,確保各層職責單一化:
🔀點擊以展開/摺疊架構圖
graph TD
UI[ERPLAB.UI <br> 表現層] -->|Method Call| BLL
BLL[ERPLAB.BLL <br> 商業邏輯層] -->|Delegate Execution| DAL
DAL[ERPLAB.DataAccess <br> 資料存取層] -->|TDS Protocol| DB[(SQL Server)]
UI -.-> Models[ERPLAB.Models <br> 領域模型與 DTO]
BLL -.-> Models
DAL -.-> Models
- ERPLAB.UI:負責視窗生命週期、狀態機控制與資料雙向綁定。不具備資料庫存取套件參考。
- ERPLAB.BLL:負責跨模組商業規則運算、單號配發機制與狀態流轉檢核。
- ERPLAB.DataAccess:負責 SQL 參數化封裝、批次寫入與非同步資料庫 I/O。
系統資料庫依據業務領域劃分為三大核心模型。詳細圖解請參閱 docs/diagrams/ 目錄:
-
RBAC 權限與資安模型: 採多對多關聯設計。將
Accounts與Employee實體分離,並透過SystemNodes與Permissions表建立動態選單路由機制。🔀點擊以展開/摺疊 ER 圖
LoadingerDiagram %% ========================================================= %% 關聯定義 (嚴守 IE Notation) %% ========================================================= Employee ||..o| Accounts : "(ON DELETE NO ACTION)" Accounts ||--o{ UserRoles : "(ON DELETE NO ACTION)" Roles ||--o{ UserRoles : "(ON DELETE NO ACTION)" Roles ||--o{ RolePermissions : "(ON DELETE NO ACTION)" Permissions ||--o{ RolePermissions : "(ON DELETE CASCADE)" Permissions |o..o{ SystemNodes : "(ON DELETE NO ACTION) (ON UPDATE CASCADE)" SystemNodes |o..o{ SystemNodes : "(ON DELETE NO ACTION)" %% ========================================================= %% 實體定義 %% ========================================================= Accounts { AccountID int PK EmployeeID int FK Username varchar(50) UK "過濾唯一索引 (UK)" PasswordHash varchar(255) "Identity V3 封裝" IsLocked bit "防暴力破解鎖定" FailedCount tinyint IsActive bit "軟刪除防線" RowVersion timestamp "樂觀鎖" other 其它欄位 } Roles { RoleID int PK RoleCode varchar(50) UK IsSystem bit "系統內建防護" IsActive bit "暫時隔離用" other 其它欄位 } Permissions { PermissionCode varchar(100) PK "自然主鍵" PermissionName nvarchar(50) IsActive bit "暫時隔離用" } UserRoles { AccountID int PK, FK "複合主鍵" RoleID int PK, FK "複合主鍵" other 其它欄位 } RolePermissions { RoleID int PK, FK "複合主鍵" PermissionCode varchar(100) PK, FK "複合主鍵" other 其它欄位 } SystemNodes { NodeID int PK NodeType tinyint ParentNodeID int FK "自我參照 (樹狀結構)" FormClassPath varchar(255) "UI 反射路由字串" PermissionCode varchar(100) FK "允許 NULL (目錄節點)" IsActive bit "暫時隔離用" other 其它欄位 } Employee { EmployeeID int PK EmployeeNo varchar(20) UK JobStatus tinyint "人事狀態" IsActive bit "軟刪除防線" RowVersion timestamp "樂觀鎖" other 其它欄位 } -
進銷存主明細交易模型: 實作 Master-Detail 架構。單據過帳後受
INSTEAD OF DELETE觸發程序保護;庫存異動採用差異沖平演算法確保帳實一致。 此 Master-Detail 資料處理架構與 TVP 批次寫入機制已具備模組化特性,並同步實作於進貨單 (PurchaseMaster/Detail) 與庫存盤點單 (InventoryMaster/Detail) 等核心業務模組中。🔀點擊以展開/摺疊 ER 圖
LoadingerDiagram %% ========================================================= %% 關聯定義 (全數為非識別性弱關聯) %% ========================================================= Customer ||..o{ SalesMaster : "(ON DELETE NO ACTION)" Base_District ||..o{ SalesMaster : "(ON DELETE NO ACTION)" SalesMaster ||..|{ SalesDetail : "(ON DELETE NO ACTION)" Product ||..o{ SalesDetail : "(ON DELETE NO ACTION)" %% ========================================================= %% 實體定義 (AutoNumber 無實體 FK,故獨立展示) %% ========================================================= Customer { CustomerID int PK CustomerNo varchar(20) UK "單據號碼 (來自 AutoNumber)" CustomerName nvarchar(50) IsActive bit "軟刪除防線" RowVersion timestamp "樂觀鎖" other 其它欄位 } SalesMaster { SalesID bigint PK SalesNo varchar(20) UK "單據號碼 (來自 AutoNumber)" CustomerID int FK ShipDistrictID int FK ShipZipCode varchar(6) "歷史郵遞區號(快照)" ShipAddress nvarchar(200) "歷史出貨地址(快照)" TotalAmount decimal(18,2) "總計 (反正規化快取)" Status tinyint "4維狀態機: 1=草稿, 2=過帳..." RowVersion timestamp "樂觀鎖" other 其它欄位 } SalesDetail { SalesDID bigint PK "非叢集主鍵" SalesID bigint FK "複合叢集索引 (與 LineNo 綁定)" LineNo int "明細行號" ProductID int FK "關聯商品" UnitPrice decimal(18,2) "歷史單價 (快照)" UnitCost decimal(18,2) "歷史成本快照" Qty int other 其它欄位 } Product { ProductID int PK ProductNo varchar(20) UK "單據號碼 (來自 AutoNumber)" ProductName nvarchar(100) CurrentStock int "實體庫存 (受 CHECK 約束)" IsActive bit "軟刪除防線" RowVersion timestamp "樂觀鎖" MovingAverageCost decimal "移動加權平均成本" other 其它欄位 } AutoNumber { DocType varchar PK "單據類型 (獨立微交易取號)" CurrentDate DATE "當前日期 (滾動重置)" LastSeq int "最後流水號" } -
基礎主檔與地理連動模型: 實作
IsActive軟刪除機制,並與Base_City、Base_District建立實體外鍵約束,維持歷史單據的參照完整性。🔀點擊以展開/摺疊 ER 圖
LoadingerDiagram %% ========================================================= %% 關聯定義 (全數為非識別性弱關聯) %% ========================================================= Base_City ||..o{ Base_District : "ON DELETE NO ACTION" Base_District ||..o{ Customer : "ON DELETE NO ACTION" Base_District ||..o{ Vendor : "ON DELETE NO ACTION" Base_District ||..o{ Employee : "ON DELETE NO ACTION" %% ========================================================= %% 實體定義 %% ========================================================= Base_City { CityID int PK CityNo varchar(10) UK "政府官方代碼" CityName nvarchar(20) SortSeq int "UI 渲染權重" IsActive bit "軟刪除防線" } Base_District { DistrictID int PK CityID int FK ZipCode varchar(3) "精確 3 碼" DistrictName nvarchar(20) SortSeq int "UI 渲染權重" IsActive bit "軟刪除防線" } Customer { CustomerID int PK CustomerNo varchar(20) UK DistrictID int FK "地理強外鍵" CustomZipCode varchar(6) "3+3 碼後綴" Address nvarchar(200) "街道門牌" other 其它欄位 } Vendor { VendorID int PK VendorNo varchar(20) UK DistrictID int FK "地理強外鍵" CustomZipCode varchar(6) "3+3 碼後綴" Address nvarchar(200) "街道門牌" other 其它欄位 } Employee { EmployeeID int PK EmployeeNo varchar(20) UK DistrictID int FK "地理強外鍵" CustomZipCode varchar(6) "3+3 碼後綴" Address nvarchar(200) "街道門牌" other 其它欄位 }
⚠️ 考量:逐筆寫入單據明細易造成連線池佔用與網路通訊延遲;而傳統取號邏輯在高併發時易引發資料庫鎖定競爭 (Lock Contention)。- 🛠️實作:建立使用者自訂資料表型別 (
UDTT),透過表值參數 (TVP, Table-Valued Parameters) 將明細清單轉為DataTable執行單次批次寫入。另將「單號生成」邏輯抽離為獨立的微交易 (Micro-transaction),配合UPDLOCK處理取號。 - 💡效益:大幅降低資料庫往返通訊次數 (Round-trips),減少高頻寫入時的鎖定等待時間,提升整體 I/O 處理效率;並透過
SqlTransaction確保主檔與明細寫入的資料一致性 (ACID)。
⚠️ 考量:多位使用者並行編輯同一單據時,易產生 遺失更新 (Lost Update) 或庫存數據不一致的風險。- 🛠️實作:於業務主檔配置
[RowVersion] TIMESTAMP欄位實作樂觀鎖 (Optimistic Locking)。資料存取層執行UPDATE時,透過ExecuteScalarAsync搭配OUTPUT INSERTED精確比對時間戳記,若受影響資料列為 0 則瞬間拋出DBConcurrencyException。 - 📈效益:在不提升資料庫交易隔離層級的前提下,有效攔截併發衝突。發生衝突時引導前端重新載入最新狀態,確保資料寫入與庫存異動的絕對一致性。
⚠️ 考量:頻繁的密碼雜湊運算易產生大量短生命週期的字串與陣列物件,增加 GC (垃圾回收) 壓力;常規字串比對亦存在時序攻擊 (Timing Attack) 的安全疑慮。- 🛠️實作:密碼驗證模組採用
PBKDF2演算法,並相容Identity V3的 Big-Endian 二進位封裝格式。處理過程中採用stackalloc與Span<byte>進行記憶體物理切片操作。 - 📈效益:達成零記憶體配置 (Zero-Allocation),避免頻繁生成短生命週期的
byte[]陣列,大幅降低 GC 觸發頻率;採用CryptographicOperations.FixedTimeEquals進行常數時間比對,物理免疫時序攻擊。
⚠️ 考量:傳統 WinForms 的頻繁資料更新易引發事件雪崩 (Event Cascading)、畫面閃爍,以及焦點狀態不同步。- 🛠️實作:擴充
BindingList<T>自訂ExtendedBindingList,實作AddRange暫停事件觸發機制以凍結重繪。透過BindingSource統一管理資料游標,並於CellEndEdit事件進行 O(1) 記憶體內計算與開窗查詢。設計BasePage基底類別,依據單據 4 維狀態機 (草稿/過帳/註銷/作廢) 動態控制介面元件的ReadOnly與Enabled屬性。 - 📈效益:大幅減少
DataGridView大量載入時的重繪次數,達成業務員雙手不離鍵盤的極速盲打體驗。統一控管表單狀態,以物理斷路避免使用者在錯誤的單據生命週期下執行非法存檔或修改操作。
本專案之核心價值在於底層基礎設施之封裝與 I/O 效能最佳化。以下列出系統中具代表性之關鍵模組與實體路徑,供開發者或架構檢視者快速掌握本系統之底層實作細節:
1. 全域交易安全沙盒 : ERPLAB.UI/Core/BasePage.cs
將所有資料庫 I/O 透過委派 (Delegate) 封裝,集中攔截 SqlException 與 DBConcurrencyException,確保 UI 層的純潔性與錯誤轉譯的一致性。
protected async Task<bool> SafeExecuteAsync(Func<Task> dbAction, Func<Task> reloadDataAction = null)
{
try { await dbAction(); return true; }
catch (DBConcurrencyException cx) { /* 樂觀鎖攔截與重載邏輯 */ }
catch (SqlException sqlex) { /* 約束衝突轉譯邏輯 */ }
}2. 零配置密碼學引擎 : ERPLAB.DataAccess/Core/CryptoHelper.cs
展示如何使用 Span<byte> 與 BinaryPrimitives 進行記憶體物理切片,實作相容 Identity V3 的二進位 PBKDF2 演算法。
3. TVP 分散式交易引擎 : ERPLAB.DataAccess/Repositories/SalesRepository.cs
展示 CreateSalesOrderAsync 中,主明細表如何透過 SqlTransaction 與表值參數 (TVP) 達成單次 I/O 批次寫入與微交易獨立取號。
📦 庫存盤點作業 (Inventory Check) ── 點擊展開檢視
展示雙軌庫存比對機制(帳面與實盤數量)。實作盤點單的狀態流轉,並於過帳時透過差異沖平邏輯自動調整庫存(於資料庫底層運算 實盤 - 帳面快照 並透過 Transaction 寫回實體庫存),確保帳實一致。




