LINQ 简介
语言集成查询 (LINQ) 是 Visual Studio 2008 和 .NET Framework 3.5 版中引入的一项创新功能。 传统上,针对数据的查询都是以简单的字符串表示,而没有编译时类型检查或 IntelliSense 支持。此外,您还必须针对以下各种数据源学习一种不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等等。 通过LINQ, 您可以使用语言关键字和熟悉的运算符针对强类型化对象集合编写查询。一、介绍 LINQ 查询
查询是一种从数据源检索数据的表达式。随着时间的推移,人们已经为各种数据源开发了不同的语言;例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。因此,开发人员不得不针对他们必须支持的每种数据源或数据格式而学习新的查询语言。LINQ 通过提供一种跨数据源和数据格式使用数据的一致模型,简化了这一情况。在 LINQ 查询中,始终会用到对象。可以使用相同的编码模式来查询和转换 XML 文档、SQL 数据库、ADO.NET 数据集、.NET 集合中的数据以及对其有 LINQ 提供程序可用的任何其他格式的数据。1.1 查询操作的三个部分
操作三部曲: ①取数据源 ②创建查询 ③执行查询
internal class Program
{
private static void Main(string[] args)
{
//1.获取数据源
var nums = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
//2.创建查询
var numQuery =
from num in nums
where (num % 2) == 0
select num;
//3.执行查询
foreach (var num in numQuery)
{
Console.WriteLine("{0}", num);
}
}
}
1.2 数据源
在上一个示例中,由于数据源是数组,因此它隐式支持泛型 IEnumerable在 LINQ to SQL 中,首先需要创建对象关系映射。 针对这些对象编写查询,然后由 LINQ to SQL 在运行时处理与数据库的通信。
//从 XML 中创建数据源
//using System.Xml.Linq;
var contacts = XElement.Load(@"c:\xxx.xml");
Customers 表示数据库中的特定表:
var db = new Northwnd(@"c:\northwnd.mdf");
//查询在伦敦的客户
var custQuery =
from cust in db.Customers
where cust.City == "London"
select cust;
1.3 查询
查询指定要从数据源中检索的信息。 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。 查询存储在查询变量中,并用查询表达式进行初始化。 之前的示例中的查询是从整数数组中返回所有的偶数。 该查询表达式包含三个子句:from、where 和 select。(如果您熟悉 SQL,您会注意到这些子句的顺序与 SQL 中的顺序相反。)from 子句指定数据源,where 子句指定应用筛选器,select 子句指定返回的元素的类型。 目前需要注意的是,在 LINQ 中,查询变量本身不执行任何操作并且不返回任何数据。 它只是存储在以后某个时刻执行查询时为生成结果而必需的信息。1.4 查询执行
1.延迟执行
如前所述,查询变量本身只是存储查询命令。 实际的查询执行会延迟到在 foreach 语句中循环访问查询变量时发生。 此概念称为“延迟执行”。2.强制立即执行
对一系列源元素执行聚合函数的查询必须首先循环访问这些元素。Count、Max、Average 和 First 就属于此类查询。由于查询本身必须使用 foreach 以便返回结果,因此这些查询在执行时不使用显式 foreach 语句。另外还要注意,这些类型的查询返回单个值,而不是 IEnumerable 集合。
var numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
var evenNumQuery =
from num in numbers
where (num % 2) == 0
select num;
var evenNumCount = evenNumQuery.Count();
此外,还可以通过在紧跟查询表达式之后的位置放置一个 foreach 循环来强制执行查询。但是,通过调用 ToList 或 ToArray,也可以将所有数据缓存在单个集合对象中。
var numQuery2 =
(from num in numbers
where (num % 2) == 0
select num).ToList();
var numQuery3 =
(from num in numbers
where (num % 2) == 0
select num).ToArray();
二、基本 LINQ 查询操作
2.1 获取数据源:from
在 LINQ 查询中,第一步是指定数据源。像在大多数编程语言中一样,必须先声明变量,才能使用它。在 LINQ 查询中,最先使用 from 子句的目的是引入数据源和范围变量。范围变量类似于 foreach 循环中的迭代变量,但在查询表达式中,实际上不发生迭代。执行查询时,范围变量将用作对 customers 中的每个后续元素的引用。因为编译器可以推断 cust 的类型,所以您不必显式指定此类型。
//queryAllCustomers 是 IEnumerable<Cutsomer> 类型
//数据源 (customers) 和范围变量 (cust)
var queryAllCustomers = from cust in customers
select cust;
2.2 筛选:where
也许最常用的查询操作是应用布尔表达式形式的筛选器。此筛选器使查询只返回那些表达式结果为 true 的元素。使用 where 子句生成结果。实际上,筛选器指定从源序列中排除哪些元素。您可以使用熟悉的 C# 逻辑 AND(&&)和 OR(||) 运算符来根据需要在 where 子句中应用任意数量的筛选表达式。
var queryLondonCustomers = from cust in customers
where cust.City = "London"
select cust;
where cust.City = "London" && cust.Name = "Devon"
where cust.City = "London" || cust.Name = "Paris"
2.3 排序:orderby
通常可以很方便地将返回的数据进行排序。orderby 子句将使返回的序列中的元素按照被排序的类型的默认比较器进行排序。因为 Name 是一个字符串,所以默认比较器执行从 A 到 Z 的字母排序。若要按相反顺序(从 Z 到 A)对结果进行排序,请使用 orderby…descending 子句。
var queryLondonCustomers = from cust in customers
where cust.City = "London"
orderby cust.Name descending
select cust;
2.4 分组:group
使用 group 子句,您可以按指定的键分组结果。您可以指定结果应按 City 分组,以便位于伦敦或巴黎的所有客户位于各自组中。:
在本例中,cust.City 是键。 在使用 group 子句结束查询时,结果采用列表的列表形式。列表中的每个元素是一个具有 Key 成员及根据该键分组的元素列表的对象。在循环访问生成组序列的查询时,您必须使用嵌套的 foreach 循环。外部循环用于循环访问每个组,内部循环用于循环访问每个组的成员。 如果您必须引用组操作的结果,可以使用 into 关键字来创建可进一步查询的标识符。
var queryLondonCustomers = from cust in customers
group cust by cust.City;
foreach (var queryLondonCustomer in queryLondonCustomers)
{
Console.WriteLine(queryLondonCustomer.Key);
foreach (var cust in queryLondonCustomer)
{
Console.WriteLine(cust.Name);
}
}
这里的查询只返回那些包含两个以上的客户的组:
//custQuery 是 IEnumable<IGrouping<string, Customer>> 类型
var custQuery = from cust in customers
group cust by cust.City
into custGroup
where custGroup.Count() > 2
orderby custGroup.Key
select custGroup;
2.5 联接:join
联接运算创建数据源中没有显式建模的序列之间的关联。例如,您可以执行联接来查找位于同一地点的所有客户和经销商。在 LINQ 中,join 子句始终针对对象集合而非直接针对数据库表运行。在 LINQ 中,join 子句始终针对对象集合而非直接针对数据库表运行。 在 LINQ 中,您不必像在 SQL 中那样频繁使用 join,因为 LINQ 中的外键在对象模型中表示为包含项集合的属性。
var innerJoinQuery = from cust in customers
join dist in distributors on cust.City equals dist.City
select new {CustomerName = cust.Name, DistributorName = dist.Name};
from order in Customer.Orders...
2.6 选择(投影):select
select 子句生成查询结果并指定每个返回的元素的“形状”或类型。
例如,您可以指定结果包含的是整个 Customer 对象、仅一个成员、成员的子集,还是某个基于计算或新对象创建的完全不同的结果类型。当 select 子句生成除源元素副本以外的内容时,该操作称为“投影”。三、使用 LINQ 进行数据转换
语言集成查询 (LINQ) 不仅可用于检索数据,而且还是一个功能强大的数据转换工具。通过使用 LINQ 查询,您可以将源序列用作输入,并采用多种方式修改它以创建新的输出序列。您可以通过排序和分组来修改该序列,而不必修改元素本身。但是,LINQ 查询的最强大的功能是能够创建新类型。这一功能在 select 子句中实现。 例如,可以执行下列任务:3.1 将多个输入联接到一个输出序列
学生和老师两个类:
class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string City { get; set; }
public List<int> Scores { get; set; }
}
class Teacher
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string City { get; set; }
}
控制台输出代码:
internal class Program
{
private static void Main(string[] args)
{
//创建第一个数据源
var students = new List<Student>()
{
new Student()
{
Age = 23,
City = "广州",
Name = "小C",
Scores = new List<int>(){85,88,83,97}
},
new Student()
{
Age = 18,
City = "广西",
Name = "小明",
Scores = new List<int>(){86,78,85,90}
},
new Student()
{
Age = 33,
City = "梦里",
Name = "小叁",
Scores = new List<int>(){86,68,73,97}
}
};
//创建第二个数据源
var teachers = new List<Teacher>()
{
new Teacher()
{
Age = 35,
City = "梦里",
Name = "啵哆"
},
new Teacher()
{
Age = 28,
City = "云南",
Name = "小红"
},
new Teacher()
{
Age = 38,
City = "河南",
Name = "丽丽"
}
};
//创建查询
var peopleInDreams = (from student in students
where student.City == "梦里"
select student.Name)
.Concat(from teacher in teachers
where teacher.City == "梦里"
select teacher.Name);
//执行查询
foreach (var person in peopleInDreams)
{
Console.WriteLine(person);
}
Console.Read();
}
}
3.2 选择各个源元素的子集
1. 若要只选择源元素的一个成员,请使用点运算。2. 若要创建包含源元素的多个属性的元素,可以使用具有命名对象或匿名类型的对象初始值设定项。
var query = from cust in Customers
select cust.City;
var query = from cust in Customer
select new {Name = cust.Name, City = cust.City};
3.3 将内存中的对象转换为 XML
//创建数据源
var students = new List<Student>()
{
new Student()
{
Age = 18,
Name = "小A",
Scores = new List<int>() {88,85,74,66 }
},
new Student()
{
Age = 35,
Name = "小B",
Scores = new List<int>() {88,85,74,66 }
},
new Student()
{
Age = 28,
Name = "小啥",
Scores = new List<int>() {88,85,74,66 }
}
};
//创建查询
var studentsToXml = new XElement("Root",
from student in students
let x = $"{student.Scores[0]},{student.Scores[1]},{student.Scores[2]},{student.Scores[3]}"
select new XElement("student",
new XElement("Name", student.Name),
new XElement("Age", student.Age),
new XElement("Scores", x))
);
//执行查询
Console.WriteLine(studentsToXml);
3.4 对源元素执行操作
输出序列可能不包含源序列的任何元素或元素属性。输出可能是通过将源元素用作输入参数计算出的值的序列。
//数据源
double[] radii = {1, 2, 3};
//创建查询
var query = from radius in radii
select $"{radius * radius * 3.14}";
//执行查询
foreach (var i in query)
{
Console.WriteLine(i);
}
四、LINQ 查询操作的类型关系
LINQ 查询操作在数据源、查询本身及查询执行中是强类型的。查询中变量的类型必须与数据源中元素的类型和 foreach 语句中迭代变量的类型兼容。强类型可以保证在编译时捕获类型错误,以便及时改正。4.1 不转换源数据的查询
下图演示不对数据执行转换的 LINQ to Objects 查询操作。源包含一个字符串序列,查询输出也是一个字符串序列。4.2 转换源数据的查询
下图演示对数据执行简单转换的 LINQ to SQL 查询操作。查询将一个 Customer 对象序列用作输入,并只选择结果中的 Name 属性。因为 Name 是一个字符串,所以查询生成一个字符串序列作为输出。4.3 让编译器推断类型信息
您也可以使用关键字 var,可用于查询操作中的任何局部变量。但是,编译器为查询操作中的各个变量提供强类型。五、LINQ 中的查询语法和方法语法
我们编写的 LINQ 查询语法,在编译代码时,CLR 会将查询语法转换为方法语法。这些方法调用标准查询运算符的名称类似 Where、Select、GroupBy、Join、Max和 Average,我们也是可以直接使用这些方法语法的。 查询语法和方法语法语义相同,但是,许多人员发现查询语法更简单、更易于阅读。某些查询必须表示为方法调用。例如,必须使用方法调用表示检索元素的数量与指定的条件的查询。还必须使用方法需要检索元素的最大值在源序列的查询。System.Linq 命名空间中的标准查询运算符的参考文档通常使用方法语法。5.1 标准查询运算符扩展方法
下面的示例演示简单的查询表达式和编写为基于方法的查询的语义上等效的查询:
static void Main(string[] args)
{
var nums = new int[4] { 1, 2, 3, 4 };
//创建查询表达式
var qureyNums = from n in nums
where n % 2 == 0
orderby n descending
select n;
Console.WriteLine("qureyNums:");
foreach (var n in qureyNums)
{
Console.WriteLine(n);
}
//使用方法进行查询
var queryNums2 = nums.Where(n => n % 2 == 0).OrderByDescending(n => n);
Console.WriteLine("qureyNums2:");
foreach (var n in queryNums2)
{
Console.WriteLine(n);
}
Console.Read();
}