linq学习圣经

   linq是Language Integrated QueryLINQ,语言集成查询)属于net平台。通过LINQ,我们可以使用相同API操作不同的数据源。linq项目,包括linq威客系统交易系统开发,学生管理系统等。

linq使用知识

     隐式类型、匿名类型、对象初始化器

1)        隐式类型,使用var关键字创建,C#编译器会根据用于初始化局部变量的初始值推断出变量的数据类型。(不过我个人认为,能用具体类型的地方尽量不要用var关键字,因为这样会让你遗忘“被封装类库”方法的返回值类型--有损可读性)

隐式类型使用限制:

a)        隐式类型只能应用于方法或者属性内局部变量的声明,不能使用var来定义返回值、参数的类型或类型的数据成员。

b)       使用var进行声明的局部变量必须赋初始值,并且不能以null作为初始值。

2)        匿名类型,只是一个继承了Object的、没有名称的类。C#编译器会在编译时自动生成名称唯一的类。

3)        对象初始化器,提供一种非常简洁的方式来创建对象和为对象的属性赋值。(相关还有“集合初始化器”)

 

由于C#强类型语言,即我们在声明变量时必须指定变量的具体类型。所以在创建匿名对象时,需要结合隐式类型、匿名类型、对象初始化器一起创建匿名对象。(避免类型转换)

       example

              var person = new { name = “heyuquan” , age = 24 }

2.        Lambda表达式,Func委托

1)        Lambda表达式只是用更简单的方式来书写匿名方法,从而彻底简化.NET委托类型的使用。  

Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。

2)        Func委托

       Func委托,是微软为我们预定义的常用委托,封装一个具有:零个或多个指定类型的输入参数并返回一个指定类型的结果值的方法。

linq学习圣经_第1张图片

代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    static void Main(string[] args)
    {
        // 委托函数
        Func<string,string,string> func1 = Hello;
        // 匿名方法
        Func<string,string,string> func2 =
            delegate(string a,string b)
            {
                return "欢迎光临我的博客" + Environment.NewLine + a +" " + b;
            };
        // Lambda表达式
        Func<string,string,string> func3 =
            (a, b) => {return "欢迎光临我的博客" + Environment.NewLine + a +" " + b; };
 
        // 调用Func委托
        string helloStr = func2("滴答的雨",@" http://www.cnblogs.com/heyuquan/");
 
        Console.WriteLine(helloStr);
}
    static string Hello(string a,string b)
    {
        return "欢迎光临我的博客" + Environment.NewLine + a +" " + b;
    }

扩展方法

1)        扩展方法声明在静态类中,定义为一个静态方法,其第一个参数需要使用this关键字标识,指示它所扩展的类型。

2)        扩展方法可以将方法写入最初没有提供该方法的类中。还可以把方法添加到实现某个接口的任何类中,这样多个类就可以使用相同的实现代码。(LINQ中,System.Linq.Queryable.csSystem.Linq.Enumerable.cs 正是对接口添加扩展方法)

3)        扩展方法虽定义为一个静态方法,但其调用时不必提供定义静态方法的类名,只需引入对应的命名空间,访问方式同实例方法

4)        扩展方法不能访问它所扩展的类型的私有成员。

代码如下:    

1
2
3
4
5
6
7
8
9
public static IEnumerable<TSource> MyWhere<TSource>(
    this IEnumerable<TSource> source, Func<TSource,bool> predicate)
{
    foreach (TSource itemin source)
    {
        if (predicate(item))
            yield return item;
    }
}

 Yield迭代器,延迟计算

1)        Yield迭代器

在上面定义的MyWhere扩展方法中,我们使用了yield迭代器。使我们不必“显示”实现IEnumerableIEnumerator接口。只需要简单的使用 yield 关键字,由 JIT 编译器帮我们编译成实现 IEnumerableIEnumerator 接口的对象(即:本质还是传统遍历,只是写法上非常简洁),就能使用foreach进行遍历。

图: linq学习圣经_第2张图片

    

  延迟计算(Lazy evaluation

a)        定义:来源自函数式编程,在函数式编程里,将函数作为参数来传递,传递过程中不会执行函数内部耗时的计算,直到需要这个计算结果的时候才调用,这样就可以因为避免一些不必要的计算而改进性能。

b)        Yield迭代器的延迟计算原理:JIT 编译器会帮助我们将迭代主体编译到IEnumerator.MoveNext()方法中。从上图foreach的执行流程来看,迭代主体是在每次遍历执行到 in 的时候才会调用MoveNext(),所以其迭代器耗时的指令是延迟计算的。

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

