一、查询表达式
1、查询表达式的结构
查询表达式由 from 子句和查询主体组成。
- 子句必须按照一定的顺序出现。
- from 子句和 select…group 子句这两部分是必需的。
- 其他子句是可选的。
- 在 LINQ 查询表达式中,select 子句在表达式最后。
- 可以有任意多的 from…let…where 子句。
2、from 子句
from 子句指定了要作为数据源使用的数据集合。它还引入了迭代变量。
要点:
-
1)迭代变量逐个表示数据源的每一个元素。
-
2)from 子句的语法如下:
- Type 是集合中元素的类型。这是可选的,因为编译器可以从集合中推断类型。
- Item 是迭代变量的名字。
- Items 是要查询的集合的名字。集合必须是可枚举的。
from Type Item in Items
//迭代变量声明:Type Item in Items
int arr1 = {10,11,12,13};
var query = from item in arr1
where item < 13
select item;
foreach(var item in query)
Console.Write($"{ item },");
//迭代变量:item
输出结果:
10,11,12,
图演示了 from 子句的语法
LINQ 的 from 子句和 foreach 语句非常相似,但主要的不同点如下:
-
foreach 语句命令式地制定了要从第一个到最后一个按顺序访问集合中的项。而 from 子句则声明式地规定集合中的每个项都要被访问,但并没有假定以什么样的顺序。
-
foreach 语句在遇到代码时就执行其主体,而 from 子句什么也不执行。它创建可以执行查询的后台代码对象。只有在程序的控制流遇到访问查询变量的语句时,才会执行查询。
3、join 子句
有关联结的要点:
- 使用联结来结合两个或更多集合中的数据。
- 联结操作接受两个集合,然后创建一个临时的对象集合,其中每一个对象包含两个原始集合对象中的所有字段。
联结的语法如下,它指定了第二个集合要和之前子句中的集合进行联结:
join Identifier in Collection2 on Field1 equals Field2
//关键字:join、in、on、equals
//指定另外的集合和 ID 来引用它:Identifier in Collection2
//用于比较相等性的字段:Field1 equals Field2
图20-4演示了 join 子句的语法:
join 子句的示例
var query = from s in students
join c in studentsInCourses on s.StID equals c.StID
//第一个集合和 ID:from s in students
//第二集合和 ID:c in studentsInCourses
//第一个集合的项:s.StID
//第二集合的项:c.StID
什么是联结
LINQ 中的 join 接受两个集合,然后创建一个新的集合,其中每一个元素包含两个原始集合中的元素成员。
public class Student
{
public int StID;
public string LastName;
}
public class CourseStudent
{
public string CourseName;
public inr StID;
}
如图所示:
Student 对象为 students,CourseStudent 对象为 studentsInCourses。
*图20-6演示了联结是如何工作的。
如果我们拿第一个学生的记录并把它的 ID 和每一个 studentsInCourses 对象中的学生 ID 进行比较,可以找到两条匹配的记录,中间列的顶部就是。
示例:
class Program
{
public class Student
{
public int StID;
public string LastName;
}
public class CourseStudent
{
public string CourseName;
public int StID;
}
static Student[] students = new Student[]
{
new Student { StID = 1, LastName = "Carson" },
new Student { StID = 2, LastName = "Klassen" },
new Student { StID = 3, LastName = "Fleming" },
};
static CourseStudent[] studentsInCourses = new CourseStudent[]
{
new CourseStudent { CourseName = "Art", StID = 1 },
new CourseStudent { CourseName = "Art", StID = 2 },
new CourseStudent { CourseName = "History", StID = 1 },
new CourseStudent { CourseName = "History", StID = 3 },
new CourseStudent { CourseName = "Physics", StID = 3 },
};
static void Main(string[] args)
{
var query = from s in students
join c in studentsInCourses on s.StID equals c.StID
where c.CourseName == "History"
select s.LastName;
foreach (var q in query)
Console.WriteLine($"Student taking History: { q }");
Console.ReadKey();
}
}
输出结果:
Student taking History: Carson
Student taking History: Fleming
4、查询主体中的 from…let…where 片段
可选的 from…let…where 部分是查询主体的第一部分,可以由任意数量的3种子句构成—— from 子句、let 子句和 where 子句。
1)from 子句
查询表达式从必需的 from 子句开始,后面跟的是查询主体。主体本身可以从任何数量的其他 from 子句开始,每一个 from 子句都指定了一个额外的源数据集合并引入了要在之后运算的迭代变量。
示例:
static void Main(string[] args)
{
var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 6, 7, 8, 9 };
var someInts = from a in groupA//必需的第一个 from 子句
from b in groupB//查询主体的第一个子句
where a > 4 && b <= 8
select new { a, b, sum = a + b };//匿名类型对象
foreach (var x in someInts)
Console.WriteLine(x);
Console.ReadKey();
}
输出结果:
{ a = 5, b = 6, sum = 11 }
{ a = 5, b = 7, sum = 12 }
{ a = 5, b = 8, sum = 13 }
{ a = 6, b = 6, sum = 12 }
{ a = 6, b = 7, sum = 13 }
{ a = 6, b = 8, sum = 14 }
2)let 子句
let 子句接受一个表达式的运算并且把它赋值给一个需要在其他运算种使用的标识符。
let 子句的语法如下:
let Identifier = Expression
示例:
static void Main(string[] args)
{
var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 6, 7, 8, 9 };
var someInts = from a in groupA
from b in groupB
let sum = a + b //在新的变量中保存结果
where sum == 12
select new { a, b, sum};
foreach (var x in someInts)
Console.WriteLine(x);
Console.ReadKey();
}
输出结果:
{ a = 3, b = 9, sum = 12 }
{ a = 4, b = 8, sum = 12 }
{ a = 5, b = 7, sum = 12 }
{ a = 6, b = 6, sum = 12 }
3)where 子句
==where 子句根据之后的运算来去除不符合指定条件的项目。==where 子句如下:
where BooleanExpression
有关 where的要点:
- 只要是在 from…let…where 部分中,查询表达式可以有任意多个 where 子句。
- 一个项必须满足所有 where 子句才能避免在之后被去除。
static void Main(string[] args)
{
var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 6, 7, 8, 9 };
var someInts = from a in groupA
from b in groupB
let sum = a + b
where sum >= 11//条件1
where a == 4 //条件2
select new { a, b, sum };
foreach (var x in someInts)
Console.WriteLine(x);
Console.ReadKey();
}
输出结果:
{ a = 4, b = 7, sum = 11 }
{ a = 4, b = 8, sum = 12 }
{ a = 4, b = 9, sum = 13 }
二、orderby 子句
orderby 子句接受一个表达式并根据表达式按顺序返回结果项。
可选的 ascending 和 descending 关键字设置了排序方向。表达式通常是项的一个字段。该字段不一定非得是数值字段,也可以是字符串这样的可排序类型。
- orderby 子句的默认排序是升序。然后,我们可以使用 ascending 和 descending 关键字显式地设置元素的排序为升序或降序。
- 可以有任意多个子句,它们必须使用逗号分隔。
static void Main(string[] args)
{
var students = new[]
{
new { LName = "Jones", FName = "Mary" , Age = 19, Major = "History" },
new { LName = "Smith", FName = "Bob" , Age = 20, Major = "CompSci" },
new { LName = "Fleming", FName = "Carol" , Age = 21, Major = "History" },
};
var query = from student in students
orderby student.Age//根据年龄排序
select student;
foreach (var s in query)
Console.WriteLine($"{ s.LName },{ s.FName },{ s.Age },{ s.Major }");
Console.ReadKey();
}
输出结果:
Jones,Mary,19,History
Smith,Bob,20,CompSci
Fleming,Carol,21,History
三、select…group 子句
select…group 部分由两种类型的子句组成——select 子句和 group…by 子句。select…group 部分之前的子句指定了数据源和要选择的对象。
select…group 部分的功能如下:
-
1)==select 子句指定应用选择所选对象的哪些部分。==它可以指定下面的任意一项。
- 整个数据项。
- 数据项的一个字段。
- 数据项中几个字段组成的新对象。(或类似其他值)
-
2)group…by 子句是可选的,用来指定选择的项如何被分组。
示例:
static void Main(string[] args)
{
var students = new[]
{
new { LName = "Jones", FName = "Mary" , Age = 19, Major = "History" },
new { LName = "Smith", FName = "Bob" , Age = 20, Major = "CompSci" },
new { LName = "Fleming", FName = "Carol" , Age = 21, Major = "History" },
};
var query = from s in students
select s;
foreach (var q in query)
Console.WriteLine($"{ q.LName },{ q.FName },{ q.Age },{ q.Major }");
Console.ReadKey();
}
输出结果:
Jones,Mary,19,History
Smith,Bob,20,CompSci
Fleming,Carol,21,History
也可以使用 select 子句来选择对象的某些字段。
var query = from s in students
select s.LName;
foreach(var q in query)
Console.WriteLine(q);
输出结果:
Jones
Smith
Fleming
四、查询中的匿名类型
查询结果可以由原始集合的项、原始集合中项的字段或匿名类型组成。
select new { s.LastNmae, s.FirstName, s.Major };
示例:
static void Main(string[] args)
{
var students = new[]
{
new { LName = "Jones", FName = "Mary" , Age = 19, Major = "History" },
new { LName = "Smith", FName = "Bob" , Age = 20, Major = "CompSci" },
new { LName = "Fleming", FName = "Carol" , Age = 21, Major = "History" },
};
var query = from s in students
select new { s.LName, s.FName, s.Major };
foreach (var q in query)
Console.WriteLine($"{ q.LName },{ q.FName } -- { q.Major }");
Console.ReadKey();
}
//创建匿名类型:s.LName, s.FName, s.Major
//匿名类型的访问字段:q.LName、q.FName、q.Major
输出结果:
Jones,Mary – History
Smith,Bob – CompSci
Fleming,Carol – History
五、group 子句
group 子句根据指定的标准对选择的对象进行分组。
有关 group 子句的要点:
-
如果项包含在查询的结果中,它们就可以根据某个字段的值进行分组。作为分组依据的属性叫键(key)。
-
group 子句返回的不是原始数据源中项的枚举,而是返回可以枚举已经形成的项的分组的可枚举类型。
-
分组本身是可枚举类型,它们可以枚举实际的项。
group student by student.Majot;
示例:
static void Main(string[] args)
{
var students = new[]
{
new { LName = "Jones", FName = "Mary" , Age = 19, Major = "History" },
new { LName = "Smith", FName = "Bob" , Age = 20, Major = "CompSci" },
new { LName = "Fleming", FName = "Carol" , Age = 21, Major = "History" },
};
var query = from student in students
group student by student.Major;
foreach (var g in query)//枚举分组
{
Console.WriteLine("{0}", g.Key);
foreach (var s in g)//枚举分组中的项
Console.WriteLine($" { s.LName },{ s.FName }");
}
Console.ReadKey();
}
//分组键:g.Key
输出结果:
History
Jones,Mary
Fleming,Carol
CompSci
Smith,Bob
图20-10演示了从查询表达式返回并保存于查询变量中的对象。
- 从查询表达式返回的对象是从查询中枚举分组结果的可枚举类型。
- 每一个分组由一个叫作键的字段区分。
- 每一个分组本身是可枚举类型并且可以枚举它的项。
六、查询延续:into 子句
查询延续子句可以接受查询的一部分的结果并赋予一个名字,从而可以在查询的另一部分中使用。
示例:
static void Main(string[] args)
{
var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 4, 5, 6, 7 };
var someInts = from a in groupA
join b in groupB on a equals b
into groupAandB //查询延续
from c in groupAandB
select c;
foreach (var v in someInts)
Console.Write($"{ v } ");
Console.ReadKey();
}
输出结果:
4 5 6