一个典型的LINQ查询由若干个子句组成,from子句指定数据源,where 子句应用筛选器,select 子句负责创建返回的结果对像,
如: int[] numbers=new Int[7]{0,1,2,3,4,5,6}
var numQuery=from num i numbers
where (num%2)==0
select num;
from子句中紧跟着from关键字的变量称为范围变量,它代表数据源(即紧跟in关键字后的那个集合对像)中的单个对像,范围变量可用于后继的子句中,查询的结果由查询变量所代表
Linq查询中类型推断:
根据数据源numbers的数据类型,,编译器可以推断出范围变量num的数据类型,在上述代码中由于numbers是一个int[]类型的数组,所以范围变量num就是int类型的变量,根据slect子句,编译器可以推断出”查询变量”类型,LINQ查询返回的数据类型都是IEnumerable<T>
LINQ查询的延迟执行:
查询变量本身只是存储查询命令,实际的查询执行会延迟到foreach语句中循环访问查询变量时发生,此即为LINQ查询的延迟特性,如果只是创建查询变量,则不会检索任何数据.若要强制立即执行LINQ查询并缓存其结果,可以调用IEnumerable<T>类型的ToList或ToArray
LINQ中混用扩展方法:
LINQ查询是在使用上比较简单,而扩展方法虽然在使用上显得不太方便,但更为灵活和强大,某些复杂功能比较适合于用扩展方法实现,在实际开发中可以在LINQ查询中混用扩展方法,比如:
int[] numbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7 };
int numberQuery = (from num in numbers
where num % 2 == 0
select num).Count();
上述代码中的count就是一个扩展方法
LINQ筛选数据:
示例代码:从C盘上查找创建日期在今天以前的纯文本(.TXT)文件
class Program
{
static void Main(string[] args)
{
IEnumerable<FileInfo> files = from filename in Directory.GetFiles("e:\\")
where File.GetLastWriteTime(filename) < DateTime.Now
&& Path.GetExtension(filename).ToUpper() == ".TXT"
select new FileInfo(filename);
foreach (var f in files)
{
Console.WriteLine(f.Name+","+f.FullName+","+f.Extension);
}
Console.ReadKey();
}
}
1.可以在where子句中直接使用C#的逻辑运算符"&&"
2.可以在select子句中转换数据类型,动态创建新的对像
消除查询结果中的重复项:
示例代码如下:示例程序通过反射列出IEnumerable类型的所有公共成员的名称,因为IEnumerbale类型包含多个同名的重载方法,因为会查出不少重复项,使用distinct()可以去除重复项
class Program
{
static void Main(string[] args)
{
IEnumerable<string> enumberableMethodName = (from method in typeof(Enumerable).GetMembers(
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
select method.Name).Distinct();
foreach (var f in enumberableMethodName)
{
Console.WriteLine(f);
}
Console.ReadKey();
}
}
数据排序:
Linq查询表达式中,order by子句可使返回的结果按升序或降序排序。此子句可以指定多个用于排序的属性,按照从左到右的顺序排列数据。示例代码中首先按照文件大小进行排序,如果文件大小相同再按照文件名称升序排列,默认情况下排序顺序为升序,上述代码中ascending可以省略,如果需要降序排列,将ascending改为descending即可
示例代码:
class Program
{
static void Main(string[] args)
{
IEnumerable<string> fileNames = from filename in Directory.GetFiles("c:\\")
orderby (new FileInfo(filename).Length), filename ascending
select filename;
foreach (var f in fileNames)
{
Console.WriteLine(f);
}
Console.ReadKey();
}
}
组合数据中的特定属性为匿名对像
可以只选择某个对像的部分属性,或者将多个对像部分属性组合为一个新的匿名对像加入到查询结果集中,由于只对文件名和访问时间感兴趣,所以下面的LINQ查询只选取感兴趣的那部分信息构造一个匿名对像,从而得到了一个仅包含所需信息的匿名对像集合
示例代码如下
class Program
{
static void Main(string[] args)
{
var files = from filename in Directory.GetFiles("c:\\")
orderby File.GetLastWriteTime(filename)
select new { Name = filename, LastWriteTime = File.GetLastWriteTime(filename) };
foreach (var f in files)
{
Console.WriteLine(f.Name+","+f.LastWriteTime);
}
Console.ReadKey();
}
}
调用本地方法:
由于LINQ直接与编程语言集成,因此可以在LINQ查询表达式中直接调用编程语言编写的本地方法,这是一个非常强大的功能,可以完成复杂的数据处理工作
示例代码如下:
class Program
{
static void Main(string[] args)
{
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var queryEvenNums = from num in numbers
orderby num
where IsEven(num)
select num;
foreach (var f in queryEvenNums)
{
Console.WriteLine(f);
}
Console.ReadKey();
}
private static bool IsEven(int num)
{
if (num % 2 == 0)
{
return true;
}
return false;
}
}
在LINQ查询中,可以嵌套多个from子句以处理多层次的数据
示例代码如下:嵌套了两层的from子句,外层子句在students集合中每次选择一个学生,内层子句在每个学生对像的成绩集合中检查有没有大于90分的成绩
class Program
{
static void Main(string[] args)
{
List<Student> students = new List<Student>
{
new Student {Name="张三", Scroes= new List<int> {97, 92, 81, 60}},
new Student {Name="李四", Scroes= new List<int> {75, 84, 91, 39}},
new Student {Name="王五", Scroes= new List<int> {88, 94, 65, 85}},
new Student {Name="赵六", Scroes= new List<int> {97, 89, 85, 82}},
new Student {Name="马七", Scroes= new List<int> {35, 72, 81, 70}}
};
var scoreQuer = from student in students
from scroes in student.Scroes
where scroes > 90
select new { student.Name, scroe = student.Scroes.Average() };
foreach (var f in scoreQuer)
{
Console.WriteLine(f.Name+","+f.scroe);
}
Console.ReadKey();
}
}
public class Student
{
public string Name { get; set; }
public List<int> Scroes { get; set; }
}
引入新的范围变量暂存查询结果:
对于一些比较复杂的查询,可以引入一个新的范围变量来暂存子查询的结果
示例代码如下:
class Program
{
static void Main(string[] args)
{
IEnumerable<FileInfo> fileNames = from filename in Directory.GetFiles("c:\\")
let files=new FileInfo(filename)
orderby files.Length, filename ascending
select files;
//等同于如下方式
//IEnumerable<FileInfo> fileNames2 = from filename in Directory.GetFiles("c:\\")
// orderby new FileInfo(filename).Length, filename ascending
// select new FileInfo(filename);
}
}
通过引入新的范围变量files来表示根据filename创建的FileInfo对像,可以在后面直接使用这一变量,避免了多次重复书写 new FileInfo(filename)
数组分组是一种常见的数据处理工作,本节详细介绍如何编写LINQ查询语句实现数据分组
先看一段示例代码:
class Program
{
static void Main(string[] args)
{
List<Student> students = new List<Student> {
new Student{Name="张山",City="广州"},
new Student{Name="李四",City="上海"},
new Student{Name="王五",City="北京"},
new Student{Name="赵六",City="广州"}};
var studentQuery = from student in students
group student by student.City;
foreach (var studentGroup in studentQuery)
{
foreach (var stu in studentGroup)
{
Console.WriteLine(stu.Name + "," + stu.City);
}
}
Console.ReadKey();
}
}
public class Student
{
public string Name { get; set; }
public string City { get; set; }
}
上述代码中最引人注目的是group查询运算符,LINQ查询使用group子句在对数组进行分组。每个分组都有一个”分组标识key”,这个分组标识就是group子句中的by 后指定的那个对像,每个分组中都包含一个或多个数据对像,这些数据对像拥有相同的分组标识每个分组本质上是一个实现了IGrouping<Tkey,TElement>接口的对像,TKey代表分组标识的类型,TElement代表分组中单个数据对像的类型,因此要分组显示所有数据,需要嵌套两层循环:外层循环遍历每个组,内层循环遍历组中的元素。
使用Into语句将分组暂时存到一个临时变量中(类似于前面介绍的let子句),然后在后面的子句(如where和select子句)中使用此临时变量访问分组
示例代码如下:
class Program
{
static void Main(string[] args)
{
//一个单词数组作为数据源
string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese", "elephant", "umbrella", "anteater" };
var strQuery = from word in words
group word by word[0] into grps
where grps.Key=='a' || grps.Key == 'e'
select grps;
foreach (var str in strQuery)
{
foreach (var s in str)
{
Console.WriteLine(s);
}
}
Console.ReadKey();
}
}
上述代码使用临时变量grps暂存分组结果,这样一来就可以在后面的子句中对其作进一步处理
有时候一些规则由外部方法定义,分组条件中可以指定一个方法
class Program
{
static void Main(string[] args)
{
List<Student> list = new List<Student> {
new Student{Name="张山",Scores=new List<int>{80,50}},
new Student{Name="李四",Scores=new List<int> {70}},
new Student{Name="王五",Scores=new List<int>{60,58}},
new Student{Name="赵六",Scores=new List<int>{100}}};
var booleanGroupQuery = from q in list
group q by HasFailed(q.Scores); //返回一个bool值的自定义方法,即key为true,和false
}
public static bool HasFailed(List<int> Scroes)
{
foreach (int scroes in Scroes)
{
if (scroes < 60)
{
return true;
}
}
return false;
}
public class Student
{
public string Name { get; set; }
public List<int> Scores;
}
}
有时候需要将数据分为多组,比如按考试分数将学生成绩数据分为"优","良","中","及格","不及格",只需稍微改动下上述本地方法
示例代码:
class Program
{
static void Main(string[] args)
{
List<Student> list = new List<Student> {
new Student{Name="张山",Scores=new List<int>{80,50}},
new Student{Name="李四",Scores=new List<int> {70}},
new Student{Name="王五",Scores=new List<int>{60,58}},
new Student{Name="赵六",Scores=new List<int>{100}}};
var groupQuery = from student in list
group student by GroupKey(student.Scores);//key即为 优,良,中,及格,不及格
}
public static string GroupKey(List<int> Scroes)
{
int avg=(int)Scroes.Average();
string ret = "";
switch (avg / 10)
{
case 10:
case 9:
ret = "优";break;
case 8:
ret="良";break;
case 7:
ret="中";break;
case 6:
ret = "及格";break;
case 5:
ret = "不及格";break;
}
return ret;
}
public class Student
{
public string Name { get; set; }
public List<int> Scores;
}
}
对像集合的连接操作:
连接处理两个数据集合中的数据。它负责从第一个集合中抽取出部分数据与另一个集合中的数据进行匹配,将相匹配的数据作为查询结果返回,有几种典型的数据连接方式
内连接:从左右集合中选择相匹配的数据返回
//初始化数据
//四个员工
List<Person> people = new List<Person> {
new Person { ID = 1,IDRole = 1,Name="张三"},
new Person { ID = 2,IDRole = 2,Name="李四"},
new Person { ID = 3,IDRole = 2,Name = "王五"},
new Person { ID = 4,IDRole = 3,Name="赵六"}
};
//两种岗位
List<Role> roles = new List<Role> {
new Role { ID = 1, RoleDescription = "项目经理" },
new Role { ID = 2, RoleDescription = "程序员" }
};
var query = from p in people
join r in roles
on p.IDRole equals r.ID
select new { p.Name, r.RoleDescription };
分组连接:分组连接将查询结果按左集合元素进行分组
var query2 = from r in roles
join p in people
on r.ID equals p.IDRole
into pgroup
select new { r.RoleDescription, stuff = pgroup };
给join子句加上into子句即可实现分组连接,当有into子句时,查询会按照左集合数据中的关联属性值进行分组,上述代码中的pgroup代表一个分组,同时这也是一个数据对像的集合,类型为IEnumerable<people>,这与group子句生成的分组类型是不一样的,后者类型为IGrouping<Tkey,TElement>。要访问这样的分组需要进行两层迭代
foreach (var item in query2)
{
Console.Write(item.RoleDescription + ":");
foreach (var p in item.stuff)
{
Console.Write(p.Name + " ");
}
Console.WriteLine();
}
左外连接:包括左集合中的所有元素,如果右集合中没有对应在元素,可以添加一个"临时元素"以维持关联的完整性
要实现左外连接,须使用Enumerable.DefaultEmpay扩展方法,此方法可以为不存在对应元素的左集合元素提供一个对应的"临时元素"
示例代码如:
var query3 = from p in people
join r in roles
on p.IDRole equals r.ID
into pr
from r in pr.DefaultIfEmpty(new Role{ID=3,RoleDescription="临时工"})
select new {p.Name,p.IDRole,r.RoleDescription};