本篇继续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)的更多相关文章

  1. LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法

    本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...

  2. LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法

    本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators).Zip操作符.转换方法(Conversion Methods).生成器方法(Generation M ...

  3. LINQ之路11:LINQ Operators之过滤(Filtering)

    在本系列博客前面的篇章中,已经对LINQ的作用.C# 3.0为LINQ提供的新特性,还有几种典型的LINQ技术:LINQ to Objects.LINQ to SQL.Entity Framework ...

  4. LINQ之路 7:子查询、创建策略和数据转换

    在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍 ...

  5. LINQ之路 4:LINQ方法语法

    书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression). LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方 ...

  6. LINQ之路(3):LINQ扩展

    本篇文章将从三个方面来进行LINQ扩展的阐述:扩展查询操作符.自定义查询操作符和简单模拟LINQ to SQL. 1.扩展查询操作符 在实际的使用过程中,Enumerable或Queryable中的扩 ...

  7. .NET面试题系列[14] - LINQ to SQL与IQueryable

    .NET面试题系列目录 名言警句 "理解IQueryable的最简单方式就是,把它看作一个查询,在执行的时候,将会生成结果序列." - Jon Skeet LINQ to Obje ...

  8. LINQ之路10:LINQ to SQL 和 Entity Framework(下)

    在本篇中,我们将接着上一篇“LINQ to SQL 和 Entity Framework(上)”的内容,继续使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术 ...

  9. [转]LINQ之路系列博客导航

    分享一个学习Linq的好博客:Linq之路

随机推荐

  1. python语法_input

    input:与用户的交互,返回用户输入的值 注意:input接受的所有数据都为字符串,即便输入的为数字,依然会被当成字符串

  2. 四则运算第三次 PSP

     

  3. 常用C++面试总结

    指定对齐值:#pragma pack(n),n=1,2,4,8,16改变系统的对齐系数struct和union都是由多个不同的数据类型成员组成, 但在任何同一时刻, union中只存放了一个被选中的成 ...

  4. H/s:哈希率单位转换

    哈系率说明 挖矿能力是通过寻找矿工可以执行的地块的尝试次数来衡量的.每次尝试都包括创建一个唯一的块候选项,并通过SHA-256d(一种加密哈希函数)创建块候选项的摘要.或者,简而言之,哈希.由于这是一 ...

  5. InnoDB中锁的算法(2)

    Ⅰ.上节回顾 session1: (root@localhost) [test]> select * from l; +---+------+------+------+ | a | b | c ...

  6. [redis] redis

    redis是什么? wiki redis官方介绍:introduction to redis 安装: install 拉到最下面的install小节 wget http://download.redi ...

  7. sass基础学习(一)

    移动端布局各种问题 pc端布局各种问题sass 组件模块化面向对象编程ajax 框架学习 webpack 打包 性能优化 gulp是基于Nodejs的自动任务运行器她能自动化地完成 javascrip ...

  8. android从IIS/asp.net下载apk文件

    解决步骤: 1.web.config中 <configuration>  <configSections> ...    <section name="rewr ...

  9. openshift 容器云从入门到崩溃之六《Source-to-Image》

    上次说到了怎么在oc上面部署应用而且说道了怎么定义模板部署应用,也许你会奇怪那个我代码打包编译在哪一步,那就要说道oc的s2i流程了 下面是基本s2i流程 1.制作base-image镜像 要使用s2 ...

  10. iDigital2019数字营销广告主峰会

    iDigital 数字营销品牌广告主峰会是定向邀请形式的闭门社交峰会, 现已成长为享誉亚洲的营销广告行业活动, 致力于帮助品牌营销官, 决策者和业界同行在日益整合的数字商业时代下获得战略远景.组委会邀 ...