【C#】LINQ
一、什么是LINQ
长期以来,开发社区形成以下的格局:
1、面向对象与数据访问两个领域长期分裂,各自为政。
2、编程语言中的数据类型与数据库中的数据类型形成两套不同的体系,例如:
C#中字符串用string数据类型表示。
SQL中字符串用NVarchar/Varchar/Char数据类型表示。
3、SQL编码体验落后
没有智能感知效果。
没有严格意义上的强类型和类型检查。
4、SQL和XML都有各自的查询语言,而对象没有自己的查询语言。
上面描述的问题,都可以使用LINQ解决,那么究竟什么是LINQ呢?
LINQ(Language Integrated Query)即语言集成查询。
LINQ是一组语言特性和API,使得你可以使用统一的方式编写各种查询。用于保存和检索来自不同数据源的数据,从而消除了编程语言和数据库之间的不匹配,以及为不同类型的数据源提供单个查询接口。
LINQ总是使用对象,因此你可以使用相同的查询语法来查询和转换XML、对象集合、SQL数据库、ADO.NET数据集以及任何其他可用的LINQ提供程序格式的数据。
LINQ主要包含以下三部分:
1、LINQ to Objects 主要负责对象的查询。
2、LINQ to XML 主要负责XML的查询。
3、LINQ to ADO.NET 主要负责数据库的查询。
LINQ to SQL
LINQ to DataSet
LINQ to Entities
LINQ教程二:LINQ操作语法
LINQ查询时有两种语法可供选择:查询表达式语法(Query Expression)和方法语法(Fluent Syntax)。
一、查询表达式语法
查询表达式语法是一种更接近SQL语法的查询方式。
LINQ查询表达式语法如下:
1 from<range variable> in <IEnumerable<T> or IQueryable<T> Collection>
2 <Standard Query Operators> <lambda expression>
3 <select or groupBy operator> <result formation>
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子句开始,然后是一个Range变量。 From子句的结构类似于“From rangeVariableName in IEnumerablecollection”。 在英语中,这意味着,从集合中的每个对象。 它类似于foreach循环:foreach(student in studentList)。
在From子句之后,您可以使用不同的标准查询运算符来过滤,分组,连接集合的元素。 LINQ中有大约50个标准查询运算符。标准查询运算符后面通常跟一个条件,这个条件通常使用lambda表达式来表示。
LINQ查询语法总是以Select或Group子句结束。 Select子句用于对数据进行整形。 您可以选择整个对象,因为它是或只有它的一些属性。 在上面的例子中,我们选择了每个结果字符串元素。
例如:我们要从数组中查询出偶数,查询表达式示例代码如下:
var result = from p in ints where p % 2 == 0 select p;
查询表达式语法要点总结:
1、查询表达式语法与SQL(结构查询语言)语法相同。
2、查询语法必须以from子句开头,可以以Select或GroupBy子句结束 。
3、使用各种其他操作,如过滤,连接,分组,排序运算符以构造所需的结果。
4、隐式类型变量 - var可以用于保存LINQ查询的结果。
二、方法语法
方法语法(也称为流利语法)主要利用System.Linq.Enumerable类中定义的扩展方法和Lambda表达式方式进行查询,类似于如何调用任何类的扩展方法。
以下是一个示例LINQ方法语法的查询,返回数组中的偶数:
var result = ints.Where(p => p % 2 == 0).ToArray();
从上面的示例代码中可以看出:方法语法包括扩展方法和Lambda表达式。 扩展方法Where()在Enumerable类中定义。
如果你检查Where扩展方法的签名,你会发现Where方法接受一个谓词委托,如Func <Student,bool>。 这意味着您可以传递任何接受Student对象作为输入参数的委托函数,并返回一个布尔值,如下图所示。 lambda表达式作为在Where子句中传递的委托传递。 在下一节中学习lambda表达式。
三、查询表达式语法VS方法语法
查询表达式语法与方法语法存在着紧密的关系
1、CLR本身并不理解查询表达式语法,它只理解方法语法。
2、编译器负责在编译时将查询表达式语法翻译为方法语法。
3、大部分方法语法都有对应的查询表达式语法形式:如Select()对应select、OrderBy()对应orderby
4、部分查询方法目前在C#中还没有对应的查询语句:如Count()和Max(),这是只能采用以下替代方案:
方法语法
查询表达式语法+方法语法的混合方式
LINQ教程三:Lambda表达式解剖
C#3.0(.NET3.5)中引入了Lambda表达式和LINQ。Lambda表达式是使用一些特殊语法表示匿名方法的较短方法。
最基本的Lambda表达式语法如下:
(参数列表)=>{方法体}
说明:
1、参数列表中的参数类型可以是明确类型或者推断类型。
2、如果是推断类型,则参数的数据类型将由编辑器根据上下文自动推断出来。
让我们看看Lambda表达式是如何从匿名方法演变而来的。
相关示例:
1 delegate(int item) { return item % 2 == 0; };
1、Lambda表达式从匿名方法演变,首先删除delegate关键字和参数类型并添加Lambda运算符=>,演变后的代码如下:
1 (item)=>{return item % 2 == 0;};
2、如果我们只有一个返回值的语句,那么我们不需要花括号、返回和分号,所以我们可以去掉这些符号,演变后的代码如下:
1 (item)=>item %2 == 0;
3、如果我们只有一个参数,我们也可以删除(),代码如下:
1 item=>item %2 == 0;
因此,我们得到如下所示的Lambda表达式:
item => item % 2 == 0
其中item是参数,=>是Lambda运算符,item % 2 == 0是正文表达式。
二、具有多个参数的Lambda表达式
如果需要传递多个参数,那么必须将参数括在括号内,如下所示:
1 (ints, item) => ints.Contains(item);
如果不想使用推断类型,那么可以给出每个参数的类型,如下所示:
1 (int[] ints, int item) => ints.Contains(item)
三、不带任何参数的Lambda表达式
在Lambda表达式中可以没有参数,如下所示:
1 () => Console.WriteLine("这是一个不带任何参数的Lambda表达式");
四、正文表达式中有多条语句
在前面讲过,如果正文表达式有一个语句,那么可以去掉方法体外面的大括号。如果正文表达式中有多条语句,那么必须用大括号将正文表达式括起来,如下所示:
(ints, item) =>
{
Console.WriteLine("这是包含多条语句的Lambda表达式");
return ints.Contains(item);
};
五、表达式中的局部变量
你可以在表达式的主体中声明一个变量,以便在表达式主体的任何位置使用它,如下所示:
1 ints =>
2 {
3 int item = 10;
4 return ints.Contains(item);
5 };
六、Lambda表达式中的内置泛型委托
1、Func委托
当你想从lambda表达式返回一些东西时,使用Func <> delegate。
其中T是输入参数的类型,TResult是返回类型。
示例代码如下:
1 Func<int[], bool> isContains = p => p.Equals(10);
2 int[] ints = { 5, 2, 0, 66, 4, 32, 7, 1 };
3 bool isEquals = isContains(ints);
在上面的例子中,Func委托期望第一个输入参数是int[]类型,返回类型是boolean。Lambda表达式是p => p.Equals(10)。
2、Action委托
与Func委托不同,Action委托只能有输入参数。 当不需要从lambda表达式返回任何值时,请使用Action委托类型。
示例代码如下:
1 Action<int[]> PrintItem = p =>
2 {
3 foreach (int item in p)
4 {
5 Console.WriteLine(item);
6 }
7 };
8 int[] ints = { 5, 2, 0, 66, 4, 32, 7, 1 };
9 PrintItem(ints);
七、在LINQ中使用Lambda表达式
通常情况下,Lambda表达式与LINQ查询一起使用。枚举静态类包括接受Func <TSource,bool>的IEnumerable <T>的Where扩展方法。IEnumerable <Int>集合的Where()扩展方法需要传递Func <Student,bool>,如下所示:
现在,您可以将分配给Func委托的lambda表达式传递给方法语法中的Where()扩展方法,如下所示:
1 Func<int, bool> isContains = p =>p.Equals (4);
2 int[] ints = { 5, 2, 0, 66, 4, 32, 7, 1 };
3 var result = ints.Where(isContains).ToList();
八、Lambda表达式要点总结
1、Lambda表达式是一种表示匿名方法的更短的方法。
2、Lambda表达式语法:parameters =>正文表达式
3、Lambda表达式可以在()中具有零个或多个参数。
4、Lambda表达式可以在大括号{}中的正文表达式中有一条或多条语句。
5、Lambda表达式可以分配给Func,Action或Predicate委托。
6、Lambda表达式可以以类似的方式调用委托。
四 实例操作
一、什么是LINQ
LINQ是Language Integrate Query的缩写,意为语言集成查询,是微软在.Net Framework 4.5版中推出的主要特性之一。
它为开发人员提供了统一的数据查询模式,并与.Net开发语言(如C#和VB.Net)集成,很大程度上简化了数据查询的编码和调试工作,提供了数据查询的性能。
LINQ中查询表达式访问的是一个对象,而该对象可以表示为各种类型的数据源。比如SQL Server数据库,XML文档,ADO.NET数据集,以及内存中的数据集合等。
在.NET类库中,LINQ相关类库都在System.Linq命名空间中,该命名空间提供支持使用LINQ进行查询的类和接口,其中主要是以下两个接口和两个类:
IEnumerable<T>接口:它表示可以查询的数据集合,一个查询通常是逐个对集合对象中的元素进行筛选操作,返回一个新的IEnumerable<T>对象,用来保存查询结果。
IQueryable<T>接口:它继承自IEnumerable<T>接口,表示一个可以查询的表达式目录树。
Enumerable类:它通过对IEnumerable<T>提供扩展方法,实现LINQ标准查询运算。包括过滤、导航、排序、关联、求和、求最大值、求最小值等操作。
Queryable类:它通过对IQueryable<T>提供扩展方法,实现LINQ标准查询运算。包括过滤、导航、排序、关联、求和、求最大值、求最小值等操作。
根据数据源类型,可以将LINQ技术分为以下几个主要技术方向:
1.LINQ to Object:数据源为实现了接口IEnumerable<T>和IQueryable<T>的内存数据集合,这也是LINQ的基础,本文将介绍着方面的内容。
2.LINQ to ADO.NET:数据源为ADO.NET数据集,这里将数据库中的表结构映射到类结构,并通过ADO.NET从数据库中获取数据集到内存,通过LINQ进行数据查询。
3.LINQ to XML:数据源为XML文档,这里通过XElement、XAttribute等类将XML文档数据加载到内存中,通过LINQ进行数据查询。
二、LINQ查询表达式
在进行LINQ查询的编写之前,首先要了解查询表达式。查询表达式是LINQ查询的基础,也是最常用的编写LINQ查询的方法。查询表达式由查询关键字和对应的操作数组成。其中,查询关键字是常用的查询运算符。
在C# 3.0中可以直接使用的查询关键字和功能如下表:
1.用from子句指定数据源
每个LINQ查询表达式都以from子句开始,from子句包括以下两个功能。
(1)指定查询将采用的数据源
(2)定义一个本地变量,表示数据源中单个数据
单个from子句的编写格式为: from localVar in dataSource,其中,dataSource表示数据源,localVar表示单个元素。
示例代码如下:
int[] array = { 1, 2, 4, 5, 7 };
var query = from item in array
select item;
foreach (var item in query)
{
Console.WriteLine(item);
}
2.使用select子句指定目标数据源
select子句指定在执行查询是产生的结果类型,其格式为:select element,其中elment参数指定查询结果中元素的类型及初始化方式。
在进一步介绍select子句之前,先介绍一下本文示例中要用到的实体类:
public class LessonScore
{
/// <summary>
/// 课程成绩
/// </summary>
public float Score { get; set; }
/// <summary>
/// 课程名称
/// </summary>
public string Lession { get; set; }
public override string ToString()
{
return string.Format("{0}-----{1}分",Lession,Score);
}
}
public class Student
{
/// <summary>
/// 学生名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 学生性别
/// </summary>
public string XingBie { get; set; }
/// <summary>
/// 学生年龄
/// </summary>
public int Age { get; set; }
public List<LessonScore> Scores { get; set; }
public override string ToString()
{
return string.Format("{0}---{1}---{2}",Name, XingBie,Age);
}
}
select子句中要选择的目标类型不仅可以为数据源中的元素,还可以是该元素的不同操作结果,包括属性、方法和运算等。示例代码如下:
Student[] students = {
new Student() {Name="乔峰",Age=20,XingBie="男" },
new Student() {Name="欧阳修",Age=22,XingBie="男" },
new Student() {Name="王五",Age=19,XingBie="男" },
new Student() {Name="王丹",Age=20,XingBie="女" },
new Student() {Name="倾国倾城",Age=18,XingBie="女" }
};
var query1 = from student in students
select student;//整个元素作为查询结果
foreach (var item in query1)
{
Console.WriteLine(item);
}
var query2 = from student in students
select student.Name;//元素的属性作为查询结果
foreach (var item in query2)
{
Console.WriteLine(item);
}
var query3 = from student in students
select student.Name.Length;
foreach (var item in query3)
{
Console.WriteLine(item);
}
在某些特殊的场合下,往往查询结果只是临时使用一下,而查询结果的数据包括很多字段,并非一个简单的属性、方法返回值等。这时,可在select子句中使用匿名类型来解决这类问题。
示例代码如下:
//返回数据源中学生的姓名、年龄、姓名的长度
//使用匿名类型方式返回
var query4 = from student in students
select new {student.Name,student.Age,NameLen = student.Name.Length };
foreach (var item in query4)
{
Console.WriteLine(item);
}
3.使用where子句指定筛选条件
通常一个LING查询不会如前面的示例代码那么简单,通常还需要对数据源中的元素进行过滤。只有符合条件的元素,才能参与查询结果的计算。
LINQ中,where子句格式为:where expression,其中,expression是一个逻辑表达式,返回布尔值。
where子句中的条件表达式,可以用&&和||指定多个条件的逻辑运算关系。其中,&&表示逻辑并,||表示逻辑或。
示例代码:
int[] array = { 1,3,9,5,16,20,54,60,18,37};
var query1 = from item in array //查询array中所以大于15的元素
where item > 15
select item;
foreach (var item in query1)
{
Console.Write("{0}, ",item);
}
Console.WriteLine();
var query2 = from item in array //查询array中所以大于10小于40的元素
where item > 10 && item < 40
select item;
foreach (var item in query2)
{
Console.Write("{0}, ", item);
}
Console.WriteLine();
var query3 = from item in array //查询array中小于10或者大于40的元素
where item < 10 || item > 40
select item;
foreach (var item in query3)
{
Console.Write("{0}, ", item);
}
4.使用orderby子句进行排序
在一些场合,还需要对查询结果进行排序。在LINQ中,通过orderby子句对查询结果进行排序操作。
orderby子句格式为:orderby element [ascending|descending],其中element是要进行排序的字段,可以是数据源中的数据,也可以是对元素操作的结果。
[ascending|descending]是排序类型,ascending为升序,descending为降序,默认请客下为ascending。
示例代码:
int[] array = { 1, 3, 9, 5, 16, 20, 54, 60, 18, 37 };
var query1 = from item in array //升序
orderby item
select item;
foreach (var item in query1)
{
Console.Write("{0}, ", item);
}
Console.WriteLine();
var query2 = from item in array //降序
orderby item descending
select item;
foreach (var item in query2)
{
Console.Write("{0}, ", item);
}
Console.WriteLine();
在LINQ中,orderby子句可以同时指定多规排序元素,还可以为每个元素指定独立的排序类型。orderby语句后的第一个排序元素为主要排序,第二个为次要排序,以此类推。
示例代码如下:
Student[] students = {
new Student() {Name="乔峰",Age=20,XingBie="男" },
new Student() {Name="欧阳修",Age=22,XingBie="男" },
new Student() {Name="王五",Age=19,XingBie="男" },
new Student() {Name="王丹",Age=20,XingBie="女" },
new Student() {Name="倾国倾城",Age=18,XingBie="女" }
};
//主要按姓名长度从小到大,次要按年龄从大到小
var query3 = from stu in students
orderby stu.Name.Length ascending, stu.Age descending
select stu;
foreach (var item in query3)
{
Console.WriteLine(item);
}
5.使用group子句进行分组
在LINQ中,用group子句实现对查询结果的分组操作。group子句的常用格式为:group element by key。其中,element表示作为查询结果返回的元素,key表示分组条件。
group子句返回类型为IGrouping<TKey,TElement>的查询结果。
示例代码如下:
Student[] students = {
new Student() {Name="乔峰",Age=22,XingBie="男" },
new Student() {Name="欧阳修",Age=22,XingBie="男" },
new Student() {Name="王五",Age=19,XingBie="男" },
new Student() {Name="王丹",Age=19,XingBie="女" },
new Student() {Name="倾国倾城",Age=19,XingBie="女" }
};
//按学生性别分组
var query1 = from stu in students
group stu by stu.XingBie;
foreach (var grp in query1)
{
Console.WriteLine(grp.Key);
foreach (var item in grp)
{
Console.WriteLine(item);
}
}
//按多条件分组,按性别和年龄分组
var query2 = from stu in students
group stu by new { stu.XingBie, stu.Age };
foreach (var grp in query2)
{
Console.WriteLine("{0}---{1}",grp.Key.XingBie,grp.Key.Age);
foreach (var item in grp)
{
Console.WriteLine(item);
}
}
有时候需要对分组的结果进行排序、再次查询等操作。这就需要使用into关键字将group查询的结果保存到一个临时变量,并且必须使用select子句对其进行重新查询。
into关键字语法为:group element by key into grp,其中,tmpGrp表示一个本地变量,用来临时保存group产生的结果,提供后面的LINQ子句使用。
示例代码如下:
Student[] students = {
new Student() {Name="乔峰",Age=22,XingBie="男" },
new Student() {Name="欧阳修",Age=22,XingBie="男" },
new Student() {Name="王五",Age=19,XingBie="男" },
new Student() {Name="王丹",Age=19,XingBie="女" },
new Student() {Name="倾国倾城",Age=19,XingBie="女" }
};
var query3 = from stu in students
group stu by stu.Age into grp
orderby grp.Key descending
select grp;
foreach (var grp in query3)
{
Console.WriteLine("{0}岁的学生:", grp.Key);
foreach (var item in grp)
{
Console.WriteLine(item);
}
}