1. 程式人生 > 實用技巧 >由淺入深表達式樹(三)

由淺入深表達式樹(三)

由淺入深表達式樹(完結篇)重磅打造

  一個多月之後,由淺入深表達式系列的最後一篇終於要問世了。想對所有關注的朋友說聲:“對不起,我來晚了!” 希望最後一篇的內容對得起這一個月時間的等待。在學習完表示式樹的建立和遍歷之後,我們要利用它的特性來寫一個我們自己的Linq Provider。人家都有Linq to Amazon為什麼我們不能有Linq to cnblogs呢?今天我們就來一步一步的打造自己的Linq Provider,文章未尾已附上原始碼下載地址。如果對於表示式樹的建立和遍歷還是熟悉的話,建議先看前面兩篇:

  建立表示式樹 http://www.cnblogs.com/jesse2013/p/expressiontree-part1.html

  遍歷表示式樹 http://www.cnblogs.com/jesse2013/p/expressiontree-part2.html

更新:之前沒有描述清楚本篇部落格的意圖,導致很多朋友的誤解表示抱歉。本系列重在理解表示式目錄樹,以及Linq Provider。最後一篇是Linq Provider的實現,之所有會寫這麼多的程式碼去做一件簡單的事(拉取部落格園首頁文章列表)完全是為了有一個生動的例子去展示如何實現自己的Linq Provider。和我們專案中的三層架構,或者直接序列化到本地是沒有可比性的。

  當然,表示式目錄樹以及Linq Provider的強大也遠非這個小小的Demo能體現得了的,如果你真正知道Linq Provider和表示式樹目錄樹是什麼,用來幹什麼的,也許你就能明白本篇部落格的意圖了。如果不瞭解的,建議讀完前面兩篇之後再做評論。因為你在自己不理解的情況下就直接去評論其它的領域,你就失去了一個瞭解它的機會。:)

實現目標

  我們實現的目標就像Linq to SQL一樣,可以用Linq查詢語句來查詢資料,我們這裡面的資料用到了部落格園官方的Service去查詢到最新的釋出到首頁的部落格資訊。看下面的程式碼:

var provider = new CnblogsQueryProvider();
var queryable = new Query<Post>(provider);

var query =
    from p in queryable
    where p.Diggs >= 10 &&
    p.Comments > 10 &&
    p.Views > 10 &&
    p.Comments < 20
    select p;

var list = query.ToList();

  作為實時訪問遠端的Service,我們還應該具體以下幾點要求:

  • 延遲載入,即到最後使用的時候才真正的去請求資料
  • 只返回需要的資料,不能把所有的資料全下載過來再到本地過濾,那就沒有意義了

最後實現的結果:

資料準備

  根據部落格園公開的API顯示,獲取首頁文章列表非常容易,大家可以點下面的URL來體檢一把。我們最後給的引數是100000,當然真實返回肯定是沒有那麼多的,我們是希望把能夠取回來的都取回來。

http://wcf.open.cnblogs.com/blog/sitehome/recent/100000

  點選完上面的URL之後呢,問題就來了,它只有一個引數。我並不能傳給它查詢條件,比如說根據標題來搜尋,或者根據評論數,瀏覽量來過濾。難道我的計劃就此要泡湯了麼,剛開始我很不開心,為什麼部落格園就不能提供靈活一點的Service呢?但是事實就是這樣,咋是程式設計師呀,需求擺在這,怎麼著還得實現是不?沒有辦法,我給它封裝了一層。在它的基礎上做了一個自己的Service。

封裝部落格園Service

  我們如何在部落格園公開Service的基礎上加一層實現條件查詢呢?主要思路是這樣的:

  • 為文章建立實體類(Post)
  • 將部落格園Service返回的資料解析成Post的集合,我們可以加上自己的快取機制,可以採用1分鐘才到部落格園取一次資料
  • 把我們上面建立的post集合當作資料庫,建立查詢Service

  我們首先要做的就是為部落格園的部落格建立實體類。

