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之路
随机推荐
- elk-准备(一)
一.在搭建elk之前需要做准备工作 1.创建elk用户 groupadd elk -g 1001 useradd elk -m -d /home/elk -s /bin/bash -g 1001 -u ...
- history.back();谷歌浏览器,iframe后退问题
history.back();谷歌浏览器,iframe后退直接会后退父页面. 使用以下方式即可//document.referrer是获取上一页的urllocation.href=document.r ...
- HDU 1010生成树
求起点到终点的最短权值和
- 【实战】Docker入门实践二:Docker服务基本操作 和 测试Hello World
操作环境 操作系统:CentOS7.2 内存:1GB CPU:2核 Docker服务常用命令 docker服务操作命令如下 service docker start #启动服务 service doc ...
- PrimeNG之TreeTable
--treetable用于显示分层数据表格的格式 Import import {TreeTableModule,TreeNode,SharedModule} from 'primeng/primeng ...
- 洛谷P4396 作业 [AHOI2013] 莫队
正解:莫队 解题报告: 传送门! 天呐太久没做莫队了连板子都认不出来了,,,所以复健下做下莫队的题目QAQ 就很板子鸭,和莫队板子比好像只有一个离散化,,,?就不讲了QAQ 等下直接放代码QAQ ov ...
- POJ 3264 线段树入门解题报告
题意:给n个值, Q次询问, 每次询问给定一个区间, 要求输出该区间最大最小值之差 思路:暴力的话每次询问都要遍历多次for循环一定会超时, 用线段树记录区间的信息(左边界右边界, 该区间最大值最小值 ...
- ASM磁盘组剔盘、加盘实施过程
Task:从一个ASM磁盘组中剔除一块盘,加入到另一个ASM磁盘组. 环境:AIX6.1 + Oracle RAC 11.2.0.3 前期准备: 1.查看DG磁盘组空间情况: --查看DG磁盘组空间情 ...
- laravel框架基础(2)---laravel项目加载机制
当我们,通过浏览器请求laravel的时候 laravel就会根据我们的请求链接来选择对应的方法执行并返回我们所需要的实际结果. 那么这个过程是怎样的呢? 1.生命周期 2018-12-28 17:0 ...
- git之关联远程仓库
1.创建本地仓库. 2.与远程仓库交互. ----------------------------------------------------------------------------