本篇继续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. loadrunner笔记(二):飞机订票系统--客户信息注册

    (一)  几个重要概念说明 集合点:同步虚拟用户,以便同一时间执行任务. 事务:事务是指服务器响应用户请求所用的时间,当然它可以衡量某个操作,如登录所需要的时间,也可以衡量一系列的操作所用的时间,如从 ...

  2. 布局fixed和sticky

    sticky非常非常非常好用怎么用看代码: 这里为什么没有设置高度呢,因为这个高度应该是浏览器高度,浏览器高度在时刻变化怎么办? js处理: 此JS里面会有执行方法一步一步看 这个里面有JS方法 这个 ...

  3. python中matplotlib的颜色及线条控制

    参考网址: http://www.cnblogs.com/darkknightzh/p/6117528.html http://stackoverflow.com/questions/22408237 ...

  4. jq 点击按钮显示div,点击页面其他任何地方隐藏div

    css .bl_rencai_32{ float: left; height: 35px; line-height: 35px; } .bl_rencai_32 >input{ width: 3 ...

  5. Django中URL有关

    django 模板中url的处理   在模板中直接添加‘/home’这样的链接是十分不推荐的,因为这是一个相对的链接,在不同网页中打开可能会返回不一样的结果. 所以推荐的是 1 <a href= ...

  6. python-颜色显示

    格式:\033[显示方式;字体色;背景色m......[\033[0m] ------------------------------------------- 字体色 | 背景色 | 颜色描述 -- ...

  7. MACD技术的高级应用--MACD与波浪

    在开始分析MACD指标之前,我想我们必须先从思想上认同以下两点,否则本文的研究就没有意义.  1)趋势在一段时间内是可以把握的:  2)每个指标都有有效的时候,没有指标会始终有效.我们就是要搞清楚指标 ...

  8. java之项目构建工具Gradle

    介绍 Java 作为一门世界级主流编程语言,有一款高效易用的项目管理工具是 java 开发者共同追求的心愿和目标.显示 2000 年的 Ant,后有 2004 年的 Maven 两个工具的诞生,都在 ...

  9. vue和jQuery嵌套实现异步ajax通信

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8&qu ...

  10. debug调试命令