【程式開發經驗】EF Core Contains 空白字串陷阱

文章目錄

本文將說明在使用 EF Core Contains 篩選資料時可能遇到的空白字串陷阱,並會解釋如何確認資料庫中的欄位值是否為空白?整理並比較資料庫中的字串資料型態。此外,介紹如何在 SQL Server 取得欄位值的長度

EF Core Contains 篩選資料

假設有一個產品資料表 Product,包含以下資料:

Name
紅葉布丁
紅葉蛋糕
紅磚布丁

在 .NET 中,以 Entity Framework Core (EF Core) 寫一段程式碼,篩選特定品項的產品。

db.Products.where(_ => _.Name.Contains(condition));

考慮到 condition 可能包含空白,我們使用 Trim() 方法移除這些空白以正確篩選。 例如,當 condition 為 "布丁 " ,加上 Trim() 方法以 "布丁" 正確取得兩筆資料:「紅葉布丁」、「紅磚布丁」。

db.Products.where(_ => _.Name.Contains(condition.Trim()));

這段程式碼會被 EF Core 自動轉換成以下的 SQL 語法:

SELECT [t].[NAME]
FROM [PRODUCT] AS [t]
WHERE (@condition LIKE N'') OR (CHARINDEX(@condition, [t].[NAME]) > 0)

可以發現 @condition 若為空 ''@condition LIKE N'' 條件將為 true,而取得資料表中的所有資料。

因此在進入查詢前,先透過 IsNullOrEmpty() 函式判斷 condition 是否為空;若不為空,才進入查詢。

if(!string.IsNullOrEmpty(condition)) {
    db.Products.where(_ => _.Name.Contains(condition.Trim()));
}

然而,當 condition 為多個空白組成的字串 " " 時,因為通過了 if 檢查,所以執行查詢,返回了資料表中的所有資料 :「紅葉布丁」、「紅葉蛋糕」、「紅磚布丁」。

由此可知,由多個空白組成的字串,既不是 NULL 也不是 EMPTY

因此,我們也需要在 IsNullOrEmpty() 的檢查中,加上 Trim() 方法移除 condition 中的空白。讓 condition 在只含有多個空白的情況下無法通過檢查。

if(!string.IsNullOrEmpty(condition.Trim())) {
    db.Products.where(_ => _.Name.Contains(condition.Trim()));
}

這時可以意識到,保持變數前後一致性的重要。因此,整理一下程式碼。

var queryParam = condition.Trim();
if(!string.IsNullOrEmpty(queryParam)) {
    db.Products.where(_ => _.Name.Contains(queryParam));
}

這種由多個空白組成字串的條件查詢,可能發生在人工意外輸入、惡意攻擊;或是資料庫中固定大小的字串資料型態,自動補足空白所致。

看起來是空值,但既不是 NULL 也不是 EMPTY ?

在資料庫中,一個欄位看起來是空值,但實際上可能包含空白字元。 這種誤解情況,特別容易發生在固定大小的字串資料型態 char(n)nchar(n) 中,因為它們會自動補足空白字元。

如何確認資料庫中的欄位值是否為空白?

首先,確認欄位的資料型態是否為 char(n)nchar(n)。如果是的話,可以透過以下方法確認空白是否為 n 個:

  • 將資料匯出至 Excel 檔案,查看欄位值是否由 n 個空白組成。
  • 使用 WHERE 篩選資料列,確認欄位值是否為 n 個空白。
    SELECT * FROM mytable WHERE column = '        ';
    
  • 在 SQL Server 中可使用 DATALENGTH 取得空白數量。
    SELECT DATALENGTH(column) FROM mytable WHERE id = 1; -- 結果應為 n
    

為避免需要經常移除字串空白,在設計資料模型前選擇適當的資料型態就顯得相當重要。

比較 CHAR、NCHAR、VARCHAR、NVARCHAR

比較字串資料型態,CHARNCHARVARCHARNVARCHAR

特性CHARNCHARVARCHARNVARCHAR
描述固定長度的 ASCII 字符型別固定長度的 Unicode 字符型別可變長度的 ASCII 字符型別可變長度的 Unicode 字符型別
存儲方式固定長度,右側自動補足空白固定長度,右側自動補足空白根據實際長度存儲,不補空白根據實際長度存儲,不補空白
字符編碼ASCIIUnicodeASCIIUnicode
適用場景固定長度的英數字資料,如手機號碼固定長度的多語言資料,如縣市名可變長度的英數字資料,如護照名可變長度的多語言資料,如中文名
長度範圍1 到 8,000 字符1 到 4,000 字符1 到 8,000 字符1 到 4,000 字符
示例CHAR(10)NCHAR(10)VARCHAR(50)NVARCHAR(50)
是否支持多語言

SQL Server 取得欄位值長度

在 SQL Server 中使用 LENDATALENGTH 函數,取得欄位值長度。以下提供範例,說明兩者的不同之處。

LEN

LEN 函數用來計算字串中字符的數量,但不計算尾端的空白

SELECT LEN('Hello World') -- 結果是 11
SELECT LEN('Hello World   ') -- 結果也是 11,尾端空白不計算
SELECT LEN('你好') -- 結果是 2
SELECT LEN('') -- 結果是 0
SELECT LEN(NULL) -- 結果是 NULL

DATALENGTH

DATALENGTH 函數用來計算字串的總長度,包含所有的字符和空白。 但無法正確計算 Unicode 編碼的字元數量

SELECT DATALENGTH('Hello World') -- 結果是 11
SELECT DATALENGTH('Hello World   ') -- 結果是 14,包含尾端的3個空白
SELECT DATALENGTH('你好') -- 結果是 4,無法正確計算 Unicode 編碼的字元數量
SELECT DATALENGTH('') -- 結果是 0
SELECT DATALENGTH(NULL) -- 結果是 NULL

經驗總結

  1. 由多個空白組成的字串,既不是 NULL 也不是 EMPTY
  2. 比較字串值之前,習慣先移除空白。
  3. 設計資料模型時,適當的資料型態,可避免不必要的空白問題。