非常抱歉,這麼長時間沒寫部落格,沒有繼續寫這個系列。我想解釋下,因為之前一直忙於換工作,換了工作後,新公司在上班時間無法上外網,所以才有了這樣的情況。經過這段時間的忙碌和適應,我想我今後還是會努力調整好時間和狀態,一周應該會至少發表一篇博文,順便說下,在這個系列完結後,我可能會寫一個關於prism的系列,也希望大家能來捧場,再交代下,我之前在Hp EDS-wuhan從事.net開發,現在在光庭導航資料(武漢)有限公司從事.net開發。
ok,情況就交代這麼多,下面我們來進入正題。
首先我們來澄清一個問題,linq直譯過來是Language-integrated Query (LINQ),顧名思義,是做查詢的,像之前說的,對本機資料源有linq to object,遠端有linq to sql, linq to dataset, linq to entities, linq to xml。那麼運算式樹狀架構又是做什麼的呢?運算式樹狀架構是不是也是用來描述或者儲存查詢條件的呢?
答案,否。準確地說,方法(函數)可以做什麼運算式樹狀架構就可以做什麼。在具體闡述運算式樹狀架構可以做什麼,怎麼做之前,我們先來提綱挈領的看看結構。之前的文章中已經提到過了IEnumerable<T>和IQueryble<T>的繼承結構,所以今天我們就從我的上一篇博文中的Provider說起,至於總體的詳細結構,我會在這個系列的最後一片文章中具體來說。
這是我從TerryLee的博文中摘取下來的一張圖,原文地址: http://www.cnblogs.com/Terrylee/archive/2008/08/25/custom-linq-provider-part-2-IQueryable-IQueryProvider.html
從這張圖中我們可以看到,IQueryable<T>繼承於IQueryable,而IQueryable中有三個成員,下面我們就來具體說說這3個成員分別是做什麼用的。
我們可以參考msdn上的資料:http://msdn.microsoft.com/zh-cn/library/system.linq.iqueryable_members.aspx
ElementType:擷取在執行與 IQueryable 的此執行個體關聯的運算式樹狀架構時返回的元素的類型。
Expression:擷取與 IQueryable 的執行個體關聯的運算式樹狀架構。
Provider:擷取與此資料來源關聯的查詢提供者。
根據之前提到過的TerryLee的那篇博文:我們所有定義在查詢運算式中方法調用或者Lambda運算式都將由該Expression屬性工作表示,而最終會由Provider表示的提供者翻譯為它所對應的資料來源的查詢語言,這個資料來源可能是資料庫,XML檔案或者是WebService等。該介面非常重要,在我們自訂LINQ Provider中必須要實現這個介面。同樣對於IQueryable的標準查詢操作都是由Queryable中的擴充方法來實現的。
相信大家應該還記得,我們之前提到過,IEnumerable<T>和IQueryable<T>用的是2套東西,IEnumerable<T>的擴充方法定義在Enumerable類中,而IQueryable<T>的方法定義在Queryable類中。
下面我們再來看看Queryable類中的一個擴充方法是如何工作的,藉此也看看IQueryable各成員的作用。
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(new Type[] { typeof(TSource) }),
new Expression[] { source.Expression, Expression.Quote(predicate) }));
}
這個例子中我們看到source.Expression用來表示查詢運算式,而source.Provider提供者翻譯為它所對應的資料來源的查詢語言,為了更直觀一點,我們來看看Enumerable中的擴充方法:
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (predicate == null)
{
throw Error.ArgumentNull("predicate");
}
return WhereIterator<TSource>(source, predicate);
}
大家可以看到,在這個過程中是沒有接收查詢運算式和把運算式翻譯成對應的資料來源的查詢語言這麼一個過程的,它直接帶入了一個謂語運算式(相當於一個匿名方法的邏輯)WhereIterator<TSource>(source, predicate);
我們再來看看IQueryProvider這個介面,我們再來看張圖
大家可以看到,這個介面裡實際只有2個方法。CreateQuery和Execute(泛型和非泛型)。我來解釋下這2個方法。
按照MSDN上的資料:http://msdn.microsoft.com/zh-cn/library/bb549043.aspx
如果給定一個運算式樹狀架構,CreateQuery 方法可建立新的 IQueryable<(Of <(T>)>) 對象。返回的對象所表示的查詢與特定 LINQ 提供者相關聯。
大多數返回可枚舉結果的 Queryable 標準查詢運算子方法將調用此方法。這些標準查詢運算子方法將向此方法傳遞一個表示 LINQ 查詢的 MethodCallExpression。
我來說明白一點,這個方法就是把查詢資料來源和Expression關聯起來,就是給一個查詢資料來源IQueryable<T>指定它的Expression是什麼。
而當我們真正foreach資料來源執行查詢操作時,我們調用的是Execute方法,Execute返回一個單值,Execute方法具體起到來了訪問ExepressionTree,並把運算式樹狀架構轉換成相應的資料來源可以識別的邏輯這個過程。我們可以通過一個msdn上的資料來具體看看這2個方法:http://msdn.microsoft.com/zh-cn/library/bb546158.aspx
public class TerraServerQueryProvider : IQueryProvider
{
public IQueryable CreateQuery(Expression expression)
{
Type elementType = TypeSystem.GetElementType(expression.Type);
try
{
return (IQueryable)Activator.CreateInstance(typeof(QueryableTerraServerData<>).MakeGenericType(elementType), new object[] { this, expression });
}
catch (System.Reflection.TargetInvocationException tie)
{
throw tie.InnerException;
}
}
// Queryable's collection-returning standard query operators call this method.
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
return new QueryableTerraServerData<TResult>(this, expression);
}
public object Execute(Expression expression)
{
return TerraServerQueryContext.Execute(expression, false);
}
// Queryable's "single value" standard query operators call this method.
// It is also called from QueryableTerraServerData.GetEnumerator().
public TResult Execute<TResult>(Expression expression)
{
bool IsEnumerable = (typeof(TResult).Name == "IEnumerable`1");
return (TResult)TerraServerQueryContext.Execute(expression, IsEnumerable);
}
}
為了弄清楚Execute,我們再來看看TerraServerQueryContext有什麼
class TerraServerQueryContext
{
// Executes the expression tree that is passed to it.
internal static object Execute(Expression expression, bool IsEnumerable)
{
// The expression must represent a query over the data source.
if (!IsQueryOverDataSource(expression))
throw new InvalidProgramException("No query over the data source was specified.");
// Find the call to Where() and get the lambda expression predicate.
InnermostWhereFinder whereFinder = new InnermostWhereFinder();
MethodCallExpression whereExpression = whereFinder.GetInnermostWhere(expression);
LambdaExpression lambdaExpression = (LambdaExpression)((UnaryExpression)(whereExpression.Arguments[1])).Operand;
// Send the lambda expression through the partial evaluator.
lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression);
// Get the place name(s) to query the Web service with.
LocationFinder lf = new LocationFinder(lambdaExpression.Body);
List<string> locations = lf.Locations;
if (locations.Count == 0)
throw new InvalidQueryException("You must specify at least one place name in your query.");
// Call the Web service and get the results.
Place[] places = WebServiceHelper.GetPlacesFromTerraServer(locations);
// Copy the IEnumerable places to an IQueryable.
IQueryable<Place> queryablePlaces = places.AsQueryable<Place>();
// Copy the expression tree that was passed in, changing only the first
// argument of the innermost MethodCallExpression.
ExpressionTreeModifier treeCopier = new ExpressionTreeModifier(queryablePlaces);
Expression newExpressionTree = treeCopier.CopyAndModify(expression);
// This step creates an IQueryable that executes by replacing Queryable methods with Enumerable methods.
if (IsEnumerable)
return queryablePlaces.Provider.CreateQuery(newExpressionTree);
else
return queryablePlaces.Provider.Execute(newExpressionTree);
}
private static bool IsQueryOverDataSource(Expression expression)
{
// If expression represents an unqueried IQueryable data source instance,
// expression is of type ConstantExpression, not MethodCallExpression.
return (expression is MethodCallExpression);
}
}
這是個特例正是在execute方法中執行了訪問運算式樹狀架構並把運算式樹狀架構中的邏輯變成webservice的過程
下面也是MSDN上的資料
Execute 方法會執行那些返回單個值(而不是值的可枚舉的序列)的查詢。在枚舉包含返回可枚舉結果的查詢的運算式樹狀架構的 IQueryable<(Of <(T>)>) 對象時,會執行這些運算式樹狀架構。
返回單一結果的 Queryable 標準查詢運算子方法會調用 Execute。這些標準查詢運算子方法將向此方法傳遞一個表示 LINQ 查詢的 MethodCallExpression。
最後再貼一張Linq to sql 的時序圖 來源:http://www.rainsts.net/article.asp?id=537
ok,關於provider的內容就寫這麼多,歡迎大家給我留言交流,下一篇我會寫些輕鬆的內容,來寫些具體的運算式樹狀架構的構造和應用。