LINQ使用

通过上节 LINQ 的基础知识的学习,我们可以开始使用 LINQ 来进行内存数据的查询了,我们上节说了 LINQ 的定义为: Language Integrated Query (语言集成查询)的简称,它是集成在 .NET 编程语言中的一种特性 .

1.LINQ 的构架

从这幅图中,我们可以知道LINQ包括五个部分:LINQ to Objects、LINQ to XML、LINQ to SQL、LINQ to DataSet、LINQ to Entities

程序集 命名空间 描述
LINQ to Objects System.Core.dll System.Linq 提供对内存中集合操作的支持
LINQ to XML System.Xml.Linq.dll System.Xml.Linq 提供对XML数据源的操作的支持
LINQ to SQL System.Data.Linq.dll System.Data.Linq 提供对Sql Server数据源操作的支持。(微软已宣布不再更新,推荐使用LINQ to Entities)
LINQ to DataSet System.Data.DataSetExtensions.dll System.Data 提供对离线数据操作的支持。
LINQ to Entities System.Core.dll 和System.Data.Entity.dll System.Linq 和System.Data.Objects LINQ to Entities 是 Entity Framework 的一部分并且取代LINQ to SQL 作为在数据库上使用 LINQ 的标准机制。(Entity Framework 是由微软发布的开源对象-关系映射(ORM)框架,支持多种数据库。)

目前,还可以下载其他第三方提供程序,例如LINQ to JSON、LINQ to MySQL、LINQ to Amazon、LINQ to Flickr和LINQ to SharePoint。无论使用什么数据源,都可以通过LINQ使用相同的API进行操作。

1.LINQ 操作语法

LINQ 查询时有两种语法可供选择:查询表达式( Query Expression)和方法语法(Fluent Syntax)

.NET公共语言运行库(CLR)并不具有查询表达式的概念。所以,编译器会在程序编译时把查询表达式转换为方法语法,即对扩展方法的调用。所以使用方法语法会让我们更加接近和了解LINQ的实现和本质,并且一些查询只能表示为方法调用。但另一方面,查询表达式通常会比较简单和易读。不管怎样,这两种语法是互相补充和兼容的,我们可以在一个查询中混合使用查询表达式和方法语法。

.net 的设计者在类库中定义了一系列的扩展方法来方便用户操作集合对象 ,这些扩展方法构成了 LINQ 的查询操作符 。

以下扩展方法存在对应的查询表达式关键字:Where、Select、SelectMany、OrderBy、ThenBy、OrderByDescending、ThenByDescending、GroupBy、Join、GroupJoin。

LINQ查询表达式

约束 LINQ查询表达式必须以from子句开头,以select或group子句结束。
关键字 功能
from…in… 指定要查找的数据源以及范围变量,多个from子句则表示从多个数据源查找数据。 注意:c#编译器会把“复合from子句”的查询表达式转换为SelectMany()扩展方法。
join…in…on…equals… 指定多个数据源的关联方式
let 引入用于存储查询表达式中子表达式结果的范围变量。通常能达到层次感会更好,使代码更易于阅读。
orderby、descending 指定元素的排序字段和排序方式。当有多个排序字段时,由字段顺序确定主次关系,可指定升序和降序两种排序方式
where 指定元素的筛选条件。多个where子句则表示了并列条件,必须全部都满足才能入选。每个where子句可以使用谓词&&、||连接多个条件表达式。
group 指定元素的分组字段。
select 指定查询要返回的目标数据,可以指定任何类型,甚至是匿名类型。(目前通常被指定为匿名类型)
into 提供一个临时的标识符。该标识可以引用join、group和select子句的结果。 1) 直接出现在join子句之后的into关键字会被翻译为GroupJoin。(into之前的查询变量可以继续使用) 2) select或group子句之后的into它会重新开始一个查询,让我们可以继续引入where, orderby和select子句,它是对分步构建查询表达式的一种简写方式。(into之前的查询变量都不可再使用)

表达式的语法如下:

from [type] id in source

[join [type] id in source on expr equals expr [into subGroup]]

[ from [type] id in source | let id = expr | where condition]

[ orderby ordering,ordering,ordering...]

select expr | group expr by key

[into id query]

<1> 第一行:

type 是可选的, id 是集合中的一项, source 是一个集合,如果集合中的类型与 type 指定的类型不同则导致强制转化

<2> 第二行:

一个查询表达式中可以有 0 个或多个 join 子句, 这里的 source 可以不等于第一句中的 source

expr 可以是一个表达式

[into subGroup] subGroup 是一个中间变量,它继承自 IGrouping ,代表一个分组,也就是说“一对多”里的“多”

可以通过这个变量得到这一组包含的对象个数,以及这一组对象的键

<3> 第三行:

一个查询表达式中可以有 1 个或多个 from 子句

一个查询表达式中可以有 0 个或多个 let 子句, let 子句可以创建一个临时变量

