LINQ笔记

LINQ概述#

  1. 语言集成查询(Language intergrated Query,LINQ)在C#编程语言中集成了查询语法。
  2. 可以使用相同的语法访问不同的数据源
  3. 提供了不同数据源的抽象层,所有可以使用相同的语法

LINQ查询

var query= from r in [list] where r.x=='xxx' orderby r.x desceding select r;

查询表达式必须以from子句开头,以select或者group 结束 变量query只是指定了查询,查询语句不是通过赋值来执行,只要是使用了foreach循环查询,才正式执行

扩展方法

编译器会转换LINQ查询,以调用方法而不是LINQ查询。LINQ为IEnumberable接口提供了各种扩展方法,已便用户在实现该接口的任意集合上使用LINQ查询。
扩展方法可以吧方法添加到实现某个特性接口的任何类中,这样多个类就可以使用相同的实现代码。

public static class StringExtension
{
    public static void Foo(this string s)
    {
        System.Console.WriteLine("Foo invoked for {0}",s);
    }
}

string s="Hello";
s.Foo();

扩展方法不能访问它的扩展类型的私有成员。调用扩展方法只是调用静态方法的一种新语法

string s="Hello";
StringExtension.Foo(s);

只需要导入包含该类的命名空间就使用扩展方法

定义LINQ扩展方法的一个是在System.Linq命名空间Enumerable.

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

yield 关键字向编译器指示它所在的方法是迭代器块。编译器生成一个类来实现迭代器块中表示的行为。在迭代器块中,yield 关键字与 return 关键字结合使用,向枚举器对象提供值。这是一个返回值,例如,在 foreach 语句的每一次循环中返回的值。yield 关键字也可与 break 结合使用,表示迭代结束。

例子:
yield return ;
yield break;

在 yield return 语句中,将计算 expression 并将结果以值的形式返回给枚举器对象;expression 必须可以隐式转换为 yield 类型的迭代器。
在 yield break 语句中,控制权将无条件地返回给迭代器的调用方,该调用方为枚举器对象的 IEnumerator.MoveNext 方法(或其对应的泛型 System.Collections.Generic.IEnumerable)或 Dispose 方法。

方法返回IEnumerable,所有可以使用前面的结果依次调用这些方法。

Func是系统定义的泛型委托 public delegate TResult Func<in T, out TResult>(T arg);

推迟查询执行

在运行期间定义的查询表达式时,查询不会立刻运行。查询会在迭代数据项是运行。

       var names = new List<string> {"Nino","Windy","Juan" };
        //延时查询的案例
        var namesWithJ = from n in names
                         where n.StartsWith("J")
                         orderby n
                         select n;
        Console.WriteLine("First iteration");
        //每次迭代才会执行查询
        foreach( var name in namesWithJ)
        {
            Console.WriteLine(name);
        }
        Console.WriteLine();
        names.Add("John");
        names.Add("Jim");
        Console.WriteLine("Second iteration");
        //每次迭代才会执行查询
        foreach (var name in namesWithJ)
        {
            Console.WriteLine(name);
        }
        Console.ReadLine();

当然,每次在迭代是才使用查询,调用扩展方法,在大多数使用很有效,但是有些情况下我们想立刻执行,可以调用扩展方法ToArray()、ToList()

两次迭代之前输出保持不变,但集合中的值已经改变了。

标准的查询操作符

在Enumerable类定义很多标准查询操作符

筛选

使用where 子句,可以合并多个表达式,传递给where子句的表示结果类型应该是布尔类型:

var namesWithJ = from n in names
//组合表达式,返回布尔类型即可
                         where n.StartsWith("J") && n.Length > 3
                         orderby n
                         select n;

映射到扩展方法Where()和Select(),以下代码会与前面的LINQ查询非常类似的结果:

var namesWithJV2 = names.
            Where(n => n.StartsWith("J") && n.Length > 3).
            Select(n => n);

用索引筛选

