Linq透视(1)
时间:2010-11-28 来源:新瓶老酒
首先,简述一下位于System.Linq命名空间下的几个接口和重要的类:
- IEnumerable<T>接口,表示一个可以可查询和可操作的数据集合;
- IQueryable<T>接口,它继承自IEnumerable<T>接口,表示可以查询表达式的目录树;
- Enumerable类,通过IEnumerable<T>接口提供了一组扩展方法,并实现Linq查询标准运算符;
- Queryable类,通过IQueryable<T>接口提供一组扩展方法,并实现Linq查询的标准运算符;
注: Enumerable和Queryable类中的扩展方法包括:过滤,导航、排序、查询、联连、求和、最(大、小)值等操作,具体会在后在分享;
Linq表达式关键字:
- from 指定要查询的数据源以及范围变量,若有多个from 子句则从多个数据源中查找;
- select 指定查询返回的数据,其类型可以是任意类型,或匿名类型;
- where 指定元素的筛选条件,可有多个where子句,表示TSQL中的AND(并且)关系;
- orderby 指定元素的排序字段和排序方式,若有多个排列字段时,由字段顺序确定主次关系,可指定升(ascending)或降(descending)序排列,默认为ascending(升序);
- group ..by group指定要分组的字段数据源,by后面是要分组的条件;
- join 指定多个数据源的关联方式;
为了更好的演示Linq表达式的用法,先将公共的代码放在这了。
public class Student {
public String ID { get; set; }
public String Name { get; set; }
public String Sex { get; set; }
public uint Age { get; set; }
public List<LessonScore> Score { get; set; }
public override string ToString() {
return string.Format("学生编号:{0};姓名:{1};性别:{2};年龄:{3};", ID, Name, Sex, Age);
}
}
public class LessonScore {
public string LessonScoureID { get; set; }
public string StudentID { get; set; }
public string Lesson { get; set; }
public float Score { get; set; }
public override string ToString() {
return string.Format("学生编号:{0};编号:{1};课程:{2};成绩:{3};", StudentID, LessonScoureID, Lesson, Score);
}
}
int[] arr = { 1, 2, 3, 4, 7, 890, 5423, 12, 3, 561 };
List<Student> students = new List<Student> {
new Student {
ID="1001",
Name="刘总",
Age=25,
Sex="男",
Score=new List<LessonScore> {
new LessonScore {
StudentID="1001",
Lesson="语文",
Score=67
}
}
},
new Student {
ID="1002",
Name="贺总",
Age=23,
Sex="男",
Score=new List<LessonScore> {
new LessonScore {
StudentID="1002",
Lesson="化学",
Score=61
}
}
},
new Student {
ID="1003",
Name="李总",
Age=25,
Sex="男",
Score=new List<LessonScore> {
new LessonScore {
StudentID="1003",
Lesson="生物",
Score=81
}
}
},
new Student {
ID="1004",
Name="小丽",
Age=18,
Sex="女",
Score=new List<LessonScore> {
new LessonScore {
StudentID="1004",
Lesson="绘画",
Score=59
}
}
}
};
private static bool Comparison(int x) {
return x > 500 && x < 5000;
}
关键字透视
-
from 字句:
有两个功能,即指定要查询的数据源和定义一个本地变量,表示数据源中的单个元素;
示例1:
#region select和from语句
//简单的Linq表达式。相当于TSQL语句: select * from [表名];
var query0 = from num in arr
select num;
//注意:在这明确指定了,自动推断类型是Student类型。
//若没有特别的需要,不建议使用这种方式
//建议使用编译器自动根据数据源类型具体的元素的类型。
var query1 = from Student i in arr
select i;
//以匿名方式返回
var query3 = from student in students
select new {
student.Name,
student.Sex,
student.Age,
LessonCount = student.Score.Count
};
foreach (var stu in query3) {
System.Console.WriteLine(stu);
}
#endregion
-
select 子句:
指定查询返回的数据,其类型可以是任意类型,或匿名类型(在上例(示例1)中query3就是很好的例子);
-
where 子句:
指定元素的筛选条件,可有多个where子句,表示TSQL中的AND(并且)关系;
示例2:
#region where语句
//查找数组中大于500的数字列表
var query4 = from i in arr
where i > 500
select i;
//查找数组中大于500且小于5000的数字列表
var query5 = from i in arr
where i > 500 && i < 5000
select i;
//演示多个where,注意其时,也是把多个where并成了and的形式
var query6 = from i in arr
where i > 500
where i < 5000
select i;
//注意啦,Comparison()是一个函数
var queryf = from i in arr
where Comparison(i)
select i;
//注:以上query5,query6都是等效的方式。
//技巧:在where子句中的条件尽可能简单易懂
//并可以以函数的方式来提供判断条件,当出现
//多个逻辑并(&&)条件时,可考虑使用where子句
//的方式去代替。当然的看你具体个人习惯了。
#endregion -
orderby 子句:
指定元素的排序字段和排序方式,若有多个排列字段时,由字段顺序确定主次关系,可指定升(ascending)或降(descending)序排列,默认为ascending(升序);
示例3:
#region orderby 语句
//查询所有,按从小到大的顺序排序
var query7 = from i in arr
orderby i ascending //ascending升/descending降 默认排序升序
select i;
var query8 = from entry in students
orderby entry.Name ascending, entry.Age descending
select entry;
//注意:当一个表达式中有多个 orderby语句时,只有最后一个起作用,前面的都无效。
#endregion
-
group ..by group指定要分组的字段数据源,by后面是要分组的条件;
group子名常用的格式:
group element by key
其中element作为查询结果返回的元素,key作为分组的条件,group子句的返回值的类型为IGrouping<TKey,TElement>的查询结果。其中TKey的参型是由key的数据类型来决定的,TElement的参数类型是由element的数据类型来决定的。
IGrouping<TKey,TElement>可以看成一个HashTable内部嵌套一个List列表的数据结果,它包括一个属性,即Key,其类型为TKey,表示查询中的分组关键字,通常使用两层foreach来遍历IGrouping中的所有元素。其外层foreach使有Key遍历,内层使用foreach按照对应的元素列表遍历。
示例4:
#region group子句的简单用法以及遍历值
// 按学生性别进行分组
var query9 = from stu in students
group stu by stu.Sex;
foreach (var entry in query9) {
System.Console.WriteLine(entry.Key);
foreach (var student in entry) {
System.Console.WriteLine(student);
}
}
#endregion
有实际应用中,需要对分组的结果进行排序,再次操作。此时就需要使用into关键字将group查询的结果保存到一个临时变量中,并且必须使用新的select或group子句对其进行重新查询,也可以使用orderby进行排序或使用where过滤操作。
Into关键字语法格式:
group element by key into tempGroup
其中tempGroup表示本地的一个变量,用来临时保存group产生的结果,给后面的Linq子句作为数据源使用。
示例5:
#region group子句的简单用法以及遍历值
//按学生性别进行分组,并按性别进行排序
var query10 = from student in students
group student by student.Sex
into SexGroup
orderby SexGroup.Key descending
select SexGroup;
#endregion
注:在上例(示例5)中,students是IEnumerable<Student>类型,student是一个Student类型,SexGroup是一个IGrouping<int,Student>类型,最后query10的查询结果是通过select子句产生,是一个IEnumerable<T>类型,它的元素为SexGroup,故:query10的最终类型为IEnumerable<IGrouping<int,Student>>类型。
-
用from子句进行复合查询
在实际开发中通常会用到一个数据集是需要从多个表中获取的,例如,查找学生对应学生成绩信息。此使就需要from子句进行复合查询来实现了。
示例6:
#region from子句进行复合查询
var query11 = from student in students
from score in student.Score
where score.Score > 50
group new {
student.Name,
score
} by student.Name;
#endregion
注:在上例(示例6)中,内层的from子句并非一定要查询外层from子句中元素的属性或方法,可以是任何数据源(示例7)。
示例7:
#region from 子句进行复合查询,使用外总数据源
int[] arr1 = { 5, 15, 25, 30, 33, 50 };
int[] arr2 = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
var query12 = from n1 in arr1
from n2 in arr2
where n2 % n1 == 0
group n2 by n1;
foreach (var num in query12) {
System.Console.Write("{0}: ", num.Key);
foreach (var i in num) {
System.Console.Write("{0} ", i);
}
System.Console.WriteLine();
}
#endregion
-
join 子句进行内连接
内联接:
join element in datascoure on exp1 equals exp2
#region 用join子句进行内联接
List<Student> datascoure1 = new List<Student> {
new Student {
ID="1001",
Age=22,
Name="刘总",
Sex="男"
},
new Student {
ID="1002",
Age=23,
Name="李总",
Sex="男"
},
new Student {
ID="1003",
Age=20,
Name="小丽",
Sex="女"
}
};
List<LessonScore> datascoure2 = new List<LessonScore> {
new LessonScore {
LessonScoureID="1001",
Lesson="语文",
Score=60,
StudentID="1001"
},
new LessonScore {
LessonScoureID="1002",
Lesson="文学",
Score=60,
StudentID="1002"
},
new LessonScore {
LessonScoureID="1003",
Lesson="理化",
Score=60,
StudentID="1003"
},
new LessonScore {
LessonScoureID="1004",
Lesson="生物",
Score=60,
StudentID="1004"
}
};
//注意:join不可以使用同部数据源
var query13 = from student in datascoure1
join lessonScore in datascoure2 on student.ID equals lessonScore.StudentID
select new {
student,
lessonScore
};
foreach (var var1 in query13) {
System.Console.WriteLine("{0} {1}", var1.student, var1.lessonScore);
}
#endregion
-
join 子句进行分组连接
内联接:
join element in datascoure on exp1 equals exp2 into TempGroupName
分组连接可用于产生分层的数据结果,它将第一个结果集中的每一个元素与第二集合中的一组相关元素进行配对,如果,第一个集合中的元素在第二集合中没有配对元素,也会为它产生
#region 用join子句进行分组联连
List<int> nums1 = new List<int> { 5, 15, 25, 30, 33, 40 };
List<int> nums2 = new List<int> { 10, 20, 30, 50, 60, 70, 80 };
var query14 = from num1 in nums1
join num2 in nums2 on num1 % 5 equals num2 % 15 into NewGroup
select new {
VAL1 = num1,
VAL2 = NewGroup
};
foreach (var var1 in query14) {
System.Console.Write("{0}: ", var1.VAL1);
foreach (var var2 in var1.VAL2) {
System.Console.Write("{0} ", var2);
}
System.Console.WriteLine();
}
#endregion
-
join 子句进行左外部连接
语法格式:
join element in datascoure on exp1 equals exp2 into TempGroupName
分组连接可用于产生分层的数据结果,它将第一个结果集中的每一个元素与第二集合中的一组相关元素进行配对,如果,第一个集合中的元素在第二集合中没有配对元素,也会为它产生
#region 用join子句进行分组联连
List<int> nums1 = new List<int> { 5, 15, 23, 30, 33, 40 };
List<int> nums2 = new List<int> { 10, 20, 30, 50, 60, 70, 80 };
var query14 = from num1 in nums1
join num2 in nums2 on num1 % 5 equals num2 % 15 into NewGroup
select new {
VAL1 = num1,
VAL2 = NewGroup
};
foreach (var var1 in query14) {
System.Console.Write("{0}: ", var1.VAL1);
foreach (var var2 in var1.VAL2) {
System.Console.Write("{0} ", var2);
}
System.Console.WriteLine();
}
#endregion
- join 子句进行左外部连接
#region 用join子句进行分组联连
List<int> nums3 = new List<int> { 5, 15, 23, 30, 33, 40 };
List<int> nums4 = new List<int> { 10, 20, 30, 50, 60, 70, 80 };
var query15 = from num1 in nums3
join num2 in nums4 on num1 % 5 equals num2 % 15 into NewGroup
from grp in NewGroup.DefaultIfEmpty()
select new {
VAL1 = num1,
VAL2 = grp
};
foreach (var var1 in query15) {
System.Console.WriteLine("{0}: ", var1);
}
#endregion
结论:
- 一个Linq表达式中可以包括多个where子句,是以并且(And)的关系。
-
一个表达式中可以使用多个orderby子句,但是只有最后一个有效,前面都无效。
在后续文章中会继续探讨关Linq话题。如果不足,欢迎指正。