一个查询表达式中可以有 0 个或多个 where 子句, where 子句可以指定查询条件

<4> 第四行:

一个查询表达式可以有 0 个或多个排序方式

每个排序方式以逗号分割

<5> 第五行:

一个查询表达式必须以 select 或者 group by 结束

select 后跟要检索的内容

group by 是对检索的内容进行分组

<6> 第六行:

最后一个 into 子句起到的作用是将前面语句的结果作为后面语句操作的数据源

3. LINQ to Objects

LINQ to Objects 是 LINQ 的精华部分。

LINQ to Objects 提供对内存中集合操作的支持,由程序集 System.Core.dll 中 System.Linq 命名空间下的 Enumerable 静态类提供。

这些扩展方法都是针对 IEnumerable 的对象进行扩展的也就是说,只要实现了 IEnumerable 接口,就可以使用这些扩展方法 。

这些扩展方法都是针对 IEnumerable 的对象进行扩展的也就是说,只要实现了 IEnumerable 接口,就可以使用这些扩展方法 。

4.示例

我们说对于 LINQ 查询语法,有两种可供选择,一种是扩展方法(又叫方法语法( Fluent Syntax ) ),另一种是查询表达式( Query Expression )。他们是等价的,只是写法上不同。

在下面的示例中,我们都会用两种语法来完成。

编号 Id 姓名 Name 年龄 Age 门派 Menpai 武学 Kungfu 武学级别 Level
1 黄蓉 18 丐帮 打狗棒法 9
2 洪七公 70 丐帮 打狗棒法 10
3 郭靖 22 丐帮 降龙十八掌 10
4 任我行 50 明教 葵花宝典 1
5 东方不败 35 明教 葵花宝典 10
6 林平之 23 华山 葵花宝典 7
7 岳不群 50 华山 葵花宝典 8
编号 KongfuId 武学名称 KongfuName 杀伤力 Lethality
1 打狗棒法 90
2 降龙十八掌 95
3 葵花宝典 100

如上对象“武林高手”( MartialArtsMaster )和“武学”( Kongfu )

/// <summary>
/// 类:武林高手
/// MartialArtsMaster
/// </summary>
class  MartialArtsMaster
{
    /// <summary>
    /// 编号
    /// </summary>
    public int Id{get;set;}
    /// <summary>
    /// 姓名
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 年龄
    /// </summary>
    public int Age { get; set; }
    /// <summary>
    /// 门派
    /// </summary>
    public string Menpai { get; set; }
    /// <summary>
    /// 武学
    /// </summary>
    public string Kungfu { get; set; }
    /// <summary>
    /// 级别
    /// </summary>
    public int Level { get; set; }
}

/// <summary>
/// 类:武学
/// Kongfu
/// </summary>
class Kongfu
{
    /// <summary>
    /// 武学编号
    /// </summary>
    public int KongfuId { get; set; }

    /// <summary>
    /// 武学名称
    /// </summary>
    public string KongfuName { get; set; }

    /// <summary>
    /// 杀伤力
    /// </summary>
    public int Lethality { get; set; }
}
//初始化武林高手
var master = new List < MartialArtsMaster > () {
    new MartialArtsMaster() {
        Id = 1,
        Name = "黄蓉",
        Age = 18,
        Menpai = "丐帮",
        Kungfu = "打狗棒法",
        Level = 9
    },
    new MartialArtsMaster() {
        Id = 2,
        Name = "洪七公",
        Age = 70,
        Menpai = "丐帮",
        Kungfu = "打狗棒法",
        Level = 10
    },
    new MartialArtsMaster() {
        Id = 3,
        Name = "郭靖",
        Age = 22,
        Menpai = "丐帮",
        Kungfu = "降龙十八掌",
        Level = 10
    },
    new MartialArtsMaster() {
        Id = 4,
        Name = "任我行",
        Age = 50,
        Menpai = "明教",
        Kungfu = "葵花宝典",
        Level = 1
    },
    new MartialArtsMaster() {
        Id = 5,
        Name = "东方不败",
        Age = 35,
        Menpai = "明教",
        Kungfu = "葵花宝典",
        Level = 10
    },
    new MartialArtsMaster() {
        Id = 6,
        Name = "林平之",
        Age = 23,
        Menpai = "华山",
        Kungfu = "葵花宝典",
        Level = 7
    },
    new MartialArtsMaster() {
        Id = 7,
        Name = "岳不群",
        Age = 50,
        Menpai = "华山",
        Kungfu = "葵花宝典",
        Level = 8
    }
};
//初始化武学
var kongfu = new List < Kongfu > () {
    new Kongfu() {
        KongfuId = 1,
        KongfuName = "打狗棒法",
        Lethality = 90
    },
    new Kongfu() {
        KongfuId = 2,
        KongfuName = "降龙十八掌",
        Lethality = 95
    },
    new Kongfu() {
        KongfuId = 3,
        KongfuName = "葵花宝典",
        Lethality = 100
    }
}