不能使用LINQ查询是Where方法的重载,可以传递int参数--索引。可以在表达式中使用这个索引,执行基于索引的计算。

 //
    // 摘要: 
    //     基于谓词筛选值序列。将在谓词函数的逻辑中使用每个元素的索引。
    //
    // 参数: 
    //   source:
    //     要筛选的 System.Collections.Generic.IEnumerable<T>。
    //
    //   predicate:
    //     用于测试每个源元素是否满足条件的函数;该函数的第二个参数表示源元素的索引。
    //
    // 类型参数: 
    //   TSource:
    //     source 中的元素的类型。
    //
    // 返回结果: 
    //     一个 System.Collections.Generic.IEnumerable<T>,包含输入序列中满足条件的元素。
    //
    // 异常: 
    //   System.ArgumentNullException:
    //     source 或 predicate 为 null。
    public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);

案例(LINQ无法实现,Where方法可以实现)

  var namesWithJV2 = names.
            Where((n, index) => n.StartsWith("J") && n.Length > 3 && index % 2 != 0).
            Select(n => n);

类型筛选

为了进行基于类型的筛选,可以使用OfType()扩展方法。

 object[] data = { "one", 2, 3, "four", "five", 6 };
        //筛选出类型是string的值
        var query = data.OfType<string>();
        foreach (var s in query)
        {
            Console.WriteLine(s);
        }

复合的from子句

如果需要根据对象的一个成员进行筛选,而且该成员本身是一个系列,就可以使用复合from子句。

 //复合from语句
        var namesWithJ = from n in names
                         from  c in n.ToCharArray()
                         where c.Equals('N')
                         orderby n
                         select n;

C#编译器吧复合的from子句和LINQ查询转换为SelectMany()扩展方法,该方法用于迭代序列的序列。

    //
    // 摘要: 
    //     将序列的每个元素投影到 System.Collections.Generic.IEnumerable<T>,并将结果序列合并为一个序列,并对其中每个元素调用结果选择器函数。
    //
    // 参数: 
    //   source:
    //     一个要投影的值序列。
    //
    //   collectionSelector:
    //     一个应用于输入序列的每个元素的转换函数。
    //
    //   resultSelector:
    //     一个应用于中间序列的每个元素的转换函数。
    //
    // 类型参数: 
    //   TSource:
    //     source 中的元素的类型。
    //
    //   TCollection:
    //     collectionSelector 收集的中间元素的类型。
    //
    //   TResult:
    //     结果序列的元素的类型。
    //
    // 返回结果: 
    //     一个 System.Collections.Generic.IEnumerable<T>,其元素是对 source 的每个元素调用一对多转换函数
    //     collectionSelector,然后将那些序列元素中的每一个及其相应的源元素映射为结果元素的结果。
    //
    // 异常: 
    //   System.ArgumentNullException:
    //     source 或 collectionSelector 或 resultSelector 为 null。
    public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector);

第一个参数是隐试参数,第二是collectionSelector委托,其中定义内部序列,第三是resultSelector委托,对于第二个序列调用该委托。

暂时没有理解SelectMany方法,后续再研究

排序

要对序列排序,使用orderby子句。

orderby子句解析为OrderBy()方法。

orderby descending对应OrderByDescending()方法。

两个方法返回的都是IOrderedEnumerable,该接口派生 IEnumerable

增加CreateOrderedEnumerable方法。用于进一步给序列排序。可以使用ThenBy()继续排序。
在linq查询是,只需要把所有用户排序的关键字(用逗号分隔开)添加到orderby子句中。

分组

要根据一个关键字值对查询结果分组,可以使用group子句。

内连接

使用join子句可以根据特点的条件合并两个数据源,但之前要获得两个要连接的列表。

左外连接

左外连接返回左边序列中全部元素,即使它在右边的序列中没有匹配的元素。
左外连接使用join子句和DefaultifEmpty方法定义。

分区

扩展方法Take()和Skip()等分区操作可用于分页。
Skip():先忽略根据的页面大小和实际页面计算的项数,
Take():提取一点数量的项

