LINQ之路14:LINQ Operators之排序和分组(Ordering and Grouping)
本篇继续LINQ Operators的介绍,这里要讨论的是LINQ中的排序和分组功能。LINQ的排序操作符有:OrderBy, OrderByDescending, ThenBy, 和ThenByDescending,他们返回input sequence的排序版本。分组操作符GroupBy把一个平展的输入sequence进行分组存放到输出sequence中。
排序/Ordering
IEnumerable<TSource>→IOrderedEnumerable<TSource>
Operator |
说明 |
SQL语义 |
OrderBy, ThenBy |
对一个sequence按升序排序 |
ORDER BY ... |
OrderByDescending, ThenByDescending |
对一个sequence按降序排序 |
ORDER BY ... DESC |
Reverse |
按倒序返回一个sequence |
Exception thrown |
排序操作符以不同顺序返回相同的elements。
OrderBy, OrderByDescending, ThenBy, 和ThenByDescending
OrderBy和OrderByDescending的参数
参数 |
类型 |
Input sequence |
IEnumerable<TSource> |
键选择器/Key selector |
TSource => TKey |
Return type = IOrderedEnumerable<TSource>
ThenBy和ThenByDescending参数
参数 |
类型 |
Input sequence |
IOrderedEnumerable <TSource> |
键选择器/Key selector |
TSource => TKey |
查询表达式语法
orderby expression1 [descending] [, expression2 [descending] ... ]
简介
OrderBy返回input sequence的排序版本,使用键选择器来进行排序比较。请看下面的示例:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; //对names sequence按字母顺序排序:
IEnumerable<string> query = names.OrderBy (s => s);
// Result: { "Dick", "Harry", "Jay", "Mary", "Tom" }; //按姓名长度进行排序
IEnumerable<string> query = names.OrderBy (s => s.Length);
// Result: { "Jay", "Tom", "Mary", "Dick", "Harry" };
对于拥有相同排序键值的elements来说,他们的相对位置是不确定的,比如上例按姓名长度排序的查询,Jay和Tom、Mary和Dick,除非我们添加额外的ThenBy运算符:
IEnumerable<string> query = names.OrderBy(s => s.Length).ThenBy(s => s);
// Result: { "Jay", "Tom", "Dick", "Mary", "Harry" };
ThenBy只会对那些在前一次排序中拥有相同键值的elements进行重新排序,我们可以连接任意数量的ThenBy运算符:
// 先按长度排序,然后按第二个字符排序,再按第一个字符排序
IEnumerable<string> query =
names.OrderBy (s => s.Length).ThenBy (s => s[]).ThenBy (s => s[]); // 对应的查询表达式语法为
IEnumerable<string> query =
from s in names
orderby s.Length, s[], s[]
select s;
LINQ也提供了OrderByDescending和ThenByDescending运算符,用来按降序排列一个sequence。下面的LINQ-to-db查询获取的purchases先按price降序排列,对于相同的price则按Description字母顺序排列:
var query = dataContext.Purchases.OrderByDescending (p => p.Price)
.ThenBy (p => p.Description); // 查询表达式语法
var query = from p in dataContext.Purchases
orderby p.Price descending, p.Description
select p;
比较器(Comparers)和排序规则(collations)
对一个本地查询,键选择器对象本身通过其默认的IComparable实现决定了排序算法,我们可以传入一个IComparer对象来重载该排序算法。
// 排序时忽略大小写
names.OrderBy (n => n, StringComparer.CurrentCultureIgnoreCase);
查询表达式语法并不支持传入comparer的做法,LINQ to SQL和EF也没有任何方式来支持此功能。当我们查询一个数据库时,排序算法由排序列的collation(排序规则)决定。如果collation是大小写敏感的,我们可以通过在键选择器上调用ToUpper来获得忽略大小写的排序:
var query = from p in dataContext.Purchases
orderby p.Description.ToUpper()
select p;
IOrderedEnumerable和IOrderedQueryable
排序运算符 返回IEnumerable<T>的一个特殊子类型。Enumerable中的排序运算符返回
IOrderedEnumerable;Queryable中的排序运算符返回IOrderedQueryable。这些子类型允许随后的ThenBy运算符来进一步调整现有的排序,他们中定义的其他成员并没有对用户公开,所以他们看起来就像普通的sequence。仅当我们渐进的创建查询时他们的区别才会显现出来:
IOrderedEnumerable<string> query1 = names.OrderBy(s => s.Length);
IOrderedEnumerable<string> query2 = query1.ThenBy(s => s);
如果我们使用IEnumerable<string>来声明query1,第二行就会编译错误,因为ThenBy需要一个IOrderedEnumerable<string>的输入类型。我们可以通过隐式类型变量来避免这种错误:
var query1 = names.OrderBy(s => s.Length);
var query2 = query1.ThenBy(s => s);
尽管如此,隐式类型有时候也会有其自身的问题,比如下面的查询就不能编译:
var query = names.OrderBy(s => s.Length);
query = query.Where(n => n.Length > ); // Compile-time error
基于OrderBy的输出类型,编译器推断出query的类型为IOrderedEnumerable<string>。但是下一行中的Where返回一个正常的IEnumerable<string>,所以它已不能重新赋值给query了。我们可以通过显示类型定义或在OrderBy之后调用AsEnumerable()来作为一种变通的方案:
var query = names.OrderBy(s => s.Length).AsEnumerable();
query = query.Where(n => n.Length > ); // OK
相应的,针对解释查询,我们需要调用AsQueryable。
分组/Grouping
IEnumerable<TSource>→IEnumerable<IGrouping<TSource,TElement>>
Operator |
说明 |
SQL语义 |
GroupBy |
对一个sequence进行分组 |
GROUP BY |
GroupBy
参数 |
类型 |
Input sequence |
IEnumerable<TSource> |
键选择器/Key selector |
TSource => TKey |
元素选择器/Element selector(optional) |
TSource => TElement |
比较器/Comparer (optional) |
IEqualityComparer<TKey> |
查询表达式语法
group element-expression by key-expression
简介
GroupBy把一个平展的输入sequence进行分组存放到输出sequence中,比如下面的示例对C:\temp目录下的文件按扩展名进行分组:
string[] files = Directory.GetFiles("c:\\temp"); IEnumerable<IGrouping<string, string>> query =
files.GroupBy(file => Path.GetExtension(file)); // 使用匿名类型来存储结果
var query2 = files.GroupBy(file => Path.GetExtension(file)); // 遍历结果的方式
foreach (IGrouping<string, string> grouping in query)
{
Console.WriteLine("Extension: " + grouping.Key);
foreach (string filename in grouping)
Console.WriteLine(" - " + filename);
} // Result:
Extension: .pdf
- chapter03.pdf
- chapter04.pdf
Extension: .doc
- todo.doc
- menu.doc
- Copy of menu.doc
...
Enumerable.GroupBy会读取每一个输入element,把他们存放到一个临时的列表dictionary,所有具有相同key的元素会被存入同一个子列表。然后返回一个分组(grouping)sequence,一个分组是一个带有Key属性的sequence:
public interface IGrouping<TKey, TElement>
: IEnumerable<TElement>, IEnumerable
{
TKey Key { get; } // 一个subsequence共享一个Key属性
}
默认情况下,每个分组里面的element都是没有经过转换的输入element,除非你指定了元素选择器参数。下面就把输入element转换到大写形式:
var query3 = files.GroupBy(
file => Path.GetExtension(file),
file => file.ToUpper());
元素选择器和键值选择器是互相独立的两个概念,上面的例子中,尽管分组中的元素是大写的,但是分组中的Key保持原来的大小写形式:
// Result:
Extension: .pdf
- chapter03.PDF
- chapter04.PDF
Extension: .doc
- todo.DOC
- menu.DOC
- Copy of menu.DOC
...
值得注意的是,分组中的子集合并没有进行排序的功能,他会保持原来的顺序。如果需要对结果排序,我们需要添加OrderBy运算符:
files.GroupBy(file => Path.GetExtension(file), file => file.ToUpper())
.OrderBy(grouping => grouping.Key);
GroupBy的查询表达式语法非常的简单和直接:group element-expression by key-expression
下面使用查询表达式重写上面的例子:
var query =
from file in files
group file.ToUpper() by Path.GetExtension(file);
和select一样,group也会结束一个查询,除非我们增加了一个可以继续查询的子句:
var query =
from file in files
group file.ToUpper() by Path.GetExtension(file) into grouping
orderby grouping.Key
select grouping;
续写查询对于group by运算符来说非常有用,因为我们很可能要对分组进行过滤等操作。
// 只选择元素数量小于3的分组
var query =
from file in files
group file.ToUpper() by Path.GetExtension(file) into grouping
where grouping.Count() <
select grouping;
group by之后的where子句相当于SQL中的HAVING,它会应用到整个分组或subsequence,而不是单个元素。
有时候,我们可能仅对分组的汇总感兴趣,所以我们可以丢弃subsequence:
string[] votes = { "Bush", "Gore", "Gore", "Bush", "Bush" };
IEnumerable<string> query = from vote in votes
group vote by vote into g
orderby g.Count() descending
select g.Key;
string winner = query.First(); // Bush
LINQ to SQL和EF中的GroupBy
Grouping在对数据库进行查询时其工作方式是一样的。但是如果设置了关联属性,你会发现group的使用几率不会像标准SQL中那么频繁,因为关联属性已经为我们实现了特定的分组功能。例如,我们想要选择至少有 两个purchases的customers,我们并不需要分组,下面的查询就可以工作得很好:
var query =
from c in dataContext.Customers
where c.Purchases.Count >=
select c.Name + " has made " + c.Purchases.Count + " purchases";
下面是一个使用group的例子:
// 对销售额按年份分组
var query = from p in dataContext.Purchases
group p.Price by p.Date.Year into salesByYear
select new {
Year = salesByYear.Key,
TotalValue = salesByYear.Sum()
};
按多键值分组
我们可以按一个复合键值进行分组,方式是使用一个匿名类型来表示这个键值:
// 对purchase按年月分组
var query = from p in dataContext.Purchases
group p by new { Year = p.Date.Year, Month = p.Date.Month };
至此,LINQ Operators我们已经介绍了过滤、数据转换、连接、排序和分组。关于LINQ Operators,在接下来的最后两篇中,会讨论其他还没讲述的运算符,包括:Set、Zip、转换方法、Element运算符、集合方法、量词(Quantifiers)生成方法(Generation Methods)。
LINQ之路14:LINQ Operators之排序和分组(Ordering and Grouping)的更多相关文章
- LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法
本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...
- LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法
本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators).Zip操作符.转换方法(Conversion Methods).生成器方法(Generation M ...
- LINQ之路11:LINQ Operators之过滤(Filtering)
在本系列博客前面的篇章中,已经对LINQ的作用.C# 3.0为LINQ提供的新特性,还有几种典型的LINQ技术:LINQ to Objects.LINQ to SQL.Entity Framework ...
- LINQ之路 7:子查询、创建策略和数据转换
在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍 ...
- LINQ之路 4:LINQ方法语法
书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression). LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方 ...
- LINQ之路(3):LINQ扩展
本篇文章将从三个方面来进行LINQ扩展的阐述:扩展查询操作符.自定义查询操作符和简单模拟LINQ to SQL. 1.扩展查询操作符 在实际的使用过程中,Enumerable或Queryable中的扩 ...
- .NET面试题系列[14] - LINQ to SQL与IQueryable
.NET面试题系列目录 名言警句 "理解IQueryable的最简单方式就是,把它看作一个查询,在执行的时候,将会生成结果序列." - Jon Skeet LINQ to Obje ...
- LINQ之路10:LINQ to SQL 和 Entity Framework(下)
在本篇中,我们将接着上一篇“LINQ to SQL 和 Entity Framework(上)”的内容,继续使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术 ...
- [转]LINQ之路系列博客导航
分享一个学习Linq的好博客:Linq之路
随机推荐
- python语法_input
input:与用户的交互,返回用户输入的值 注意:input接受的所有数据都为字符串,即便输入的为数字,依然会被当成字符串
- 四则运算第三次 PSP
- 常用C++面试总结
指定对齐值:#pragma pack(n),n=1,2,4,8,16改变系统的对齐系数struct和union都是由多个不同的数据类型成员组成, 但在任何同一时刻, union中只存放了一个被选中的成 ...
- H/s:哈希率单位转换
哈系率说明 挖矿能力是通过寻找矿工可以执行的地块的尝试次数来衡量的.每次尝试都包括创建一个唯一的块候选项,并通过SHA-256d(一种加密哈希函数)创建块候选项的摘要.或者,简而言之,哈希.由于这是一 ...
- InnoDB中锁的算法(2)
Ⅰ.上节回顾 session1: (root@localhost) [test]> select * from l; +---+------+------+------+ | a | b | c ...
- [redis] redis
redis是什么? wiki redis官方介绍:introduction to redis 安装: install 拉到最下面的install小节 wget http://download.redi ...
- sass基础学习(一)
移动端布局各种问题 pc端布局各种问题sass 组件模块化面向对象编程ajax 框架学习 webpack 打包 性能优化 gulp是基于Nodejs的自动任务运行器她能自动化地完成 javascrip ...
- android从IIS/asp.net下载apk文件
解决步骤: 1.web.config中 <configuration> <configSections> ... <section name="rewr ...
- openshift 容器云从入门到崩溃之六《Source-to-Image》
上次说到了怎么在oc上面部署应用而且说道了怎么定义模板部署应用,也许你会奇怪那个我代码打包编译在哪一步,那就要说道oc的s2i流程了 下面是基本s2i流程 1.制作base-image镜像 要使用s2 ...
- iDigital2019数字营销广告主峰会
iDigital 数字营销品牌广告主峰会是定向邀请形式的闭门社交峰会, 现已成长为享誉亚洲的营销广告行业活动, 致力于帮助品牌营销官, 决策者和业界同行在日益整合的数字商业时代下获得战略远景.组委会邀 ...