public class Post
{
    // Id
    public int Id { get; set; }

    // 標題
    public string Title { get; set; }

    // 釋出時間
    public DateTime Published { get; set; }

    // 推薦資料
    public int Diggs { get; set; }

    // 訪問人數
    public int Views { get; set; }

    // 評論資料
    public int Comments { get; set; }

    // 作者
    public string Author { get; set; }

    // 部落格連結
    public string Href { get; set; }

    // 摘要
    public string Summary { get; set; }
}

接著,我們要將部落格園返回給我們的XML資料轉換成我們要的post集合,所以我們要用到Linq to XML。

_list = new List<CnblogsLinqProvider.Post>();
var document = XDocument.Load(
    "http://wcf.open.cnblogs.com/blog/sitehome/recent/100000"
    );

var elements = document.Root.Elements();
var result = from entry in elements
                where entry.HasElements == true
                select new CnblogsLinqProvider.Post
                {
                    Id = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "id").Value),

                    Title = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "title").Value,

                    Published = Convert.ToDateTime(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "published").Value),

                    Diggs = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "diggs").Value),

                    Views = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "views").Value),

                    Comments = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "comments").Value),

                    Summary = entry.Elements()
                        .SingleOrDefault(x=>x.Name.LocalName=="summary").Value,

                    Href = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "link")
                        .Attribute("href").Value,

                    Author = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "author")
                        .Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "name").Value
                };

_list.AddRange(result);

  然後就到了我們的查詢了,實際上我們有了IEnumrable<Post>的資料就可以直接在本地用Linq去查詢它了。但是這不是我們想要的,因為我們上面的步驟是把所有的資料一次性全部下載下來了,而不是根據我們的需求返回資料。另外我們這裡面是在部落格園Service的基礎上做一層封裝,實現通過Url直接查詢首頁的文章。為什麼要通過Url來查詢?因為我們最後會通過我們自己的LinqProvider將Linq查詢語句直接翻譯成Url這樣就能夠實現遠端的返回資料了。來看看我們對Url引數的定義:

利用JsonResult 返回json資料來建立我們的Service

  作為Service,我們返回Json或者XML格式的資料都是可以的。當然實現這個需求的方法有很多種,我們這裡面有選了一種最簡單方便又比較適合我們需求方式。不需要WCF Service也不需要Web API,直接用MVC裡面的Action返回JsonResult就可以了。

[HttpGet]
public JsonResult Index(SearchCriteria criteria = null)
{
    var result = PostManager.Posts;
    if (criteria != null)
    {
        if (!string.IsNullOrEmpty(criteria.Title))
            result = result.Where(
                p => p.Title.IndexOf(criteria.Title, StringComparison.OrdinalIgnoreCase) >= 0);

        if (!string.IsNullOrEmpty(criteria.Author))
            result = result.Where(p => p.Author.IndexOf(criteria.Author, StringComparison.OrdinalIgnoreCase) >= 0);

        if (criteria.Start.HasValue)
            result = result.Where(p => p.Published >= criteria.Start.Value);

        if (criteria.End.HasValue)
            result = result.Where(p => p.Published <= criteria.End.Value);

        if (criteria.MinComments > 0)
            result = result.Where(p => p.Comments >= criteria.MinComments);

        if (criteria.MinDiggs > 0)
            result = result.Where(p => p.Diggs >= criteria.MinDiggs);

        if (criteria.MinViews > 0)
            result = result.Where(p => p.Diggs >= criteria.MinViews);

        if (criteria.MaxComments > 0)
            result = result.Where(p => p.Comments <= criteria.MaxComments);

        if (criteria.MaxDiggs > 0)
            result = result.Where(p => p.Diggs <= criteria.MaxDiggs);

        if (criteria.MaxViews > 0)
            result = result.Where(p => p.Diggs <= criteria.MaxViews);
    }
    return Json(result, JsonRequestBehavior.AllowGet);
}

  利用Action來做這種Service還有一個好處就是我們不需要一個一個的宣告查詢引數,只需要把所有的引數放到一個model中就可以了。剩下的事就交給Model Binder吧。

 public class SearchCriteria
    {
        public string Title { get; set; }
        public string Author { get; set; }
        
        public DateTime? Start { get; set; }
        public DateTime? End { get; set; }

        public int MinDiggs { get; set; }
        public int MaxDiggs { get; set; }
        public int MinViews { get; set; }
        public int MaxViews { get; set; }
        public int MinComments { get; set; }
        public int MaxComments { get; set; }
    }

  如果大家想更熟悉這個Service的功能,可以參考上面的引數自己去體驗一下(用IE會直接下載.json的檔案,用Chrom是可以直接在瀏覽器裡面看資料的)。但是我沒有做任何安全性的措施,希望大俠高抬貴手,別把網站整掛了就行。

