代码改变世界

走进Linq-Linq to Objects(下)实例篇

2008-07-24 09:04  横刀天笑  阅读(3262)  评论(20编辑  收藏  举报
理论部分也聊了好几篇了,从今天开始我们就来进行一些实例,看到代码才心安点,呵呵。这个例子将贯穿本系列的后续所有篇章。

 

以博客园为例建模:

博客园里每个用户有且仅有一个博客,为了简单每篇博客只能属于一个分类,每个用户有一个角色

 

下面是代码

User类
Role类

Post类

现在假设博客园程序启动的时候将数据库所有数据读入到内存中(多么荒谬啊,呵呵,仅仅是个假设),填充到上面这些对象里,那我们在内存中就有了这些集合: IList<User> users,IList<Post> posts,IList<Role> roles,我们现在使用Linq to Objects对这些集合进行各种操作

 

向查询表达式传入参数

现在有一个用户输入http://yuyijq.cnblogs.com,通过UrlRewrite,这个连接将转向到http://www.cnblogs.com/blog.aspx?u=yuyijq,那现在我们的程序要干些什么呢?首先根据yuyijq读取该博主的所有帖子。那就要从IList<Post>集合里查找出UserName”yuyijq”的所有帖子,但是显示在博客首页上的时候并不需要帖子的内容,如果把帖子内容也读取的话太耗资源了,只需要博客的标题,然后我们将这个只有标题和摘要的对象的集合绑定到一个GridView上:

var dataSource = from post in posts
              
where post.UserName == "yuyijq"
              select post.Title;
mainGridView.DataSource 
= dataSource;
mainGridView.DataBind();
一切就如此简单,我只需要告诉它我需要什么,而不需要告诉它如何去做(不需要使用foreach等遍历posts集合,然后判断其UserName是否相等)

但是很明显,这个查询是硬编码的,我直接将”yuyijq”传递到查询中,拜延迟计算所赐,我们可以在查询表达式里使用变量了:

string userName = Request.QueryString["u"];

var dataSource 
= from post in posts
               
where post.UserName == userName
               select post.Title;
mainGridView.DataSource 
= dataSource;
mainGridView.DataBind();

也许你看到上面的代码不会觉得有什么特别,如果我将代码稍微坐下修改:

string userName = "zhzkl";

var dataSource 
= from post in posts
              
where post.UserName == userName
              select post.Title;
userName 
= "yuyijq";
mainGridView.DataSource 
= dataSource;
mainGridView.DataBind();

你觉得最终GridView上绑定的应该是yuyijq的帖子还是zhzkl的帖子?实际执行点经过下面的代码:

var dataSource = from post in posts
              
where post.UserName == userName
              select post.Title;
的时候这个查询表达式并没有被执行,dataSource里装的也不是最终的结果,你可以理解为dataSource里放的是一个表达式,直到mainGridView.DataBind()的时候这个表达式才会被计算,所以上面的代码最终造成的结果是GridView上绑定的是yuyijq的帖子。

 

非泛型集合的查询

虽然今天C#已经发展到了3.0,但是在2.0里出现的泛型并没有得到全面的普及,很多开发者还是在程序里大量使用1.x里出现的一些非泛型集合,比如ArrayList就是个代表,那么如果存在这样一个集合我们怎么去查询:

ArrayList posts = dataBase.GetAllPosts();
我们知道ArrayList里是Post对象,但是还记得不?Linq里的那些Where啊,Select啊,这些方法都是针对IEnumerable<T>扩展的,而ArrayList实现的是IEmerable这个接口。别急,Linq已经为我们考虑到这点了:

var dataSource = from post in posts.Cast<Post>()
             
where post.UserName == userName
             select post.Title;
Cast<TResult>()方法是对IEnumerable扩展的一个方法,它专门就是干这种转型的事情的,它将遍历非泛型集合中的每个元素,然后把它转型为TResult类型,然后返回一个IEnumerable<TResult>对象,后面我们就可以使用Linq的其他扩展方法了。但是注意:如果你的非泛型集合里有一个无法转型到TResult类型的元素,那么就要抛出异常了,如果有一个null元素是不会抛出异常的,最终的元素也会是一个null。要不你来保险点,用OfType<TResult>方法:
var dataSource = from post in posts.OfType<Post>()
           
where post.UserName == userName
           select post.Title;
这个方法只会将非泛型集合中那些“是”TResult类型的元素返回来,其它的忽略(这个就不会抛出异常了)

 

排序

在数据驱动的应用中我们经常需要对数据根据一些属性进行排序,而通常这些排序的属性应该是用户可以自己设置的,比如博客,可以根据点击率排序,也可以根据评论排序,甚至两者都作为排序根据。还有什么顺序啊,倒序啊,也就是这个排序是个动态的。那我们是不是要写一大串if…else…进行判断,然后写不同的Linq表达式:

if(根据点击率排序){
    
return from post in posts
        
where post.Title ==”yuyijq”
              orderby post.Click
            select post;
}
else if(…){

}
如果是这样也太麻烦了,我们还是来看看OrderBy的方法原型:
OrderedSequence<TElement> OrderBy<TElement, TKey>(
this IEnumerable<TElement> source, Func<TElement, TKey> keySelector)
实际上OrderBy方法需要的就是一个排序关键字选择的delegate,输入进去一个集合元素,返回一个排序的关键字就ok了,像下面这样:
Func<TElement,TKey> selector = post => post.Click;
return from post in posts
   
where post.UserName = “yuyijq”
   orderby selector(post)
   select post;
那我们甚至可以封装一个排序的方法:
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;
}