5.        表达式树

表达式树:表达式树允许在运行期间建立对数据源的查询,因为表达式树存储在程序集中。(后续在Linq to entities博文中与Queryable一起解说)

 

Language Integrated QueryLINQ,语言集成查询)

linq学习圣经_第3张图片

   

从这幅图中,我们可以知道LINQ包括五个部分:LINQ to ObjectsLINQ to XMLLINQ to SQLLINQ to DataSetLINQ 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 JSONLINQ to MySQLLINQ to AmazonLINQ to FlickrLINQ to SharePoint无论使用什么数据源,都可以通过LINQ使用相同的API进行操作。

 

1.        怎样区分LINQ操作时,使用的是哪个LINQ提供程序?

LINQ提供程序的实现方案是:根据命名空间和第一个参数的类型来选择的。实现扩展方法的类的命名空间必须是打开的,否则扩展类就不在作用域内。Eg:在LINQ to Objects中定义的 Where() 方法参数和在 LINQ to Entities中定义的 Where() 方法实现是不同。 

1
2
3
4
5
6
7
8
9
10
11
12
13
// LINQ to Objects:
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, Func<TSource,bool> predicate);
}
 
// LINQ to Entities
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, Expression<Func<TSource,bool>> predicate);
}

 

2.        LINQ查询提供几种操作语法?

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

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

 

 以下扩展方法存在对应的查询表达式关键字:WhereSelectSelectManyOrderByThenByOrderByDescendingThenByDescendingGroupByJoinGroupJoin

LINQ查询表达式

约束

LINQ查询表达式必须以from子句开头,以selectgroup子句结束

 

关键字

功能

fromin

指定要查找的数据源以及范围变量,多个from子句则表示从多个数据源查找数据。

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

joininonequals

指定多个数据源的关联方式

let

引入用于存储查询表达式中子表达式结果的范围变量。通常能达到层次感会更好,使代码更易于阅读。

orderbydescending

指定元素的排序字段和排序方式。当有多个排序字段时,由字段顺序确定主次关系,可指定升序和降序两种排序方式

where

指定元素的筛选条件。多个where子句则表示了并列条件,必须全部都满足才能入选。每个where子句可以使用谓词&&||连接多个条件表达式。

group

指定元素的分组字段。

select

指定查询要返回的目标数据,可以指定任何类型,甚至是匿名类型。(目前通常被指定为匿名类型)

into

提供一个临时的标识符。该标识可以引用joingroupselect子句的结果。

1)        直接出现在join子句之后的into关键字会被翻译为GroupJoininto之前的查询变量可以继续使用)

2)        selectgroup子句之后的into它会重新开始一个查询,让我们可以继续引入where, orderbyselect子句,它是对分步构建查询表达式的一种简写方式。into之前的查询变量都不可再使用)

      

模版如下: linq学习圣经_第4张图片

     

下面以 LINQ to Objects 为例,介绍LINQ中的各种查询。

 

LINQ to Objects

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

 

运算符图解:

linq学习圣经_第5张图片linq代码实例

      linq学习圣经_第6张图片

            

   各种LINQ示例

1.        过滤操作符

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

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

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

1
2
3
4
5
6
7
     // 查询表达式
     var racer =from rin Formula1.GetChampions()
                 where r.Wins > 15 && r.Country =="Austria"
                 select r;
// 方法语法
     var racer = Formula1.GetChampions().Where(r => r.Wins > 15
         && r.Country =="Austria");

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

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

1
2
object[] data = {"one", 2, 3,"four","five", 6 };
var query = data.OfType<string>();// "one", "four", "five"

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

 

2.        投影操作符

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

       API

1
2
3
public static IEnumerable<TResult> Select<TSource, TResult>(
          this IEnumerable<TSource> source
          , Func<TSource, TResult> selector);

2)         SelectMany

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static IEnumerable<TResult> SelectMany<TSource
   , TResult>(this IEnumerable<TSource> source
   , Func<TSource, IEnumerable<TResult>> selector);
 
   //示例:
   string[] fullNames = {"Anne Williams","John Fred Smith","Sue Green" };
 
   IEnumerable<string> query = fullNames.SelectMany(name => name.Split());
   foreach (string namein query)
      Console.Write(name +"|");
   // Anne|Williams|John|Fred|Smith|Sue|Green|
 
   //如果使用Select,则需要双重循环。
   IEnumerable<string[]> query = fullNames.Select(name => name.Split());
   foreach (string[] stringArrayin query)
      foreach (string namein stringArray)
         Console.Write(name +"|");
   // Anne|Williams|John|Fred|Smith|Sue|Green|

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

