在 LINQ 中,查詢表達式的建構方式取決於資料來源:本地集合使用 IEnumerable<T> 與委托,而遠端資料源(如 LINQ to SQL)則使用 IQueryable<T> 與表達式樹。本文將詳細探討表達式樹的核心概念、與委托的差異、編譯技巧以及動態建構表達式樹的方法。
回顧 LINQ 兩大查詢模式:
Enumerable 操作符,參數為 Func<T, bool> 委托。Queryable 操作符,參數為 Expression<Func<T, bool>> 表達式樹。以 Where 為例:
// Enumerable.Where
public static IEnumerable<T> Where(this IEnumerable<T> source, Func<T, bool> predicate)
// Queryable.Where
public static IQueryable<T> Where(this IQueryable<T> source, Expression<Func<T, bool>> predicate)
雖然 Lambda 寫法相同(p => !p.Discontinued),但編譯器會根據來源型別(IEnumerable 或 IQueryable)決定產生委托還是表達式樹。若需明確指定,可顯式宣告變數型別:
Func<Product, bool> del = p => !p.Discontinued; // 委托
Expression<Func<Product, bool>> expr = p => !p.Discontinued; // 表達式樹
表達式樹可透過 Compile() 轉為委托,這對建立可重用的查詢邏輯極有價值。例如在實體類別中定義靜態方法回傳表達式:
public partial class Product
{
public static Expression<Func<Product, bool>> IsSelling()
{
return p => !p.Discontinued && p.LastSale > DateTime.Now.AddDays(-30);
}
}
此方法同時支援本地與遠端查詢:
var sqlQuery = dataContext.Products.Where(Product.IsSelling()); // IQueryable
var localQuery = localProducts.Where(Product.IsSelling().Compile()); // IEnumerable
注意:委托無法反向轉換為表達式樹,這也是表達式樹更具彈性的原因。
AsQueryable() 可將本地集合包裝為 IQueryable,讓同一查詢邏輯同時適用於記憶體與資料庫:
IQueryable<Product> FilterSortProducts(IQueryable<Product> input)
{
return from p in input where ... orderby ... select p;
}
void Test()
{
var sqlQuery = FilterSortProducts(dataContext.Products);
var localQuery = FilterSortProducts(localProducts.AsQueryable());
}
列舉結果時,表達式樹會隱式編譯為本地委托執行。
表達式樹是一個小型 DOM,每個節點對應 System.Linq.Expressions 命名空間下的型別。基類為非泛型 Expression,泛型 Expression<TDelegate> 代表型別化的 Lambda 表達式。透過 Expression 靜態工廠方法即可動態建構樹:
// 動態建構 s => s.Length < 5
ParameterExpression p = Expression.Parameter(typeof(string), "s");
MemberExpression len = Expression.Property(p, "Length");
ConstantExpression five = Expression.Constant(5);
BinaryExpression cmp = Expression.LessThan(len, five);
Expression<Func<string, bool>> lambda = Expression.Lambda<Func<string, bool>>(cmp, p);
Func<string, bool> runnable = lambda.Compile();
Console.WriteLine(runnable("James")); // False
Console.WriteLine(runnable("dog")); // True
這展示了如何在執行時期從零建立表達式樹,並轉換為可執行委托或用於 LINQ to SQL 查詢。
表達式樹是 LINQ 解釋查詢的核心,它將程式碼轉為資料結構,使遠端提供者能翻譯為 SQL。掌握表達式樹的建構、編譯與動態生成,能大幅提升查詢邏輯的重用性與靈活性。