認識IQueryable和IQueryProvider介面

  有了上面的Service之後,我們要做的事情就簡單多了,但是在我們真正開始動手寫自己的Linq Provider之前,先來看看IQueryable和IQueryProvider這兩個重要的介面。

IQueryable

  IQueryable本身並沒有包含多少的東西,它只有三個屬性:

  • ElementType 代表當然這個Query所對應的型別
  • Expression 包含了我們當然Query所執行的所有查詢或者是其它的操作
  • IQueryProvider則是負責處理上面的Expression的實現

  更為重要的是,在IQueryable這個介面之上,.net為我們提供了很多的擴充套件方法:

  我們平常用到的Where,Select,Max,Any都包括在其中,具體的方法大家可以到System.Linq.Queryable這個靜態類下去看。大家注意一下,傳給Where方法的正是我們現在學習的Expression。

  在另外一個很重要的介面IEnumrable下,也有著同樣的擴充套件方法:

  這些擴充套件方法來自System.Linq.Enumrable這個靜態類下。我們可以看到兩組擴充套件方法的不同之處在於IQueryable下傳入的Expression型別,而IEnumrable下傳入的是委託。這樣做的用意是什麼呢?您請接著往下看。

IQueryProvider

  我們上面講到了Enumrable和Queryable這兩個靜態類下的擴充套件方法,對於Enumrable下的擴充套件方法來說他們傳入的是委託,對於委託而言直接執行就可以了。

public static IEnumerable<T> Where<T>(this IEnumerable<T> list, Func<T, bool> predicate)
{
    var result = new List<T>();
    foreach (var element in list)
    { 
        // 呼叫委託是驗證這個元素是否符合條件
        if(predicate(element))
            result.Add(element);
    }
    return result;
}

  上面的程式碼給大家作一個參考,相信不難理解,所以Enumrable下的靜態方法都是操作本地資料的。而對於Queryable下的靜態方法而言,他們接收的是表示式,還記得表示式的最大特徵嗎?可以在執行時去遍歷解釋然後執行,那麼這樣就可以將表示式轉換成各種其它的方式去獲取資料,偉大的Linq to SQL就是這麼實現的。而這背後的大功臣就是我們的Linq Provider了,而IQueryProvider就是LinqProvider的介面。

  IQueryProvider只有兩個操作,CreateQuery和Execute分別有泛型版本和非泛型版本。 CreatQuery用於構造一個IQueryable<T>的物件,這個類其實沒有任何實現,只是繼承了IQueryable和IEnumrable介面。主要用於計算指定表示式目錄樹所表示的查詢,返回的結果是一個可列舉的型別。而Execute會執行指定表示式目錄樹所表示的查詢,返回指定的結果。所有的內幕就在這個Execute方法裡面,拿我們要進行的Linq to cnblogs方法來舉例,我們將把傳入的表示式目錄樹翻譯成一個URL就是指向我們封裝好的Service的URL,通過發起web request到這個URL,拿到response進行解析,最終得到我們所要的資料,這就是我們Linq to cnblogs的思路。

