linq是Language Integrated Query(LINQ,语言集成查询)属于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委托,是微软为我们预定义的常用委托,封装一个具有:零个或多个指定类型的输入参数并返回一个指定类型的结果值的方法。
代码如下:
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.cs和System.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迭代器。使我们不必“显示”实现IEnumerable或IEnumerator接口。只需要简单的使用 yield 关键字,由 JIT 编译器帮我们编译成实现 IEnumerable或IEnumerator 接口的对象(即:本质还是传统遍历,只是写法上非常简洁),就能使用foreach进行遍历。
图:
延迟计算(Lazy evaluation)
a) 定义:来源自函数式编程,在函数式编程里,将函数作为参数来传递,传递过程中不会执行函数内部耗时的计算,直到需要这个计算结果的时候才调用,这样就可以因为避免一些不必要的计算而改进性能。
b) Yield迭代器的延迟计算原理:JIT 编译器会帮助我们将迭代主体编译到IEnumerator.MoveNext()方法中。从上图foreach的执行流程来看,迭代主体是在每次遍历执行到 in 的时候才会调用MoveNext(),所以其迭代器耗时的指令是延迟计算的。
c) LINQ查询的延迟计算原理:通过给LINQ扩展方法传递方法委托,作为yield迭代器的主体,让遍历执行到MoveNext()时才执行耗时的指令。
5. 表达式树
表达式树:表达式树允许在运行期间建立对数据源的查询,因为表达式树存储在程序集中。(后续在Linq to entities博文中与Queryable一起解说)
Language Integrated Query(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提供程序?
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的实现和本质,并且一些查询只能表示为方法调用。但另一方面,查询表达式通常会比较简单和易读。不管怎样,这两种语法是互相补充和兼容的,我们可以在一个查询中混合使用查询表达式和方法语法。
以下扩展方法存在对应的查询表达式关键字: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之前的查询变量都不可再使用) |
模版如下:
下面以 LINQ to Objects 为例,介绍LINQ中的各种查询。
LINQ to Objects
LINQ to Objects 提供对内存中集合操作的支持,由程序集System.Core.dll中System.Linq命名空间下的Enumerable静态类提供。
运算符图解:
各种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类定义了一个属性Cars,Cars是一个字符串数组。)过滤驾驶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表示在1958到1965年间获得车手冠军的信息列表;teams表示在1958到1965年间获得车队冠军的信息列表)
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);
|
注意:join…on…关键字后的相等使用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);
|
业务说明:返回1958到1965年间的车手冠军和车队冠军信息,根据年份关联
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);
|
业务说明:返回1958到1965年间的车手冠军和车队冠军信息,根据年份关联并分组
注意:直接出现在join子句之后的into关键字会被翻译为GroupJoin,而在select或group子句之后的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) 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 }
...
两个匿名类型的结构必须完全一致,这样编译器会把它们对应到同一个实现类型,从而使连接键值彼此兼容。
整理Linq to Objects中运算符延迟计算特性
按字母顺序整理:
具有延迟计算的运算符 |
Cast,Concat,DefaultIfEmpty,Distinct,Except,GroupBy,GroupJoin,Intersect |
立即执行的运算符 |
Aggregate,All,Any,Average,Contains,Count,ElementAt,ElementAtOrDefault |
特殊的AsEnumerable运算符,用于处理LINQ to Entities操作远程数据源,将IQueryable远程数据立即转化为本地的IEnumerable集合。若AsEnumerable接收参数是IEnumerable内存集合则什么都不做。
end