可惜的是上面的写法行不通,在查询表达式里这样写是不行的,但我们可以使用方法调用的方式:
public IEnumerable<Post> Sort<TKey>(string userName,Func<Post,TKey> selector,bool isAsc)
{
    var newPosts 
= from post in posts
             
where post.UserName == userName
             select post;
    
return isAsc ?newPosts.Orderby(selector(post)):newPosts. OrderByDescending(selector(post));
}
你看,灵活的将方法调用和查询表达式组合,我们能应付很多情况。因为查询表达式有它特定的语法约束,所以有的时候并不能非常动态,这个时候我们可以配合方法调用的方式,如此一组合方便多了。那要是我想根据评论数目排序后再根据点击率排序呢?放两个关键字就可以了:
var dataSource = from post in posts
             
where post.UserName == "yuyijq"
             orderby post.Click,post.Comments
             select post.Title;
实际上orderby post.Click,post.Comments这里会生成:
posts.OrderBy(post=>post.Click).ThenBy(post=>post.Comments);
 

分组

在上一篇中GroupBy对应的查询表达式也非常麻烦,下面就来瞧瞧。我们要对博客园上所有文章做个分组,分组依据就根据博客的用户名好了:

var dataSource = from post in posts
          group post.Title by post.UserName;
可我们要是想根据博客的用户名和博客的分类进行分组怎么办?
var dataSource = from post in posts
           group post.Title by post.UserName,post.CategoryId;
这样是行不通的,编译都不通过,像下面这样就可以了:
var dataSource = from post in posts
             group post.Title by 
new { post.UserName, post.CategoryId };
用一个匿名类型进行group(当然,你使用一个具名类型也是可以的);
foreach(var item in dataSource)

{

       //在遍历这个dataSource的时候,item有一个属性Key,这个Key就代表by后面的东西

}
如果像上面那样,这个查询表达式就要到这里终止了,还有这样一种方式:

var dataSource = from post in posts
                         group post by post.UserName into grouping
                         select 
new { 
                            Key 
= grouping.Key,
                            Value 
= grouping
                         };
into子句又引入了一个变量,这个变量可以在后面的select子句里使用,在select子句里我们可以访问groupingKey,并可以对grouping进行一些统计,比如Sum啊:
var dataSource = from post in posts
                         group post by post.UserName into grouping
                         select 
new { 
                            Key 
= grouping.Key,
                            TotalClick 
= grouping.Sum(post=>post.Click)
                         };
这个TotalClick就可以统计出每个博客的所有点击数了。

 

联结

Join也是个蛮复杂的表达式

如果给你一个UserId,你要根据UserIdusers集合里先找出UserName,然后根据UserName找出所有下面的Post

var dataSource = from post in posts
             join user 
in users on post.UserName equals user.UserName
             
where user.UserId = 2
             select post;

注意,联结的条件不能用”==”而应该用equals

 

 

总结

本篇文章用实例对一些重点进行了举例说明,当然还有一些更多的用法没有提及,不过对于这些东西大家还是多做练习才能灵活运用。还有Linq的性能的问题,现在很多人担心Linq的性能,确实,Linq的性能还赶不上传统写法的性能。不过在某一些方面它们还是相差不大。但用Linq写一些代码的时候,真的可以获得意想不到的速率,比如有的时候需要对几个集合的数据进行联结或者分组,用传统的做法可能要嵌套几个循环,还可能要创建临时的集合,非常繁琐,如果用Linq则不一样了,Linq不仅仅是一项技术,还是一种编程的风格,至于性能的问题我觉得Rails之父说的那句话很经典:当性能未成为问题之前它永远不是个问题。欢迎大家提出问题,我会对文章进行更新。

 

PS2.o里,string类实现了IEnumerable<char>接口,现在你都可以用Linq处理字符串了,有意思吧。

 

祝编程愉快