4.1 过滤操作符

根据条件返回匹配元素的集合IEnumerable<T>。

1) Where:根据返回bool值的Func委托参数过滤元素。

业务说明:查询获得车手冠军次数大于15次且是Austria国家的一级方程式赛手

2) OfType<TResult>:接收一个非泛型的IEnumerable集合,根据OfType泛型类型参数过滤元素,只返回TResult类型的元素。

业务说明:过滤object数组中的元素,返回字符串类型的数组。

3) Distinct:删除序列中重复的元素。

示例一:查询 丐帮 中 修行 " 级别 " 高于 "8 级 " 的大侠

//示例一:查询 丐帮 中 修行"级别"高于 "8级" 的大侠

//表达式 写法
var GaiBangMaster = from   m in master
        where  m.Level > 8 && m.Menpai == "丐帮"
        select m;

//扩展方法 写法
var GaiBangMasterMethod = master.Where(m => m.Level > 8 && m.Menpai == "丐帮");

//输出结果
string GaiBangMasterResult="查询\"丐帮\"中\"功力\"高于80的大侠(表达式写法):\n";
GaiBangMasterResult +="编号(Id) 姓名(Name) 年龄(Age) 门派(Mengpai) 武学(Kungfu) 级别(Level)\n";

foreach(var m in GaiBangMaster)
  GaiBangMasterResult += m.Id + "  " + m.Name+"  "+m.Age+"  "+m.Menpai+"  "+m.Kungfu+"  "+m.Level+"  " + "\n";
Console.WriteLine(GaiBangMasterResult);

string GaiBangMasterMethodResult = "查询\"丐帮\"中\"功力\"高于80的大侠(扩展方法 写法):\n";
GaiBangMasterMethodResult += "编号(Id) 姓名(Name) 年龄(Age) 门派(Mengpai) 武学(Kungfu) 级别(Level)\n";

foreach (var m in GaiBangMasterMethod)
  GaiBangMasterMethodResult += m.Id + "  " + m.Name + "  " + m.Age + "  " + m.Menpai + "  " + m.Kungfu + "  " + m.Level + "  " + "\n";
Console.WriteLine(GaiBangMasterMethodResult);

Console.ReadKey();

输出结果如下:

4.2. 投影操作符

1) Select 将序列的每个元素经过lambda表达式处理后投影到一个新类型元素上。(与SelectMany不同在于,若单个元素投影到IEnumerable<TResult>,Select不会对多个IEnumerable<TResult>进行合并)

2) SelectMany

a) c#编译器会把“复合from子句”的查询表达式转换为SelectMany()扩展方法。

b) 将序列的每个元素经过lambda表达式处理后投影到一个 IEnumerable<TResult>,再将多个IEnumerable<TResult>序列合并为一个返回序列IEnumerable<TResult>。

c) 将序列的每个元素经过lambda表达式处理后投影到一个 IEnumerable<TCollection>,再将多个IEnumerable<TCollection>序列合并为一个返回序列IEnumerable<TCollection>,并对其中每个元素调用结果选择器函数。

示例二:过滤所学”武功” “伤杀力” 大于 90 的大侠

//示例二:过滤所学”武功” “伤杀力” 大于90 的大侠
//表达式 写法
var masterKongfu = from m in master
                   from k in kongfu
                   where( k.Lethality > 90 && m.Kungfu==k.KongfuName)
                   orderby m.Level
                   select m.Id + "  " + m.Name + "  " + m.Age + "  " + m.Menpai + "  " + m.Kungfu + "  " + m.Level + "  ";
//扩展方法 写法
var masterKongfuMethod = master
                        .SelectMany(
                           k => kongfu,
                           (m, k) => new { mt = m, kf = k }
                        )
                        .Where(x =>x.kf.Lethality> 90 && x.mt.Kungfu==x.kf.KongfuName )
                        .OrderBy(m => m.mt.Level)
                        .Select(m => m.mt.Id + "  " + m.mt.Name + "  " + m.mt.Age + "  " + m.mt.Menpai + "  " + m.mt.Kungfu + "  " + m.mt.Level + "  ");
//输出结果
string masterKongfuResult = "过滤所学”武功” “伤杀力” 大于90 的大侠(表达式写法):\n";
masterKongfuResult += "编号(Id) 姓名(Name) 年龄(Age) 门派(Mengpai) 武学(Kungfu) 级别(Level)\n";
foreach (var m in masterKongfu)
    masterKongfuResult += m.ToString()+"  " + "\n";
Console.WriteLine(masterKongfuResult);

