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

本文通过具体实例介绍了Linq的基本用法,包括查询表达式的参数传递、非泛型集合查询、排序、分组及联结等操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

理论部分也聊了好几篇了,从今天开始我们就来进行一些实例,看到代码才心安点,呵呵。这个例子将贯穿本系列的后续所有篇章。

 

以博客园为例建模:

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

 

下面是代码

ContractedBlock.gif ExpandedBlockStart.gif User类
public class User
ExpandedBlockStart.gifContractedBlock.gif    
{
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 用户编号
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public int UserId getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 博客园Id
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public string UserName getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 密码
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public string Password getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 博客名称
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public string BlogName getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 角色
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public Role Role getset; }
}
ContractedBlock.gif ExpandedBlockStart.gif Role类
public class Role
ExpandedBlockStart.gifContractedBlock.gif
{
ExpandedSubBlockStart.gifContractedSubBlock.gif   
public int RoleId getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif   
public string RoleName getset; }
}

ContractedBlock.gif ExpandedBlockStart.gif Post类
public class Post
ExpandedBlockStart.gifContractedBlock.gif    
{
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 帖子Id
        
/// </summary>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public int PostId getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 标题
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public string Title getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 摘要
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public string Abstract getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 帖子内容
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public string Body getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 所属博客
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public string UserName getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 点击率
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public int Click getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 评论数
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public int Comments getset; }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 分类Id
        
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif        public int CategoryId getset; }
}

现在假设博客园程序启动的时候将数据库所有数据读入到内存中(多么荒谬啊,呵呵,仅仅是个假设),填充到上面这些对象里,那我们在内存中就有了这些集合: 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)
ExpandedBlockStart.gifContractedBlock.gif
{
    
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)
ExpandedBlockStart.gifContractedBlock.gif
{
    
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 子句里我们可以访问grouping Key ,并可以对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处理字符串了,有意思吧。

 

祝编程愉快


转载于:https://www.cnblogs.com/yuyijq/archive/2008/07/24/1250056.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值