在實際專案中,常需依使用者輸入的多組關鍵字對同一欄位進行「OR 模糊比對」,例如:ProductName LIKE 'P1%' OR ProductName LIKE 'P2%'。若直接用 LINQ 串接 Where,會產生 AND 條件;若用 Union 則產生多重查詢,SQL 膨脹且效能不佳。本文示範如何利用 Expression Tree 動態建構 OR 邏輯,並包裝成擴充方法 WhereOrLike,讓呼叫端如同原生 LINQ 般簡潔。
BuildContainsExpressionpublic static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
Expression<Func<TElement, TValue>> valueSelector,
IEnumerable<TValue> values)
{
// 取得 string.StartsWith 方法
var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
// 為每個值建立「valueSelector.Body.StartsWith(value)」運算式
var startWithExpressions = values.Select(value =>
(Expression)Expression.Call(valueSelector.Body, startsWithMethod, Expression.Constant(value, typeof(TValue))));
// 以 OrElse 串接所有條件
var body = startWithExpressions.Aggregate((accumulate, equal) => Expression.OrElse(accumulate, equal));
// 建立 Lambda 參數並回傳
var param = Expression.Parameter(typeof(TElement));
return Expression.Lambda<Func<TElement, bool>>(body, param);
}
WhereOrLikepublic static IQueryable<TElement> WhereOrLike<TElement, TValue>(
this IQueryable<TElement> query,
Expression<Func<TElement, TValue>> valueSelector,
IEnumerable<TValue> values)
{
return query.Where(BuildContainsExpression(valueSelector, values));
}
var productNames = new[] { "P1", "P2" };
var result = context.Products
.WhereOrLike(p => p.ProductName, productNames)
.ToList();
透過 SQL Profiler 觀察,EF 正確轉譯為參數化 OR LIKE:
-- Region Parameters
DECLARE @p0 NVarChar(3) = 'P1%'
DECLARE @p1 NVarChar(3) = 'P2%'
-- EndRegion
SELECT [t0].[Id], [t0].[ProductName]
FROM [Product] AS [t0]
WHERE ([t0].[ProductName] LIKE @p0) OR ([t0].[ProductName] LIKE @p1)
matchType 參數切換 StartsWith、Contains、EndsWith。params Expression<Func<T, string>>[] selectors,跨欄位模糊搜尋。valueSelector 與 values 組合可快取編譯後的 Delegate,避免重複建構 Expression Tree。透過 Expression Tree 動態組合 OrElse,我們在保持強型別、編譯時期檢查的前提下,實現了靈活的多關鍵字模糊查詢。WhereOrLike 擴充方法讓業務邏輯碼極度精簡,且生成的 SQL 高效參數化,是 EF 進階開發的必備技巧。