理論部分也聊了好幾篇了,從今天開始我們就來進行一些實例,看到代碼才 心安點,呵呵。這個例子將貫穿本系列的後續所有篇章。
以博客園為例 建模:
博客園裡每個用戶有且僅有一個博客,為了簡單每篇博客只能屬 於一個分類,每個用戶有一個角色
下面是代碼
public class User
{
/**//// <summary>
/// 用戶編號
/// </summary>
public int UserId { get; set; }
/**//// <summary>
/// 博客園Id
/// </summary>
public string UserName { get; set; }
/**//// <summary>
/// 密碼
/// </summary>
public string Password { get; set; }
/**//// <summary>
/// 博客名稱
/// </summary>
public string BlogName { get; set; }
/**//// <summary>
/// 角色
/// </summary>
public Role Role { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string RoleName { get; set; }
}
public class Post
{
/**//// <summary>
/// 帖子Id
/// </summary>
public int PostId { get; set; }
/**//// <summary>
/// 標題
/// </summary>
public string Title { get; set; }
/**//// <summary>
/// 摘要
/// </summary>
public string Abstract { get; set; }
/**//// <summary>
/// 帖子內容
/// </summary>
public string Body { get; set; }
/**//// <summary>
/// 所屬博客
/// </summary>
public string UserName { get; set; }
/**//// <summary>
/// 點擊率
/// </summary>
public int Click { get; set; }
/**//// <summary>
/// 評論數
/// </summary>
public int Comments { get; set; }
/**//// <summary>
/// 分類Id
/// </summary>
public int CategoryId { get; set; }
}
現在假設博客園程序啟動的時候將數據庫所有數據讀入到內存中 (多麼荒謬啊,呵呵,僅僅是個假設),填充到上面這些對象裡,那我們在內存中 就有了這些集合: IList users,IList posts,IList roles,我們現在使用Linq to Objects對這些集合進行各種操作
向查詢表達式傳入參數
現在 有一個用戶輸入http://yuyijq.cnblogs.com,通過UrlRewrite,這個連接將轉 向到http://www.cnblogs.com/blog.aspx?u=yuyijq,那現在我們的程序要干些什 麼呢?首先根據yuyijq讀取該博主的所有帖子。那就要從IList集合裡查找出 UserName為”yuyijq”的所有帖子,但是顯示在博客首頁上的時候並 不需要帖子的內容,如果把帖子內容也讀取的話太耗資源了,只需要博客的標題 ,然後我們將這個只有標題和摘要的對象的集合綁定到一個GridView上:
vardataSource=frompostinposts
wherepost.UserName=="yuyijq"
selectpost.Title;
mainGridView.DataSource=dataSource;
mainGridView.DataBind();
一切就如此簡單,我只需要告訴它 我需要什麼,而不需要告訴它如何去做(不需要使用foreach等遍歷posts集合, 然後判斷其UserName是否相等)。
但是很明顯,這個查詢是硬編碼的,我 直接將”yuyijq”傳遞到查詢中,拜延遲計算所賜,我們可以在查詢 表達式裡使用變量了:
stringuserName=Request.QueryString ["u"];
vardataSource=frompostinposts
wherepost.UserName==userName
selectpost.Title;
mainGridView.DataSource=dataSource;
mainGridView.DataBind ();
也許你看到上面的代碼不會覺得有什麼特別,如果我將代碼 稍微坐下修改:
stringuserName="zhzkl";
vardataSource=frompostinposts
wherepost.UserName==userName
selectpost.Title;
userName="yuyijq";
mainGridView.DataSource=dataSource;
mainGridView.DataBind ();
你覺得最終GridView上綁定的應該是yuyijq的帖子還是zhzkl 的帖子?實際執行點經過下面的代 碼:/p>
vardataSource=frompostinposts
wherepost.UserName==userName
selectpost.Title;
的時候這個查詢表達式並沒有被執行, dataSource裡裝的也不是最終的結果,你可以理解為dataSource裡放的是一個表 達式,直到mainGridView.DataBind()的時候這個表達式才會被計算,所以上面 的代碼最終造成的結果是GridView上綁定的是yuyijq的帖子。
非泛型集 合的查詢
雖然今天C#已經發展到了3.0,但是在2.0裡出現的泛型並沒有 得到全面的普及,很多開發者還是在程序裡大量使用1.x裡出現的一些非泛型集 合,比如ArrayList就是個代表,那麼如果存在這樣一個集合我們怎麼去查詢:
ArrayListposts=dataBase.GetAllPosts();
我們知道ArrayList 裡是Post對象,但是還記得不?Linq裡的那些Where啊,Select啊,這些方法都 是針對IEnumerable擴展的,而ArrayList實現的是IEmerable這個接口。別急, Linq已經為我們考慮到這點了:
vardataSource=frompostinposts.Cast<Post>()
wherepost.UserName==userName
selectpost.Title;
Cast()方法是對IEnumerable擴展的一個方法 ,它專門就是干這種轉型的事情的,它將遍歷非泛型集合中的每個元素,然後把 它轉型為TResult類型,然後返回一個IEnumerable對象,後面我們就可以使用 Linq的其他擴展方法了。但是注意:如果你的非泛型集合裡有一個無法轉型到 TResult類型的元素,那麼就要拋出異常了,如果有一個null元素是不會拋出異 常的,最終的元素也會是一個null。要不你來保險點,用OfType方 法:
vardataSource=frompostinposts.OfType<Post>()
wherepost.UserName==userName
selectpost.Title;
這個方法只會將非泛型集合中那些“是 ”TResult類型的元素返回來,其它的忽略(這個就不會拋出異常了)。
排序
在數據驅動的應用中我們經常需要對數據根據一些屬性進行 排序,而通常這些排序的屬性應該是用戶可以自己設置的,比如博客,可以根據 點擊率排序,也可以根據評論排序,甚至兩者都作為排序根據。還有什麼順序啊 ,倒序啊,也就是這個排序是個動態的。那我們是不是要寫一大串 if…else…進行判斷,然後寫不同的Linq表達式:
if(根據點擊率排序){
returnfrompostinposts
wherepost.Title==”yuyijq”
orderbypost.Click
selectpost;
}elseif(…) {
…
}
如果是這樣也太麻煩了,我們還是來看 看OrderBy的方法原型:
OrderedSequence<TElement>OrderBy<TElement,TKey> (
thisIEnumerable<TElement>source,Func<TElement,TKey> ;keySelector)
實際上OrderBy方法需要的就是一個排序關鍵字選擇的 delegate,輸入進去一個集合元素,返回一個排序的關鍵字就ok了,像下面這樣 :
Func<TElement,TKey>selector=post=>post.Click;
returnfrompostinposts
wherepost.UserName=“yuyijq”
orderbyselector(post)
selectpost;
那我們甚至可以封裝一個排序的方法:
public IEnumerable<Post> Sort<TKey>(string userName,Func<Post,TKey> selector)
用 戶需要定制的選擇是升序排序還是降序排序這個咋整?那弄個條件判斷呗:
{
return from post in posts
where post.UserName == userName
orderby selector(post)
select post;
}
public IEnumerable<Post> Sort<TKey>(string userName,Func<Post,TKey> selector,bool isAsc)
{
return from post in posts
where post.UserName == userName
isAsc ?Orderby selector(post):orderby selector (post) des
select post;
}
可惜的是上面的寫法行不通,在查詢表達式裡這樣寫是不行的,但我們可以 使用方法調用的方式:
publicIEnumerable<Post>Sort<TKey> (stringuserName,Func<Post,TKey>selector,boolisAsc)
{
varnewPosts=frompostinposts
wherepost.UserName==userName
selectpost;
returnisAsc?newPosts.Orderby(selector (post)):newPosts.OrderByDescending(selector(post));
}
你看,靈活的將方法調用和查詢表達式組合,我們能應付很多情 況。因為查詢表達式有它特定的語法約束,所以有的時候並不能非常動態,這個 時候我們可以配合方法調用的方式,如此一組合方便多了。那要是我想根據評論 數目排序後再根據點擊率排序呢?放兩個關鍵字就可以了:
vardataSource=frompostinposts
wherepost.UserName=="yuyijq"
orderbypost.Click,post.Comments
selectpost.Title;
實際上orderby post.Click,post.Comments 這裡會生成:
posts.OrderBy(post=>post.Click).ThenBy (post=>post.Comments);
分組
在上一篇中GroupBy對 應的查詢表達式也非常麻煩,下面就來瞧瞧。我們要對博客園上所有文章做個分 組,分組依據就根據博客的用戶名好了:
vardataSource=frompostinposts
grouppost.Titlebypost.UserName;
可我們要是想根據博客的用 戶名和博客的分類進行分組怎麼辦?
vardataSource=frompostinposts
grouppost.Titlebypost.UserName,post.CategoryId;
這樣是行 不通的,編譯都不通過,像下面這樣就可以了:
vardataSource=frompostinposts
grouppost.Titlebynew{post.UserName,post.CategoryId};
用一 個匿名類型進行group(當然,你使用一個具名類型也是可以的);
{
//在遍歷這個dataSource的時候,item有一個屬性Key,這個 Key就代表by後面的東西
}
如果像上面那樣,這 個查詢表達式就要到這裡終止了,還有這樣一種方式:
vardataSource=frompostinposts
grouppostbypost.UserNameintogrouping
selectnew{
Key=grouping.Key,
Value=grouping
};
nto子句又引入了一個變量,這個變量可以在後面的select子 句裡使用,在select子句裡我們可以訪問grouping的Key,並可以對grouping進 行一些統計,比如Sum啊:
vardataSource=frompostinposts
grouppostbypost.UserNameintogrouping
selectnew{
Key=grouping.Key,
TotalClick=grouping.Sum(post=>post.Click)
};
這個TotalClick就可以統計出每個博客的所有點擊數了。
聯結
Join也是個蠻復雜的表達式
如果給你一個UserId, 你要根據UserId從users集合裡先找出UserName,然後根據UserName找出所有下 面的Post:
vardataSource=frompostinposts
joinuserinusersonpost.UserNameequalsuser.UserName
whereuser.UserId=2
selectpost;
注意,聯 結的條件不能用”==”而應該用equals。
總結
本篇文 章用實例對一些重點進行了舉例說明,當然還有一些更多的用法沒有提及,不過 對於這些東西大家還是多做練習才能靈活運用。還有Linq的性能的問題,現在很 多人擔心Linq的性能,確實,Linq的性能還趕不上傳統寫法的性能。不過在某一 些方面它們還是相差不大。但用Linq寫一些代碼的時候,真的可以獲得意想不到 的速率,比如有的時候需要對幾個集合的數據進行聯結或者分組,用傳統的做法 可能要嵌套幾個循環,還可能要創建臨時的集合,非常繁瑣,如果用Linq則不一 樣了,Linq不僅僅是一項技術,還是一種編程的風格,至於性能的問題我覺得 Rails之父說的那句話很經典:當性能未成為問題之前它永遠不是個問題。歡迎 大家提出問題,我會對文章進行更新。
PS:2.o裡,string類實現了 IEnumerable接口,現在你都可以用Linq處理字符串了,有意思吧。