.NET Framework 3.5 提供 LINQ 支援後,各式 LINQ Provider 百花齊放 (LINQ to NHibernate、LINQ to Google 等)。LINQ 擴展性優異,可輕鬆開發自訂 Provider。本系列第二篇,詳細剖析自訂 Provider 兩大核心介面:IQueryable 與 IQueryProvider。
LINQ to Objects 查詢回傳 IEnumerable<T>,因查詢的是實作 IEnumerable<T> 的記憶體內集合,Lambda 運算式直接轉 .NET 程式碼執行,無需轉換為表達式樹,故不需特定 Provider。
IEnumerable<T> 介面無 Expression 與 Provider 屬性。標準查詢運算子定義於 Enumerable 靜態類別擴展方法,參數為 Func<TSource, bool> 委托而非 Expression<Func<TSource, bool>>。
可透過 AsQueryable() 將 IEnumerable<T> 轉為 IQueryable<T>,查詢機制隨之改變 (雖結果相同)。
繼承 IEnumerable<T> 與 IQueryable:
Expression:關聯實例的表達式樹Provider:關聯資料來源的查詢提供者所有查詢運算子方法呼叫/ Lambda 運算式均由 Expression 表示,最終由 Provider 翻譯為對應資料源查詢語言 (SQL、XML、Web Service 等)。自訂 Provider 必須實作此介面。
public static class Queryable
{
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate)
{
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
return source.Provider.CreateQuery<TSource>(
Expression.Call(null,
((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource)),
new Expression[] { source.Expression, Expression.Quote(predicate) }));
}
// 另一個 Where 多載略...
}
參數為 Expression<Func<TSource, bool>>,透過 Provider.CreateQuery 建構新查詢。
兩組方法對應兩個操作:
CreateQuery (泛型/非泛型):建構可計算指定表達式樹的 IQueryable<T>,回傳可列舉類型 (延遲執行)Execute (泛型/非泛型):執行指定表達式樹查詢,回傳單一值 (立即執行)LINQ to SQL Provider:翻譯表達式樹為 T-SQL 傳資料庫執行回結果。Web Service Provider:翻譯表達式樹呼叫 Web Service 回結果。
IEnumerable<T> 即可,無需 IQueryable 與自訂 Provider。如 List<T> 等內建類型。IQueryable<T> 與 IQueryProvider 兩大介面。public class QueryableData<TData> : IQueryable<TData>
{
public QueryableData() { Provider = new TerryQueryProvider(); Expression = Expression.Constant(this); }
public QueryableData(TerryQueryProvider provider, Expression expression) { ...驗證參數... Provider = provider; Expression = expression; }
public IQueryProvider Provider { get; private set; }
public Expression Expression { get; private set; }
public Type ElementType => typeof(TData);
public IEnumerator<TData> GetEnumerator() => (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => (Provider.Execute<IEnumerable>(Expression)).GetEnumerator();
}
public class TerryQueryProvider : IQueryProvider
{
public IQueryable CreateQuery(Expression expression) { ...動態建立 QueryableData<T>... }
public IQueryable<TResult> CreateQuery<TResult>(Expression expression) => new QueryableData<TResult>(this, expression);
public object Execute(Expression expression) { ...執行邏輯... }
public TResult Execute<TResult>(Expression expression) { ...執行邏輯... }
}
QueryableData<String>,同步實例化 TerryQueryProviderTerryQueryProvider.CreateQuery 建構表達式樹,**查詢不真正執行** (延遲載入)GetEnumerator (如 foreach) 觸發 TerryQueryProvider.Execute,**查詢真正執行**掌握 IQueryable (表達式樹容器) 與 IQueryProvider (執行引擎) 兩大核心,奠定自訂 LINQ Provider 基石。下篇將完整實作一個自訂 LINQ Provider。