Linq to cnblogs的實現

  有了前面的資料準備和一些實現的大致思路以後,我們就可以著手開始實現我們的CnblogsQueryProvider了。我們的思路大致是這樣的:

  1. 實現自己的ExpressionVisitor類去訪問表示式目錄數,將其翻譯成可以訪問Service的Url
  2. 呼叫WebRequest去訪問這個Url
  3. 將上面返回的Response解析成我們要的物件

實現PostExpressionVisitor

  關於表示式樹的訪問,我們在第二篇中已經有了比較詳細的介紹。如果對於表示式樹的遍歷不清楚的,可以去第二篇《遍歷表示式》中查閱。在這裡,我們建立一個我們自己的ExpressionVisitor類,去遍歷表示式樹。我們暫時只需要生成一個SearchCriteria(我們上面已經定義好了,對於查詢條件建的模)物件即可。

  1 public class PostExpressionVisitor
  2 {
  3 private SearchCriteria _criteria;
  4 
  5 // 入口方法
  6 public SearchCriteria ProcessExpression(Expression expression)
  7 {
  8     _criteria = new SearchCriteria();
  9     VisitExpression(expression);
 10     return _criteria;
 11 }
 12 
 13 private void VisitExpression(Expression expression)
 14 {
 15     switch (expression.NodeType)
 16     { 
 17         // 訪問 &&
 18         case ExpressionType.AndAlso:
 19             VisitAndAlso((BinaryExpression)expression);
 20             break;
 21         // 訪問 等於
 22         case ExpressionType.Equal:
 23             VisitEqual((BinaryExpression)expression);
 24             break;
 25         // 訪問 小於和小於等於
 26         case ExpressionType.LessThan:
 27         case ExpressionType.LessThanOrEqual:
 28             VisitLessThanOrEqual((BinaryExpression)expression);
 29             break;
 30         // 訪問大於和大於等於
 31         case ExpressionType.GreaterThan:
 32         case ExpressionType.GreaterThanOrEqual:
 33             GreaterThanOrEqual((BinaryExpression)expression);
 34             break;
 35         // 訪問呼叫方法,主要有於解析Contains方法,我們的Title會用到
 36         case ExpressionType.Call:
 37             VisitMethodCall((MethodCallExpression)expression);
 38             break;
 39         // 訪問Lambda表示式
 40         case ExpressionType.Lambda:
 41             VisitExpression(((LambdaExpression)expression).Body);
 42             break;
 43     }
 44 }
 45 
 46 // 訪問  &&
 47 private void VisitAndAlso(BinaryExpression andAlso)
 48 {
 49     VisitExpression(andAlso.Left);
 50     VisitExpression(andAlso.Right);
 51 }
 52 
 53 // 訪問 等於
 54 private void VisitEqual(BinaryExpression expression)
 55 {
 56     // 我們這裡面只處理在Author上的等於操作
 57     // Views, Comments, 和 Diggs 我們都是用的大於等於,或者小於等於
 58     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 59         (((MemberExpression)expression.Left).Member.Name == "Author"))
 60     {
 61         if (expression.Right.NodeType == ExpressionType.Constant)
 62             _criteria.Author = 
 63                 (String)((ConstantExpression)expression.Right).Value;
 64 
 65         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
 66             _criteria.Author = 
 67                 (String)GetMemberValue((MemberExpression)expression.Right);
 68 
 69         else
 70             throw new NotSupportedException("Expression type not supported for author: " + 
 71                 expression.Right.NodeType.ToString());
 72     }
 73 }
 74 
 75 // 訪問大於等於
 76 private void GreaterThanOrEqual(BinaryExpression expression)
 77 {
 78     // 處理 Diggs >= n  推薦人數
 79     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 80         (((MemberExpression)expression.Left).Member.Name == "Diggs"))
 81     {
 82         if (expression.Right.NodeType == ExpressionType.Constant)
 83             _criteria.MinDiggs = 
 84                 (int)((ConstantExpression)expression.Right).Value;
 85 
 86         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
 87             _criteria.MinDiggs =
 88                 (int)GetMemberValue((MemberExpression)expression.Right);
 89 
 90         else
 91             throw new NotSupportedException("Expression type not supported for Diggs:"
 92                 + expression.Right.NodeType.ToString());
 93     }
 94     // 處理 Views>= n   訪問人數
 95     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 96     (((MemberExpression)expression.Left).Member.Name == "Views"))
 97     {
 98         if (expression.Right.NodeType == ExpressionType.Constant)
 99             _criteria.MinViews = 
100                 (int)((ConstantExpression)expression.Right).Value;
101 
102         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
103             _criteria.MinViews =
104                 (int)GetMemberValue((MemberExpression)expression.Right);
105 
106         else
107             throw new NotSupportedException("Expression type not supported for Views: " 
108                 + expression.Right.NodeType.ToString());
109     }
110     // 處理 comments >= n   評論數
111     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
112     (((MemberExpression)expression.Left).Member.Name == "Comments"))
113     {
114         if (expression.Right.NodeType == ExpressionType.Constant)
115             _criteria.MinComments =
116                 (int)((ConstantExpression)expression.Right).Value;
117 
118         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
119             _criteria.MinComments = 
120                 (int)GetMemberValue((MemberExpression)expression.Right);
121 
122         else
123             throw new NotSupportedException("Expression type not supported for Comments: "
124                 + expression.Right.NodeType.ToString());
125     }
126     // 處理 釋出時間>=
127     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
128     (((MemberExpression)expression.Left).Member.Name == "Published"))
129     {
130         if (expression.Right.NodeType == ExpressionType.Constant)
131             _criteria.Start = 
132                 (DateTime)((ConstantExpression)expression.Right).Value;
133 
134         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
135             _criteria.Start = 
136                 (DateTime)GetMemberValue((MemberExpression)expression.Right);
137 
138         else
139             throw new NotSupportedException("Expression type not supported for Published: " 
140                 + expression.Right.NodeType.ToString());
141     }
142 }
143 
144 // 訪問 小於和小於等於
145 private void VisitLessThanOrEqual(BinaryExpression expression)
146 {
147     // 處理 Diggs <= n  推薦人數
148     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
149         (((MemberExpression)expression.Left).Member.Name == "Diggs"))
150     {
151         if (expression.Right.NodeType == ExpressionType.Constant)
152             _criteria.MaxDiggs =
153                 (int)((ConstantExpression)expression.Right).Value;
154 
155         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
156             _criteria.MaxDiggs =
157                 (int)GetMemberValue((MemberExpression)expression.Right);
158 
159         else
160             throw new NotSupportedException("Expression type not supported for Diggs: " 
161                 + expression.Right.NodeType.ToString());
162     }
163     // 處理 Views<= n   訪問人數
164     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
165     (((MemberExpression)expression.Left).Member.Name == "Views"))
166     {
167         if (expression.Right.NodeType == ExpressionType.Constant)
168             _criteria.MaxViews = 
169                 (int)((ConstantExpression)expression.Right).Value;
170 
171         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
172             _criteria.MaxViews =
173                 (int)GetMemberValue((MemberExpression)expression.Right);
174 
175         else
176             throw new NotSupportedException("Expression type not supported for Views: " 
177                 + expression.Right.NodeType.ToString());
178     }
179     // 處理 comments <= n   評論數
180     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
181     (((MemberExpression)expression.Left).Member.Name == "Comments"))
182     {
183         if (expression.Right.NodeType == ExpressionType.Constant)
184             _criteria.MaxComments =
185                 (int)((ConstantExpression)expression.Right).Value;
186 
187         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
188             _criteria.MaxComments = 
189                 (int)GetMemberValue((MemberExpression)expression.Right);
190 
191         else
192             throw new NotSupportedException("Expression type not supported for Comments: "
193                 + expression.Right.NodeType.ToString());
194     }
195 
196         // 處理髮布時間 <= 
197     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
198     (((MemberExpression)expression.Left).Member.Name == "Published"))
199     {
200         if (expression.Right.NodeType == ExpressionType.Constant)
201             _criteria.End = 
202                 (DateTime)((ConstantExpression)expression.Right).Value;
203 
204         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
205             _criteria.End =
206                 (DateTime)GetMemberValue((MemberExpression)expression.Right);
207 
208         else
209             throw new NotSupportedException("Expression type not supported for Published: " 
210                 + expression.Right.NodeType.ToString());
211     }
212 }
213 
214 // 訪問 方法呼叫
215 private void VisitMethodCall(MethodCallExpression expression)
216 {
217     if ((expression.Method.DeclaringType == typeof(Queryable)) &&
218         (expression.Method.Name == "Where"))
219     {
220         VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
221     }
222     else if ((expression.Method.DeclaringType == typeof(String)) &&
223         (expression.Method.Name == "Contains"))
224     {
225         // Handle Title.Contains("")
226         if (expression.Object.NodeType == ExpressionType.MemberAccess)
227         {
228             MemberExpression memberExpr = (MemberExpression)expression.Object;
229             if (memberExpr.Expression.Type == typeof(Post))
230             {
231                 if (memberExpr.Member.Name == "Title")
232                 {
233                     Expression argument;
234                     argument = expression.Arguments[0];
235                     if (argument.NodeType == ExpressionType.Constant)
236                     {
237                         _criteria.Title = 
238                             (String)((ConstantExpression)argument).Value;
239                     }
240                     else if (argument.NodeType == ExpressionType.MemberAccess)
241                     {
242                         _criteria.Title = 
243                             (String)GetMemberValue((MemberExpression)argument);
244                     }
245                     else
246                     {
247                         throw new NotSupportedException("Expression type not supported: " 
248                             + argument.NodeType.ToString());
249                     }
250                 }
251             }
252         }
253     }
254     else
255     {
256         throw new NotSupportedException("Method not supported: "
257             + expression.Method.Name);
258     }
259 }
260 
261 // 獲取屬性值
262 private Object GetMemberValue(MemberExpression memberExpression)
263 {
264     MemberInfo memberInfo;
265     Object obj;
266 
267     if (memberExpression == null)
268         throw new ArgumentNullException("memberExpression");
269 
270 
271     if (memberExpression.Expression is ConstantExpression)
272         obj = ((ConstantExpression)memberExpression.Expression).Value;
273     else if (memberExpression.Expression is MemberExpression)
274         obj = GetMemberValue((MemberExpression)memberExpression.Expression);
275     else
276         throw new NotSupportedException("Expression type not supported: "
277             + memberExpression.Expression.GetType().FullName);
278 
279     memberInfo = memberExpression.Member;
280     if (memberInfo is PropertyInfo)
281     {
282         PropertyInfo property = (PropertyInfo)memberInfo;
283         return property.GetValue(obj, null);
284     }
285     else if (memberInfo is FieldInfo)
286     {
287         FieldInfo field = (FieldInfo)memberInfo;
288         return field.GetValue(obj);
289     }
290     else
291     {
292         throw new NotSupportedException("MemberInfo type not supported: " 
293             + memberInfo.GetType().FullName);
294     }
295 }
296 }

