Object-Relational Mapping (ORM): 一種在物件導向程式碼(如 C#)與關聯式資料庫之間自動轉換資料的技術,讓開發者能以操作物件的方式來存取資料庫。ORM 就像一位翻譯官,將程式碼中的物件操作(例如 product.Name = “新名稱”)翻譯成對應的 SQL 指令(UPDATE Products SET Name = ‘新名稱’ WHERE Id = 1)。
EF Core: 可以使用 C# 物件執行 CRUD、處理資料轉換、變更追蹤 (Change Tracking)、資料庫遷移 (Migrations) 與延遲載入 (Lazy Loading) 。
# 全域安裝 EF Core 工具。
dotnet tool install –global dotnet-ef
# 安裝 EF Core SQLite 和工具包
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools
DbContext: EF Core 的核心類別,是應用程式與資料庫溝通的橋樑,通常放置在 Models/ApplicationDbContext.cs 。
DbSet<T> : 分別對應資料庫中的資料表。DbContext 會追蹤所有從資料庫查詢出來的物件,當您修改這些物件後,只需呼叫
SaveChanges() : 當修改物件後,只需呼叫 SaveChanges() DbContext 就會自動生成對應的 SQL 指令來更新資料庫,確保資料的一致性。
用 DbContext 處理 CRUD 操作
C 新增:使用 Add 方法插入新記錄。填入實體實例後,呼叫 SaveChanges 方法將其儲存到資料庫中。
var newProduct = new Product { Name = “Laptop”, Price = 1200.99m };
- m 標記為 decimal 類型
context.Products.Add(newProduct);
context.SaveChanges();
R 讀取: Find 、 FirstOrDefault 和 ToList 等方法允許從資料庫中檢索資料(無論是單一還是多個記錄)。
var allProducts = context.Products.ToList();
Console.WriteLine(“All Products:”);
allProducts.ForEach(p => Console.WriteLine($”{p.Id}: {p.Name} – ${p.Price}”));
U 更新:使用 Find 或 ToList 取得記錄 ,根據需要修改,然後套用 Update 和 SaveChanges 來反映變更。
var singleProduct = context.Products.Find(1);
Console.WriteLine($”Product Found: {singleProduct.Name} – ${singleProduct.Price}”);
singleProduct.Price = 1100.99m; // 更新數據
context.SaveChanges();
D 刪除:使用 Find 或 ToList 檢索記錄 ,將其傳遞給 Remove,然後呼叫 SaveChanges 將其從資料庫中刪除。
context.Products.Remove(singleProduct);
context.SaveChanges();
第一範式 (1NF):原子性(Atomic)
核心要求: 第一範式要求資料表中的每個欄位都必須是原子性的,也就是說,欄位裡的值不能再被分割,也不能包含多個值或一個列表。這能確保數據的最小顆粒度。
❌ 錯誤範例(不符合 1NF): 在一個名為 客戶資料 的表格中,有一個 客戶_電話 欄位儲存了多個電話號碼,例如 101, 王小明, 0912-3456, 0987-6543。由於一個欄位裡有兩個電話號碼,這違反了原子性。
✅ 正確範例(符合 1NF): 要修正這個問題,有兩種常見的做法。
方法一是將欄位拆分成多個獨立的欄位,例如:客戶_電話_主要 和 客戶_電話_備用。
方法二是將電話號碼拆分到一個獨立的 客戶_電話 資料表。這樣,客戶 ID 為 101 的客戶就會佔據兩行紀錄,每行只包含一個電話號碼,實現了數據的原子性。
第二範式(2NF):完全依賴主鍵
核心要求: 第二範式適用於使用複合主鍵的資料表。它要求所有的非主鍵欄位都必須完全依賴於整個複合主鍵,不能只依賴於主鍵中的一部分。這一步的目的是消除部分依賴。
❌ 錯誤範例(不符合 2NF): 假設有一個 訂單明細 表格,其複合主鍵是 (訂單 ID, 產品 ID)。表格中包含了 數量、產品名稱 和 產品單價 等欄位。
錯誤發生在 產品名稱 和 產品單價 欄位,因為它們只依賴於 產品 ID,而與 訂單 ID 無關。這導致同一個產品的名稱和單價資訊在不同的訂單中被重複儲存。
✅ 正確範例(符合 2NF): 要符合 2NF,必須將只依賴於 產品 ID 的欄位(即 產品名稱 和 產品單價)拆分到一個獨立的 產品 資料表。
原來的 訂單明細 表格只保留 訂單 ID、產品 ID 和 數量。通過這種拆分,我們消除了重複的產品資訊,提高了數據的一致性。
第三範式(3NF):消除傳遞依賴 (Transitive Dependency)
核心要求: 第三範式要求非主鍵欄位不能依賴於另一個非主鍵欄位。這確保了表中的所有資訊都與其主鍵直接相關,進一步減少了數據的冗餘和維護的複雜性。
❌ 錯誤範例(不符合 3NF): 假設有一個 員工資料 表格,主鍵是 員工 ID。表格中包含了 員工姓名、部門 ID 和 部門名稱。
這裡的錯誤是 部門名稱 依賴於 部門 ID,而 部門 ID 是一個非主鍵欄位。
這是一個 傳遞依賴:員工 ID → 部門 ID → 部門名稱,這導致如果某個部門(例如 ID 為 D10)的名稱需要修改,你必須修改所有屬於 D10 部門的員工記錄。
✅ 正確範例(符合 3NF): 要符合 3NF,必須將具有傳遞依賴關係的欄位(部門 ID 和 部門名稱)拆分到一個獨立的 部門 資料表。
修正後的 員工資料 表格只保留 員工 ID、員工姓名 和 部門 ID(作為外來鍵)。
這樣,當部門名稱需要變動時,只需要在獨立的 部門 表格中修改一次即可,徹底消除了更新異常。
ACID 原則: 確保資料庫交易的可靠性,是關聯式資料庫的基石。
- 原子性 (Atomicity): 交易被視為一個不可分割的工作單元。例如,銀行轉帳包含「從 A 帳戶扣款」和「向 B 帳戶存款」兩個操作,這兩個操作必須要麼同時成功,要麼同時失敗回滾。
- 一致性 (Consistency): 交易前後,資料庫的完整性約束不能被破壞。例如,轉帳前後,所有帳戶的總金額應保持不變。
- 隔離性 (Isolation): 在多個交易並行執行時,每個交易都感覺不到其他交易的存在。系統透過鎖定機制來實現,確保交易之間互不干擾。
- 耐久性 (Durability): 一旦交易被成功提交 (Commit),其結果就是永久性的。即使發生系統斷電或崩潰,資料也不會遺失,通常是透過交易日誌 (Transaction Log) 來保證。
並行控制 (Concurrency Control): 解決多使用者同時存取資料時可能發生的問題。不同的隔離級別提供了在資料準確性與系統效能之間的權衡。
- Read Uncommitted: 效能最好,但允許一個交易讀取另一個尚未提交的交易所做的修改,可能導致髒讀 (Dirty Read)。
- Read Committed: 大多數資料庫的預設級別。它保證一個交易只能讀取已經提交的變更,避免了髒讀。但如果在同一個交易中兩次讀取同一筆資料,中間若有其他交易修改了它,會讀到不同的結果,稱為不可重複讀取 (Non-Repeatable Read)。
- Repeatable Read: 保證在同一個交易中多次讀取同一筆資料時,結果總是一致的。但它無法阻止其他交易插入符合查詢條件的新資料,導致幻讀 (Phantom Read)。
- Serializable: 最嚴格的級別,透過鎖定整個查詢範圍來防止任何形式的並行問題,確保資料的絕對一致性,但會嚴重影響系統的並發效能。
索引是提升查詢速度的關鍵,其本質是用額外的儲存空間來換取更快的讀取時間,但會降低寫入操作的效能,因為每次寫入都需要同步更新索引。
聚集索引 (Clustered Index):
- 常用於 : ID (主鍵/自增長)、OrderDate (日期欄位)
- 特性: 資料表的物理儲存順序與索引鍵值的邏輯順序完全一致。就像一本按姓氏排序的電話簿,資料本身就是按順序存放的。
- 限制: 由於物理順序只能有一種,一個資料表只能有一個聚集索引,通常設定在主鍵上。
- 用途: 提供最快的資料查找速度,特別適合範圍查詢(例如,查詢某個日期區間內的所有訂單)。
非聚集索引 (Nonclustered Index):
- 常用於 : LastName、Email、ZipCode、Status。
- 特性: 建立一個獨立的排序結構(通常是 B-Tree),該結構包含索引鍵值以及一個指向實際資料行的指標(或聚集索引的鍵值)。就像書本最後的索引頁,它告訴你某個關鍵字在哪一頁,你需要根據頁碼再翻到對應位置。
- 限制: 一個資料表可以有多個非聚集索引,以滿足不同的查詢需求。
- 用途: 為不同的查詢條件提供多種快速查找途徑,而不改變資料的物理順序。
索引使用注意事項:
- 避免過度索引 (Over-Indexing),每個額外的索引都會在 INSERT、UPDATE、DELETE 時帶來額外開銷。
- 定期清理未使用的索引 (Unused Indexes),這些索引只會白白佔用儲存空間並拖累寫入效能。
- 善用複合索引 (Compound Index),即在多個欄位上建立單一索引,以優化涉及這些欄位的複雜查詢。
- 透過分析查詢計畫 (Query Plan) 來驗證索引是否被資料庫引擎有效利用,並進行相應調整。
將常用的 SQL 邏輯封裝在資料庫中,可以被應用程式重複呼叫,帶來多重效益。
優點:
- 提升效能: 預存程序在首次執行時會被編譯並快取執行計畫,後續呼叫時能直接重用,避免了重複的 SQL 解析與優化過程。
- 強化安全: 可以只授予使用者執行特定程序的權限,而不讓他們直接存取底層資料表,有效防止未經授權的資料操作與 SQL 注入攻擊。
- 減少網路流量: 對於複雜的操作,應用程式只需傳送一個簡短的呼叫指令,而不是一長串的 SQL 腳本,大幅降低了客戶端與伺服器之間的網路傳輸量。
- 模組化與維護性: 將業務邏輯集中在資料庫中管理,當邏輯需要變更時,只需修改預存程序,而無需重新部署所有應用程式。
純量函數 (Scalar-Valued Functions): 返回一個單一值(如數字、字串),常被用在 SELECT 語句的欄位列表或 WHERE 子句中。
— 假設我們有一個 ProductPrice 表格
— 目的:根據原始價格計算 8 折後的價格
CREATE FUNCTION CalculateDiscountPrice
(
@OriginalPrice DECIMAL(10, 2) — 輸入參數:原始價格
)
RETURNS DECIMAL(10, 2) AS BEGIN
— 返回值:折扣後的單一價格
DECLARE @DiscountPrice DECIMAL(10, 2);
— 計算折扣價格 (8 折)
SET @DiscountPrice = @OriginalPrice * 0.80;
RETURN @DiscountPrice;
END;
GO
# 接收@OriginalPrice然後回傳計算後的@DiscountPrice數值
— 如何使用 (效果:在 SELECT 語句中像內建函數一樣使用)
SELECT
ProductID,Price AS OriginalPrice,dbo.CalculateDiscountPrice(Price) AS DiscountPrice
FROM
Products;
# dbo.CalculateDiscountPrice(Price):
- 用途: 在 SELECT 語句中,你直接呼叫了你剛剛建立的函數。
- dbo.: 在 SQL Server 中,dbo 是預設的資料庫擁有者 (Database Owner) 結構描述 (Schema),呼叫自定義函數時通常需要加上它。
- Price: 這是傳入函數的參數。在這裡,它使用了 Products 表格中每一行的 Price 欄位的值作為輸入。
資料表值函數 (Table-Valued Functions): 返回一個完整的資料表(結果集),可以像一般資料表一樣在 FROM 子句中被查詢或連接。
— 目的:根據部門 ID 篩選出該部門的所有員工
CREATE FUNCTION GetEmployeesByDepartment
(
@DepartmentID INT — 輸入參數:部門 ID
)
RETURNS TABLE AS RETURN — 返回值:一個資料表 (結果集)
(
— 這裡的查詢結果就是這個函數返回的資料表
SELECT
EmployeeID , FirstName , LastName,Salary
FROM
Employees
WHERE
DepartmentID = @DepartmentID
);
GO
— 如何使用 (效果:在 FROM 語句中像資料表一樣使用)
SELECT
FirstName, Salary
FROM
dbo.GetEmployeesByDepartment(101);
— 將函數視為一個資料表來源
- 參數化查詢: 將使用者輸入嚴格視為資料,而非可執行的程式碼。這是防範 SQL 注入最有效且必須採用的首選方法。
- 輸入驗證: 在應用程式層面對所有使用者輸入進行嚴格的白名單驗證,限制其類型、格式與長度,作為額外的防線。
- 預存程序: 將 SQL 邏輯封裝在資料庫中,並限制應用程式只能透過參數化方式呼叫這些程序,進一步隔離了程式碼與資料。
- 身份驗證 (Authentication): 透過強密碼策略、多因素驗證 (MFA) 及單一登入 (SSO) 等機制,嚴格驗證「使用者是誰」。
- 授權 (Authorization): 遵循最小特權原則 (Principle of Least Privilege),確保每個使用者僅被授予完成其工作所必需的最小權限。常透過基於角色的存取控制 (RBAC) 來系統化地管理權限。
- 加密: 對靜態資料 (Data at Rest)(儲存在硬碟上的資料)和傳輸中資料 (Data in Transit)(在網路中傳輸的資料)進行加密,確保即使資料被竊取也無法讀取。
- 防火牆: 設定嚴格的網路存取規則,限制僅允許來自可信任 IP 或應用程式伺服器的連線,阻擋未經授權的外部存取。
- 定期備份: 制定完善的備份策略(如完整、差異、交易日誌備份),並將備份加密後儲存在安全的異地,確保在災難發生時能可靠地復原資料。
- 持續監控與日誌: 啟用資料庫稽核功能,不間斷地監控所有資料庫活動。日誌記錄可以捕捉到異常或未經授權的行為,為即時威脅偵測與事後追蹤提供依據。