string masterKongfuMethodResult = "过滤所学”武功” “伤杀力” 大于90 的大侠(扩展方法 写法):\n";
masterKongfuMethodResult += "编号(Id) 姓名(Name) 年龄(Age) 门派(Mengpai) 武学(Kungfu) 级别(Level)\n";
foreach (var m in masterKongfuMethod)
    masterKongfuMethodResult += m.ToString() + "  " + "\n";
Console.WriteLine(masterKongfuMethodResult);

Console.ReadKey();

输出结果如下:

4.3排序操作符

1) OrderBy<TSource,TKey>,OrderByDescending<TSource,TKey>:根据指定键按升序或降序对集合进行第一次排序,输出IOrderedEnumerable<TSource>。

2) ThenBy<TSource,TKey>,ThenByDescending<TSource,TKey>:只会对那些在前一次排序中拥有相同键值的elements重新根据指定键按升序或降序排序。输入IOrderedEnumerable <TSource>。

3) Reverse<TSource>:反转集合中所有元素的顺序。

示例三:“华山论剑”之武林排行榜 (注:武功高低=所学武学杀伤力*个人武学级别) 排序规则,先按武功高低(由高到低),再按年龄(从小到大),再按名字首字母(正序)

//示例三:“华山论剑”之武林排行榜 (注:武功高低=所学武学杀伤力*个人武学级别) 排序规则,先按武功高低(由高到低),再按年龄(从小到大),再按名字首字母(正序)
//表达式写法
int i=0;
var topMaster= from m in master
                    from k in kongfu
                where (k.KongfuName==m.Kungfu)
                orderby m.Level*k.Lethality descending,m.Age,m.Name
                select m.Id + "  " + m.Name + "  " + m.Age + "  " + m.Menpai + "  " + m.Kungfu + "  " + m.Level + "  "+m.Level*k.Lethality +"  "+(++i);
//输出结果
string topMasterResult = "“华山论剑”之武林排行榜 (注:武功高低=所学武学杀伤力*个人武学级别)(表达式 写法):\n";
topMasterResult += "编号  姓名  年龄  门派  武学  级别  武功  排名\n";
foreach (var m in topMaster)
    topMasterResult += m.ToString() + "  " + "\n";
Console.WriteLine(topMasterResult);

//扩展方法写法
i = 0;
var topMasterMethod = master
            .SelectMany(
                k => kongfu,
                (m, k) => new { mt = m, kf = k }
            )
            .Where(x => x.mt.Kungfu == x.kf.KongfuName)
            .OrderByDescending(m => m.mt.Level*m.kf.Lethality)
            .ThenBy(m=>m.mt.Age)
            .ThenBy(m=>m.mt.Name)
            .Select(m => m.mt.Id + "  " + m.mt.Name + "  " + m.mt.Age + "  " + m.mt.Menpai + "  " + m.mt.Kungfu + "  " + m.mt.Level + "  " + m.mt.Level * m.kf.Lethality + "  " + (++i));
//输出结果
string topMasterMethodResult = "“华山论剑”之武林排行榜 (注:武功高低=所学武学杀伤力*个人武学级别)(扩展方法 写法):\n";
topMasterMethodResult += "编号  姓名  年龄  门派  武学  级别  武功  排名\n";
foreach (var m in topMasterMethod)
    topMasterMethodResult += m.ToString() + "  " + "\n";
Console.WriteLine(topMasterMethodResult);

Console.ReadKey();

输出结果如下:

4.4 连接操作符

注意:join…on…关键字后的相等使用equals关键字。

//获取集合MasterTop (武功个人级别大于8的高手)
int ii = 1;
var MasterTop = master
 .Where(x => x.Level > 8)
 .OrderByDescending(x => x.Level)
 .Select(x => new { Id = x.Id, Name = x.Name, MasterKongfu = x.Kungfu, Level = x.Level, Top = (ii++) });


//获取集合KongfuTop (武功杀伤力大于90的武功)
ii=1;
var KongfuTop = from k in kongfu
    where (k.Lethality>90)
    orderby k.Lethality descending
    select new{ KongfuId=k.KongfuId,KongfuName=k.KongfuName,Lethality=k.Lethality,KongfuTop=(ii++)};

在此之前,我们先获取两个对象MasterTop (武功个人级别大于8的高手)和对象KongfuTop(武功杀伤力大于90的武功)

1) Join:基于匹配键对两个序列的元素进行关联。

示例三:通过对象 MasterTop 和 KongfuTop关联,返回新的对象 MasterLethalityTop高手杀伤力(包含高手的编号,名字,所学武功,级别,总杀伤力)

//示例三:通过对象MasterTop 和 KongfuTop关联,返回新的对象 MasterLethalityTop高手杀伤力(包含高手的编号,名字,所学武功,级别,总杀伤力)
//表达式
var MasterLethalityTop = from m in MasterTop
                         join k in KongfuTop on m.MasterKongfu equals k.KongfuName
                         orderby m.Level*k.Lethality descending
                         select new
                         {
                             Id = m.Id,Name=m.Name,Kongfu=m.MasterKongfu,Level=m.Level,Kill=m.Level*k.Lethality
                         };
