linq学习圣经

   linq是Language Integrated QueryLINQ,语言集成查询)属于net平台。经过LINQ,咱们能够使用相同API操做不一样的数据源。linq项目,包括linq威客系统交易系统开发,学生管理系统等。 数据库

linq使用知识 编程

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

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

隐式类型使用限制: 框架

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

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

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

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

 

因为C#强类型语言,即咱们在声明变量时必须指定变量的具体类型。因此在建立匿名对象时,须要结合隐式类型、匿名类型、对象初始化器一块儿建立匿名对象。(避免类型转换) lua

       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委托,是微软为咱们预约义的经常使用委托,封装一个具备:零个或多个指定类型的输入参数并返回一个指定类型的结果值的方法。


代码以下:
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进行遍历。

图:

    

  延迟计算(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包括五个部分: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 to Objects 为例,介绍LINQ中的各类查询。

 

LINQ to Objects

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

 

运算符图解:

linq代码实例

      

            

   各类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
相关文章
相关标签/搜索