聚合操作符

聚合操作符(Count,Sum,Min,Max,Average,Aggergate)不返回一个序列,而返回一个值。

因为同一个查询需要使用相同的计算超过一次,所以是let子句定义一个变量。

Aggregate()方法,可以传递一个lambda表达式,该表达式对所有值进行聚合。

聚合函数会影响linq的延时加载特性。

转换操作符

在前面提到,查询可以推迟到访问数据项时再执行。再迭代中使用查询时,查询才会执行。而使用转换操作符会立即执行查询,把查询结果放在数组、列表或者字典中。

把返回的对象放在列表中并没有那么简单。对于复杂的可以使用新类Lookup<>来操作。

如需要对非类型泛化的集合上使用LINQ查询们可以使用Case方法。

生成操作符

生成操作符Range()、Empty()、Repear()不是扩展方法,而是返回序列的正常静态方法。

Enumerable.Range(1,20):1 2 3 .....20;

Empty 返回一个不返回值的迭代器;

Repeat方法返回一个迭代器;

表达式树

在LINQ TO objects中,扩展方法需要将一个委托类型作为参数,这样就可以将lambda表达式赋予参数。

lambda表达式也可以赋予Expression类型的参数。C#编译器根据类型给lambda表达式定义的不同行为。如果类型是Expression,编译器就从lambda表达式中创建一个表达式树,并存储在程序集中。这样就可以在运行期间分享表达式树,并进行优化,以便于查询数据源。

在Enumberable类不是唯一定义扩展方法Where()的类。在Queryable类也定义了Where()扩展方法。两者定义是不一样的。

    // 摘要: 
    //     基于谓词筛选值序列。
    //
    // 参数: 
    //   source:
    //     要筛选的 System.Linq.IQueryable<T>。
    //
    //   predicate:
    //     用于测试每个元素是否满足条件的函数。
    //
    // 类型参数: 
    //   TSource:
    //     source 中的元素的类型。
    //
    // 返回结果: 
    //     一个 System.Linq.IQueryable<T>,包含满足由 predicate 指定的条件的输入序列中的元素。
    //
    // 异常: 
    //   System.ArgumentNullException:
    //     source 或 predicate 为 null。
    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);


    //Enumerable


    // 摘要: 
    //     通过合并元素的索引将序列的每个元素投影到新表中。
    //
    // 参数: 
    //   source:
    //     一个值序列,要对该序列调用转换函数。
    //
    //   selector:
    //     一个应用于每个源元素的转换函数;函数的第二个参数表示源元素的索引。
    //
    // 类型参数: 
    //   TSource:
    //     source 中的元素的类型。
    //
    //   TResult:
    //     selector 返回的值的类型。
    //
    // 返回结果: 
    //     一个 System.Collections.Generic.IEnumerable<T>,其元素为对 source 的每个元素调用转换函数的结果。
    //
    // 异常: 
    //   System.ArgumentNullException:
    //     source 或 selector 为 null。
    public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector);

Queryable定义的委托类型是Expression

除了使用委托之外,编译器还会把表达式树放在程序集中。表达式树可以在运行期间读取。

表达式树派生自抽象基类Expression的类中构建。

创建一个表达式树,并将该树存储在程序集中。之后在运行期间使用这个树,创建一个应用于底层数据源的优化查询。

使用Expression类型的一个是ADO.NET Entity Framework 和WCF的数据服务的客户端提供程序。这样访问数据库的LINQ提供程序就可以读取表达式,创建一个运行期间优化的查询,从数据库中获取数据。

无论是用Func还是Expression参数传递,lambda表达式都是相同的。只是编译器的行为不同,它根据source参数来选择。在EF中返回的实现了IQueryable接口的ObjectQuery对象,因此EF中是Queryable类的扩展方法。

参考图书《C#高级编程》
推进阅读 http://www.cnblogs.com/jesse2013/p/happylambda.html

原文地址:https://www.cnblogs.com/wxc-kingsley/p/linq.html