//扩展方法
var MasterLethalityTopMothod = MasterTop.Join(KongfuTop,
                                    m => m.MasterKongfu,
                                    k => k.KongfuName,
                                   (m, k) => new
                                   {
                                       Id = m.Id,
                                       Name = m.Name,
                                       Kongfu = m.MasterKongfu,
                                       Level = m.Level,
                                       Kill = m.Level * k.Lethality
                                   }
                              )
                              .OrderByDescending(m => m.Kill);

Console.WriteLine("通过对象MasterTop 和 KongfuTop关联,返回新的对象 MasterLethalityTop高手杀伤力(表达式):\n");
Console.WriteLine("编号     名字     所学武功     级别     总杀伤力\n");
foreach (var ma in MasterLethalityTop)
    Console.WriteLine(ma.Id + "  " + ma.Name + "  " + ma.Kongfu + "  " + ma.Level + "  " + ma.Kill + "\n");

Console.WriteLine("通过对象MasterTop 和 KongfuTop关联,返回新的对象 MasterLethalityTop高手杀伤力(扩展方法):\n");
Console.WriteLine("编号     名字     所学武功     级别     总杀伤力\n");
foreach (var ma in MasterLethalityTopMothod)
    Console.WriteLine(ma.Id + "  " + ma.Name + "  " + ma.Kongfu + "  " + ma.Level + "  " + ma.Kill + "\n");
Console.ReadKey();

运行结果如下:

2) GroupJoin:基于键相等对两个序列的元素进行关联并对结果进行分组。常应用于返回“主键对象-外键对象集合”形式的查询。

注意:直接出现在join子句之后的into关键字会被翻译为GroupJoin,而在select或group子句之后的into表示继续一个查询。

示例三: 使用武学关联 master 和 kongfu, 并按武学分组;并按使用者数量排序

//示例三:过滤操作符GroupJoin 使用武学关联master和kongfu,并按武学分组;并按使用者数量排序

//增加几个武林高手
master.Add(new MartialArtsMaster() { Id = 8, Name = "令狐冲", Age = 23, Menpai = "华山", Kungfu = "独孤九剑", Level = 10 });
master.Add(new MartialArtsMaster() { Id = 9, Name = "梅超风", Age = 23, Menpai = "桃花岛", Kungfu = "九阴真经", Level = 8 });
master.Add(new MartialArtsMaster() { Id =10, Name = "黄药师", Age = 23, Menpai = "梅花岛", Kungfu = "弹指神通", Level = 10 });
master.Add(new MartialArtsMaster() { Id = 11, Name = "风清扬", Age = 23, Menpai = "华山", Kungfu = "独孤九剑", Level = 10 });

//增加几个武学
kongfu.Add(new Kongfu() { KongfuId=  4, KongfuName = "独孤九剑", Lethality = 100 });
kongfu.Add(new Kongfu() { KongfuId = 5, KongfuName = "九阴真经", Lethality = 100 });
kongfu.Add(new Kongfu() { KongfuId = 6, KongfuName = "弹指神通", Lethality = 100 });

Console.WriteLine("过滤操作符GroupJoin 使用武学关联master和kongfu,并按武学分组 (表达式):\n");
Console.WriteLine("武学编号 名字 杀伤力 学会的大侠数\n");
var masterItems = from k in kongfu
                  join m in master on k.KongfuName equals m.Kungfu
                  into groups
                  orderby groups.Count() descending
                  select new
                  {
                    KongfuId = k.KongfuId,
                    KongfuName = k.KongfuName,
                    Lethality = k.Lethality,
                    Count=groups.Count()
                  };
foreach(var ma in masterItems)
Console.WriteLine(ma.KongfuId + "  " + ma.KongfuName + "  " + ma.Lethality + "  " + ma.Count + "\n");


Console.WriteLine("过滤操作符GroupJoin 使用武学关联master和kongfu,并按武学分组 (扩展方法):\n");
Console.WriteLine("武学编号 名字 杀伤力 学会的大侠数\n");
var masterItemsMothod = kongfu.GroupJoin(master,
             k => k.KongfuName, m => m.Kungfu,
             (k, m) => new { k.KongfuId, k.KongfuName, k.Lethality, Count = m.Count() }
             )
             .OrderByDescending(k => k.Count);

foreach (var ma in masterItemsMothod)
    Console.WriteLine(ma.KongfuId+"  "+ma.KongfuName + "  " + ma.Lethality +"  "+ ma.Count + "\n");

输出结果如下:

3) join…on…equals…支持多个键关联

可以使用匿名类型来对多个键值进行Join,如下所示:

from x in sequenceX

join y in sequenceY on new { K1 = x.Prop1, K2 = x.Prop2 }