實現CnblogsQueryProvider

  有了上面的訪問類之後,我們的CnblogsQueryProvider就非常簡單了。

 1 public class CnblogsQueryProvider:QueryProvider
 2 {
 3     public override String GetQueryText(Expression expression)
 4     {
 5         SearchCriteria criteria;
 6 
 7         // 翻譯查詢條件
 8         criteria = new PostExpressionVisitor().ProcessExpression(expression);
 9 
10         // 生成URL
11         String url = PostHelper.BuildUrl(criteria);
12 
13         return url;
14     }
15 
16     public override object Execute(Expression expression)
17     {
18         String url = GetQueryText(expression);
19         IEnumerable<Post> results = PostHelper.PerformWebQuery(url);
20         return results;
21     }
22 }

  我們裡面只覆蓋了基類的兩個方法,GetQueryText和Execute。

  • GetQueryText根據訪問類得到的SearchCriteria去和成訪問Service的Url
  • Execute訪問則負責去請求這個Url拿到資料返回即可

PostHelper請求資料

  我們這裡面有一個幫助類PostHelper,就負責了根據criteria生成Url以及請求Url獲取資料的功能。

 1 static internal string BuildUrl(SearchCriteria criteria,string url=null)
 2 {
 3     if (criteria == null)
 4         throw new ArgumentNullException("criteria");
 5 
 6     var sbUrl = new StringBuilder(url ?? "http://linqtocnblogs.cloudapp.net/");
 7     var sbParameter = new StringBuilder();
 8 
 9     if (!String.IsNullOrEmpty(criteria.Title))
10         AppendParameter(sbParameter, "Title", criteria.Title);
11                 
12     if (!String.IsNullOrEmpty(criteria.Author))
13         AppendParameter(sbParameter, "Author", criteria.Author);
14 
15     if (criteria.Start.HasValue)
16         AppendParameter(sbParameter, "Start", criteria.Start.Value.ToString());
17 
18     if (criteria.End.HasValue)
19         AppendParameter(sbParameter, "End", criteria.End.Value.ToString());
20 
21     if (criteria.MinDiggs > 0)
22         AppendParameter(sbParameter, "MinDiggs", criteria.MinDiggs.ToString());
23 
24     if (criteria.MinViews > 0)
25         AppendParameter(sbParameter, "MinViews", criteria.MinViews.ToString());
26 
27     if (criteria.MinComments> 0)
28         AppendParameter(sbParameter, "MinComments",
29             criteria.MinComments.ToString());
30 
31     if (criteria.MaxDiggs > 0)
32         AppendParameter(sbParameter, "MaxDiggs", criteria.MaxDiggs.ToString());
33 
34     if (criteria.MaxViews > 0)
35         AppendParameter(sbParameter, "MaxViews", criteria.MaxViews.ToString());
36 
37     if (criteria.MaxComments > 0)
38         AppendParameter(sbParameter, "MaxComments",
39             criteria.MaxComments.ToString());
40 
41     if (sbParameter.Length > 0)
42         sbUrl.AppendFormat("?{0}", sbParameter.ToString());
43 
44     return sbUrl.ToString();
45 }
46 
47 static internal void AppendParameter(StringBuilder sb,string name,string value)
48 {
49     if (sb.Length > 0)
50         sb.Append("&");
51 
52     sb.AppendFormat("{0}={1}",name,value);
53 }
54 
55 static internal IEnumerable<Post> PerformWebQuery(string url)
56 {
57     var request = WebRequest.Create(url);
58     request.Credentials = CredentialCache.DefaultCredentials;
59 
60     var response = (HttpWebResponse)request.GetResponse();
61     using(var reader= new StreamReader(response.GetResponseStream()))
62     {
63         var body = reader.ReadToEnd();
64         return JsonConvert.DeserializeObject<List<Post>>(body);
65     }
66 }
67 }

因為我們的Service是返回的Json資料,所以這裡,我們藉助了Json.Net將其轉成我們所要的List<Post>的資料。

就是這麼簡單,我們的Linq to cnblogs就大功告成了。

  點選這裡下載原始碼:http://pan.baidu.com/s/1gd85l1T