1
2
3
4
public static IEnumerable<TResult> SelectMany<TSource, TCollection
    , TResult>(this IEnumerable<TSource> source
    , Func<TSource, IEnumerable<TCollection>> collectionSelector
    , Func<TSource, TCollection, TResult> resultSelector);<br><br>

示例:

业务说明:(Racer类定义了一个属性CarsCars是一个字符串数组。)过滤驾驶Ferrari的所有冠军

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 查询表达式
    var ferrariDrivers =from rin Formula1.GetChampions()
                           from cin r.Cars
                           where c =="Ferrari"
                           orderby r.LastName
                           select r.FirstName +" " + r.LastName;
// 方法语法
    var ferrariDrivers = Formula1.GetChampions()
          .SelectMany(
              r => r.Cars,
              (r, c) =>new { Racer = r, Car = c }
          )
          .Where(r => r.Car =="Ferrari")
          .OrderBy(r => r.Racer.LastName)
          .Select(r => r.Racer.FirstName +" " + r.Racer.LastName);<br><br>

3.        排序操作符

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

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

业务说明:获取车手冠军列表,并依次按照Country升序、LastName降序、FirstName升序进行排序。

1
2
3
4
5
6
7
8
9
// 查询表达式
    var racers =from rin Formula1.GetChampions()
                 orderby r.Country, r.LastNamedescending, r.FirstName
                 select r;
// 方法语法
    var racers = Formula1.GetChampions()
        .OrderBy(r => r.Country)
        .ThenByDescending(r => r.LastName)
        .ThenBy(r => r.FirstName);

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

 

4.        连接操作符

先准备两个集合,如下:racers表示在19581965年间获得车手冠军的信息列表;teams表示在19581965年间获得车队冠军的信息列表

1
2
3
4
5
6
7
8
9
10
11
12
var racers =from rin Formula1.GetChampions()
             from yin r.Years
             where y > 1958 && y < 1965
             select new
             {
                 Year = y,
                 Name = r.FirstName +" " + r.LastName
             };
 
var teams = Formula1.GetContructorChampions()
    .SelectMany(y => y.Years, (t, y) =>new { Year = y, Name = t.Name })
    .Where(ty => ty.Year > 1958 && ty.Year < 1965);

      

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

 

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

API:

1
2
3
4
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
    , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
    , Func<TOuter, TInner, TResult> resultSelector);

 

业务说明:返回19581965年间的车手冠军和车队冠军信息,根据年份关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//  查询表达式
var racersAndTeams =from rin racers
                     join tin teamson r.Yearequals t.Year
                     select new
                     {
                         Year = r.Year,
                         Racer = r.Name,
                         Team = t.Name
                     };
 
// 方法语法
var racersAndTeams = racers.Join(teams
        , r => r.Year, t => t.Year
        , (r, t) =>new { Year = r.Year, Racer = r.Name, Team = t.Name }
    );

 

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

API

1
2
3
4
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
    , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
    , Func<TOuter, IEnumerable<TInner>, TResult> resultSelector);

 

业务说明:返回19581965年间的车手冠军和车队冠军信息,根据年份关联并分组

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    // 查询表达式
    var racersAndTeams =from rin racers
                         join tin teamson r.Yearequals t.Year
                         into groupTeams
                         select new
                         {
                             Year = r.Year,
                             Racer = r.Name,
                             GroupTeams = groupTeams
                         };
 
// 方法语法
    var racersAndTeams = racers
        .GroupJoin(teams
            , r => r.Year, t => t.Year
            , (r, t) =>new { Year = r.Year, Racer = r.Name, GroupTeams = t }
        );

3)        joinonequals…支持多个键关联

可以使用匿名类型来对多个键值进行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 }

                ...

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

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

按字母顺序整理:

具有延迟计算的运算符

CastConcatDefaultIfEmptyDistinctExceptGroupByGroupJoinIntersect 
JoinOfTypeOrderByOrderByDescendingRepeatReverseSelectSelectMany
Skip 
SkipWhileTakeTakeWhileThenByThenByDescendingUnionWhere
Zip

立即执行的运算符

AggregateAllAnyAverageContainsCountElementAtElementAtOrDefault 
EmptyFirstFirstOrDefaultLastLastOrDefaultLongCountMaxMin
Range 
SequenceEqualSingleSingleOrDefaultSumToArrayToDictionaryToList
ToLookup

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

end

    





你可能感兴趣的:(linq学习圣经)