equals new { K1 = y.Prop3, K2 = y.Prop4 }

...

两个匿名类型的结构必须完全一致,这样编译器会把它们对应到同一个实现类型,从而使连接键值彼此兼容。

4) Join与GroupJoin结果集对比

Join 操作符执行一个内连接 (inner join), 输出一个扁平序列

//Join和GroupJoin区别

Console.WriteLine("Join和GroupJoin区别,使用Join (表达式):\n");
Console.WriteLine("武学编号 名字 杀伤力 使用大侠名字\n");
var JoinOrGroupJoin = from k in kongfu
                  join m in master on k.KongfuName equals m.Kungfu
                  orderby k.KongfuId
                  select new
                  {
                      KongfuId = k.KongfuId,
                      KongfuName = k.KongfuName,
                      Lethality = k.Lethality,
                     Master= m.Name
                  };
foreach (var ma in JoinOrGroupJoin)
    Console.WriteLine(ma.KongfuId + "  " + ma.KongfuName + "  " + ma.Lethality + "  " + ma.Master + "\n");


Console.WriteLine("Join和GroupJoin区别,使用GroupJoin(扩展方法):\n");
Console.WriteLine("武学编号 名字 杀伤力 学会的大侠数\n");
var JoinOrGroupJoinMethod = kongfu.GroupJoin(master,
             k => k.KongfuName, m => m.Kungfu,
             (k, m) => new { k.KongfuId, k.KongfuName, k.Lethality, Count = m.Count() }
             )
             .OrderBy(k => k.KongfuId);

foreach (var ma in JoinOrGroupJoinMethod)
    Console.WriteLine(ma.KongfuId + "  " + ma.KongfuName + "  " + ma.Lethality + "  " + ma.Count + "\n");


Console.ReadKey();

运行结果如下:

5. 分组操作符

1) 返回值为 IEnumerable<IGrouping<TKey, TSource>> ,根据指定的键选择器函数对序列中的元素进行分组。

2) 返回值为 IEnumerable<TResult>,根据指定的键选择器函数对序列中的元素进行分组,并且从每个组及其键中创建结果值。

示例五:按门派分类大侠,统计出各个门派大侠数量

//分组Group
var GroupItems=  from m in master
                 group m by m.Menpai into g
                 orderby g.Key
                 select new
                 {
                     Menpai = g.Key,
                    Count= g.Count()
                 };
Console.WriteLine("分组Group(表达式):\n");
Console.WriteLine("门派 所在门派人数\n");
foreach (var ma in GroupItems)
    Console.WriteLine(ma.Menpai + "  " + ma.Count + "\n");


var GroupItemsMethod = master.GroupBy(m => m.Menpai, (k, m) => new { Menpai = k, Count = m.Count() });

Console.WriteLine("分组Group(扩展方法 写法一):\n");
Console.WriteLine("门派 所在门派人数\n");
foreach (var ma in GroupItemsMethod)
    Console.WriteLine(ma.Menpai + "  " + ma.Count + "\n");


var GroupItemsMethod2 = master.GroupBy(m => m.Menpai).Select((m) => new { Menpai = m.Key, Count = m.Count() });

Console.WriteLine("分组Group(扩展方法 写法二):\n");
Console.WriteLine("门派 所在门派人数\n");
foreach (var ma in GroupItemsMethod2)
    Console.WriteLine(ma.Menpai + "  " + ma.Count + "\n");

Console.ReadKey();

运行结果如下:

6. 量词操作符

如果元素序列满足指定的条件,量词操作符就返回布尔值。

1)Any:确定序列是否包含任何元素;或确定序列中的任何元素是否都满足条件。

2)All:确定序列中的所有元素是否满足条件。

3)Contains:确定序列是否包含指定的元素。

学到这里,再学习量词操作符,就相当简单了,直接看代码:

//量词操作符(Any.All,Contains)
//先初始化一个对象备用
var AnyItems = from m in master
               where m.Kungfu == "葵花宝典"
               select new { m.Name, m.Menpai, m.Kungfu };

Console.WriteLine("练葵花宝典的所有大侠名单\n");
Console.WriteLine("名字     门派   研习武学\n");
foreach (var m in AnyItems)
    Console.WriteLine(m.Name + "   " + m.Menpai + "   " + m.Kungfu+"\n");
//使用Any (确定序列是否包含任何元素;或确定序列中的任何元素是否都满足条件)。
var any = AnyItems.Any(m => m.Menpai == "明教");
Console.WriteLine("是否研习葵花宝典有明教中人?"+any);
//返回True,说练习葵花宝典的大侠中有明教的人

//All(确定序列中的所有元素是否满足条件)
var all = AnyItems.All(m => m.Menpai == "明教");
Console.WriteLine("是否研习葵花宝典的全是明教中人?"+all);

//Contains(确定序列中是否包含指定的元素)
//先声明两个元素
var OuYangFeng = new MartialArtsMaster() { Id = 13, Name = "欧阳锋", Age = 50, Menpai = "白驼山庄", Kungfu = "蛤蟆功", Level = 10 };
var HuanYaoShi = master[9];
//使用Contains判断是否存在
var IsOuYangFeng = master.Contains(OuYangFeng);
var IsHuangYaoShi = master.Contains(HuanYaoShi as MartialArtsMaster);
Console.WriteLine("大侠名单中是否存在欧阳锋?" + IsOuYangFeng);
Console.WriteLine("大侠名单中是否存在黄药师?" + IsHuangYaoShi);
Console.ReadKey();

运行结果如下:

7. 分区操作符

需要放在查询的“最后”,返回集合的一个子集。

1) Take:从序列的开头返回指定数量的连续元素。

2) TakeWhile:只要满足指定的条件,就会返回序列的元素。

3) Skip:跳过序列中指定数量的元素,然后返回剩余的元素。

4) SkipWhile:只要满足指定的条件,就跳过序列中的元素,然后返回剩余元素。

实例:我们使用分区操作符对各位大侠进行分页(每页5个)

//分区操作符
//我们在操作之前,先增加一些元素
//增加几个武林高手
master.Add(new MartialArtsMaster() { Id = 12, Name = "肖峰", Age = 33, Menpai = "丐帮", Kungfu = "降龙十八掌", Level = 9 });
master.Add(new MartialArtsMaster() { Id = 13, Name = "段誉", Age = 23, Menpai = "天龙寺", Kungfu = "六脉神剑", Level = 7 });
master.Add(new MartialArtsMaster() { Id = 14, Name = "虚竹", Age = 26, Menpai = "逍遥派", Kungfu = "北冥神功", Level = 9 });
master.Add(new MartialArtsMaster() { Id = 15, Name = "方正大师", Age = 23, Menpai = "少林寺", Kungfu = "七十二绝技", Level = 10 });
master.Add(new MartialArtsMaster() { Id = 16, Name = "杨过", Age = 23, Menpai = "古墓派", Kungfu = "玉女心经", Level = 10 });
master.Add(new MartialArtsMaster() { Id = 17, Name = "小龙女", Age = 23, Menpai = "古墓派", Kungfu = "玉女心经", Level = 10 });

//增加几个武学
kongfu.Add(new Kongfu() { KongfuId = 7, KongfuName = "六脉神剑", Lethality = 100 });
kongfu.Add(new Kongfu() { KongfuId = 8, KongfuName = "北冥神功", Lethality = 100 });
kongfu.Add(new Kongfu() { KongfuId = 9, KongfuName = "七十二绝技", Lethality = 100 });
kongfu.Add(new Kongfu() { KongfuId = 10, KongfuName = "玉女心经", Lethality = 95 });

int pageSize = 5; //每页数量 5
int pageNumber = (int)Math.Ceiling(master.Count() / (double)pageSize); //计算总页数
Console.WriteLine("使用分区操作符进行分页\n");
Console.WriteLine("大侠总数:" + master.Count() + "    总页数:" + pageNumber + "    每页:" + pageSize + "\n");
for (int i = 0; i < pageNumber;i++ )
{
    Console.WriteLine("*************************第:" + (i+1).ToString() + "页*************************\n");
    var pageMaster = (
                       from m in master
                       join k in kongfu on m.Kungfu equals k.KongfuName
                       orderby m.Level * k.Lethality descending
                       select new { m.Name, m.Menpai, m.Kungfu, k.Lethality, m.Level, Kill = m.Level * k.Lethality }
                    ).Skip(i * pageSize).Take(pageSize);
    Console.WriteLine("姓名    门派     武功   武功杀伤力  修炼等级  总武力\n");
    foreach (var m in pageMaster)
        Console.WriteLine(m.Name + " " + m.Menpai + " " + m.Kungfu + " " + m.Lethality + " " + m.Level + " " + m.Kill + "\n");
    Console.WriteLine();
}

运行结果如下:

8. 集合操作符

1)Union:并集,返回两个序列的并集,去掉重复元素。

2)Concat:并集,返回两个序列的并集。

3)Intersect:交集,返回两个序列中都有的元素,即交集。

4)Except:差集,返回只出现在一个序列中的元素,即差集。

实例:华山派和明教中都使用葵花宝典的大侠

//集合操作符
//实例:华山派和明教中都使用葵花宝典的大侠
var ItemsIntersect = (
             from m in master
             where m.Menpai == "华山" || m.Menpai=="明教"
             select m
           ).Intersect(
             from m in master
             where m.Kungfu == "葵花宝典"
             select m
           );
foreach (var m in ItemsIntersect)
    Console.WriteLine(m.Name + " " + m.Menpai + " " + m.Kungfu);

运行结果如下:

9. 元素操作符

这些元素操作符仅返回一个元素,不是IEnumerable<TSource>。(默认值:值类型默认为0,引用类型默认为null)

1)First:返回序列中的第一个元素;如果是空序列,此方法将引发异常。

2)FirstOrDefault:返回序列中的第一个元素;如果是空序列,则返回默认值default(TSource)。

3)Last:返回序列的最后一个元素;如果是空序列,此方法将引发异常。

4)LastOrDefault:返回序列中的最后一个元素;如果是空序列,则返回默认值default(TSource)。

5)Single:返回序列的唯一元素;如果是空序列或序列包含多个元素,此方法将引发异常。

6)SingleOrDefault:返回序列中的唯一元素;如果是空序列,则返回默认值default(TSource);如果该序列包含多个元素,此方法将引发异常。

7)ElementAt:返回序列中指定索引处的元素,索引从0开始;如果索引超出范围,此方法将引发异常。

8)ElementAtOrDefault:返回序列中指定索引处的元素,索引从0开始;如果索引超出范围,则返回默认值default(TSource)。

10. 合计操作符

1)Count:返回一个 System.Int32,表示序列中的元素的总数量。

2)LongCount:返回一个 System.Int64,表示序列中的元素的总数量。

3)Sum:计算序列中元素值的总和。

4)Max:返回序列中的最大值。

5)Min:返回序列中的最小值。

6)Average:计算序列的平均值。

7)Aggregate:对序列应用累加器函数。

Aggregate比较复杂,所以只列出Aggregate示例。

Aggregate的第一个参数是算法的种子,即初始值。第二个参数是一个表达式,用来对每个元素进行计算(委托第一个参数是累加变量,第二个参数当前项)。第三个参数是一个表达式,用来对最终结果进行数据转换。

示例:

int[] numbers = { 1, 2, 3 };
// 1+2+3 = 6
int y = numbers.Aggregate((prod, n) => prod + n);
// 0+1+2+3 = 6
int x = numbers.Aggregate(0, (prod, n) => prod + n);
// (0+1+2+3)*2 = 12
int z = numbers.Aggregate(0, (prod, n) => prod + n, r => r * 2);

11. 转换操作符

1)Cast:将非泛型的 IEnumerable 集合元素转换为指定的泛型类型,若类型转换失败则抛出异常。

2)ToArray:从 IEnumerable<T> 创建一个数组。

3)ToList:从 IEnumerable<T> 创建一个 List<T>。

4)ToDictionary:根据指定的键选择器函数,从 IEnumerable<T> 创建一个 Dictionary<TKey,TValue>。

5)ToLookup:根据指定的键选择器函数,从 IEnumerable<T> 创建一个 System.Linq.Lookup<TKey,TElement>。

6)DefaultIfEmpty:返回指定序列的元素;如果序列为空,则返回包含类型参数的默认值的单一元素集合。

7) AsEnumerable:返回类型为 IEnumerable<T> 。用于处理LINQ to Entities操作远程数据源与本地集合的协作。

12. 生成操作符

生成操作符返回一个新的集合。(三个生成操作符不是扩展方法,而是返回序列的正常静态方法)

1 ) Empty:生成一个具有指定类型参数的空序列 IEnumerable<T>。

2) Range:生成指定范围内的整数的序列 IEnumerable<Int32>。

3) Repeat:生成包含一个重复值的序列 IEnumerable<T>。

13. Linq to Objects的延迟计算

Linq查询的延迟计算原理:通过给LINQ扩展方法传递方法委托,作为yield迭代器的主体,让遍历执行到MoveNext()时才执行耗时的指令。

整理Linq to Objects中运算符延迟计算特性

按字母顺序整理:

具有延迟计算的运算符 Cast,Concat,DefaultIfEmpty,Distinct,Except,GroupBy,GroupJoin,Intersect ,Join,OfType,OrderBy,OrderByDescending,Repeat,Reverse,Select,SelectMany,Skip,SkipWhile,Take,TakeWhile,ThenBy,ThenByDescending,Union,Where,Zip
立即执行的运算符 Aggregate,All,Any,Average,Contains,Count,ElementAt,ElementAtOrDefault ,Empty,First,FirstOrDefault,Last,LastOrDefault,LongCount,Max,Min,Range ,SequenceEqual,Single,SingleOrDefault,Sum,ToArray,ToDictionary,ToList,ToLookup

特殊的AsEnumerable运算符,用于处理LINQ to Entities操作远程数据源,将IQueryable远程数据立即转化为本地的IEnumerable集合。若AsEnumerable接收参数是IEnumerable内存集合则什么都不做。

14.总结

在写这节的时候,参考了不少博客园还有网上的贴子,在此表示感谢。

LINQ学习还只是开始,但是有了上面两节的学习,我们再进行深入学习,就不会不知所措了。

本节主要学习了LINQ to Objects在查询中的使用方法。