LINQ 代表 语言集成查询,它是 .NET 框架的扩展,使用 LINQ 轻松地从 数据库,程序对象的集合 以及 XML文档 中查询数据。
对于每一种数据源类型,其背后一定有根据该数据源类型实现 LINQ 的代码模块,这些代码模块叫做 LINQ提供程序。
1 LINQ概述
1.1 方法语法和查询语法
在使用LINQ查询是可以组合使用两种形式的语法:方法语法 和 查询语法。
- 方法语法:使用标准的方法调用,这些方法是一组叫做 标准查询运算符 的方法
- 查询语法:与 SQL 语法类似,使用 查询表达式 形式书写
- 查询语法是 声明式,它描述的是你想返回的数据,但没有指明如何执行
- 方法语法是 命令式,它指明了查询方法调用的顺序
- 最终,编译器会将使用查询语法(查询表达式)翻译为方法调用的形式(标准查询运算符)
1.2 查询变量
LINQ查询可以返回两种类型的结果: 枚举 和 标量(单一值)。
在 查询表达式 的等号左边的变量叫做 查询变量
使用查询变量是,要注意查询执行的时间:
- 当 查询表达式 返回枚举时,查询会一直等到处理枚举是才会执行,即此时的查询变量不会包含查询的结果
- 如果枚举被处理多次,那么查询就会被执行多次,即如果在查询执行之前数据有变动,则查询会使用最新的数据
- 如果查询表达式返回的是标量,查询会立即执行,并把结果保存在查询变量中。
2 查询语法
2.1 查询表达式的结构
查询表达式由 from
子句 和 查询主体 组成,其中 from
子句和查询体中的 select
子句这两部分是必须的。
2.1.1 from
子句
from
子句指定了要作为数据源使用的数据集合,它引入了迭代变量,语法:
from Type Item in Items
LINQ的from
子句和 foreach
语句主要有以下不同:
foreach
语句 命令式 地指定了要从第一个到最后一个按顺序地访问集合中的项,而from
子句则是声明式的规定集合中的每个项都要被访问,但并没有假定以什么的顺序foreach
语句在遇到代码时就执行其主体,而from
子句则等到程序控制流遇到访问查询变量的语句时,才会执行
2.1.2 join
子句
LINQ中的 join
子句接受两个集合然后创建一个新的集合,新的集合中每个元素包含两个原始集合中的原始成员。语法:
var query = from s in students
join c in studentsInCoures on s.StID equals c.StID
where c.CourseName == "History"
select s.LastName
注意
join
语句必须使用上下文关键字equals
,而不能使用==
运算符
2.1.3 from..let..where
子句
可选的 from..let..where
子句是 *查询主体 的第一部分,可以由任意数量的3个子句组合 from
子句、let
子句和 where
子句组成(也就是说from..let..where
不是一定要三个子句同时组成)。
2.1.3.1 from
子句
查询表达式从必需的 from
子句开始,后面跟得是 查询主体。而查询主体本身有可以从任何数量的其他 from
子句开始,但所有的 from
子句的语法和含义都是一样的。
2.1.3.2 let
子句
let
子句接受一个表达式的运算并把它赋值给一个需要在其他运算中使用的标识符。eg:
var someInts = from a in groupA
from b in groupB
let sum = a + b
where sum == 12
select new {a, b, sum}
2.1.3.3 where
子句
where
子句根据之后的运算来去除不符合指定条件的项。只要是在 from .. let .. where
部分中,查询表达式可以有任意多个 where
子句。
var someInts = from int a in groupA
from int b in groupB
let sum = a + b
where sum >= 11
where a == 4
select new {a, b, sum};
2.1.4 orderby
子句
orderby
子句接受一个表达式并根据表达式按顺序返回结果项。
orderby
子句的默认排序是升序,可以使用 ascending
和 descending
关键字显示地设置排序为升序还是降序。
可以有多个 orderby
子句,使用 ,
逗号分隔,eg:
var query = from student in students
orderby student.Age descending
select student
2.1.5 select..group
子句
select..group..
字句由 select
字句和 group..by..
字句组成,select..group
字句之前的字句 指定了数据源 和 要选择的对象,而select
字句则指定了所选对象的哪些部分要被 select
,其中的group..by..
字句是可选的,它用来指定选择了的项如何分组,二者可以单独使用。
2.1.5.1 匿名类型
select
字句选择的查询结果可以由以下内容组成:
- 原始集合的项
- 项某些的字段
- 匿名类型
创建 匿名类型使用类似 对象初始化语句 一样的形式,都是没有类名或构造函数,eg:
// 匿名对象初始化语句
new {FieldProp = InitExpr, FieldProp = InitExpr, ...}
// 匿名类型数组
new[] {
{FieldProp = InitExpr, FieldProp = InitExpr, ...},
{FieldProp = InitExpr, FieldProp = InitExpr, ...}
}
要注意的是:
- 匿名类型只能与局部变量配合使用,不能用于类成员
- 由于匿名类型没有类型名称,所有必须使用
var
关键字 - 除了对象初始化语句的普通的赋值形式,匿名类型的的对象初始化语句还有两种 赋值形式:简单标识符 和 成员访问表达式,这两种形式叫做投影初始化语句,eg:
class Other
{
static public string Name = 'seiei';
}
class Program
{
static void Main()
{
string Major = "History";
var student = new {Age=19, Major, Other.Name}; // 第二种为简单标识符,第三种为成员访问表达式
}
}
2.1.5.2 group
字句
group
字句可以把 select
的对象根据一些标准进行分组,作为分组依据的属性叫做 键(Key),eg:
var query = from student in students
group student by student.Major;
foreach (var s in query)
{
Console.log("{0}", s.Key); // 分组键,Key值
foreach (var t in s)
{
Console.log("{0}, {1}", t.Name, t.FName);
}
}
2.1.6 into
字句
into
字句是 查询延续字句,它可以接受查询的一部分结果并赋予一个名字,从而在查询的另一个部分中使用。eg:
var someInts = from a in groupA
join b in groupB on a equals b
into groupAandB
from c in groupAandB
select c;
3 方法语法
3.1 标准查询运算符
标准查询运算符有一组API组成,它能查询任何.NET数组或集合。一些运算符返回 IEnumerable
对象,而其他的一些运算符返回标量。返回变量的运算符立即执行,并返回一个值。
被查询的集合叫做序列,它必须实现 IEnumerable<T>
接口。Stytem.Linq.Enumbrable
类声明了标准查询运算符方法,即标准查询运算符实质是扩展了 IEnumerable<T>
泛型类的 扩展方法。
3.1.1 委托作为参数
因为运算符是 IEnumerable<T>
的扩展方法,所以每一个运算符的第一个参数是 IEnumerable<T>
对象的引用,之后的参数可以是任何类型。
而很多运算符接受泛型委托作为参数。这些运算符常常需要提供代码来指示运输费如何执行它的操作,LINQ定义了两套泛型委托类型与标准查询一起使用,即 Func
委托与 Action
委托,各有17个成员。
// Func
public delegate TR Func<out TR>(); // 没有参数方法
public delegate TR Func<in T1, out TR>(T1 a1); //接受一个参数方法
// Action, 没有返回值
public delegate void Action(); // 没有参数方法
public delegate void Action<in T1>(T1 a1);
其中:
Func
委托有返回值,而Action
委托没有返回值。Func
委托中返回值类型TR
必须作为类型参数列表中的最后一个并且为协变,而其他的类型参数都是逆变。
举例:
class Program
{
static bool IsOdd(int x) // 委托对象使用的方法
{
return x%2 == 1;
}
static void Main()
{
int[] intArray = new int[] {3,4,5,6,7};
Func<int, bool> myDel = new Func<int, bool>(IsOdd); // 委托对象
var countOdd = intArray.Count(myDel); // 使用委托
Counsole.WriteLine("Count of odd numbers:{0}", countOdd);
}
}
注意:不需要声明
Func
委托类型,因为LINQ已经预定义了。
3.1.2 使用Lambda表达式作为参数
可以使用更简洁和跟局部化的方法来给运算符提供代码,那就是使用Lambda表达式,eg:
calss Program
{
static void Main()
{
int[] intArray = new int[] {3,4,5,6,7,9};
var countOdd = intArray.Count(x => x%2 == 1);
Console.WriteLine("Count of odd numbers:{0}", countOdd);
}
}
4 LINQ to XML
LINQ to XML可以创建、查询和操作XML,感觉就像JavaScript操作DOM树,而最大的不同是,LINQ to XML在搜索一个XML树时,不需遍历它。
需要了解XML文档的重要事项:
- XML文档必须有一个根元素包含所有其他元素
- XML标签区分大小写
- XML也有属性(如同HTML中的
class
、style
等) - XML文档中的空格是有效的,这与HTML把空格作为单个空格输出不同。
4.1 XML类
LINQ to XML可以用两种方式和XML配合使用,第一种方式是作为简化的XML操作API,第二种是使用LINQ查询工具。
4.1.1 LINQ to XML API
LINQ to API由很多表示XML树组件的类组成,最常使用到的XML类有以下几种:
XDocument
(XML树),可作为其直接地子节点有:
XProcessingInstruction
节点:提供XML该如何被使用和翻译的额外数据XDeclaration
节点:包含XML使用的版本号、字符编码等XDocumentType
节点XElement
节点,若在XDocument
中有最高级别的XElement
节点,即它就是XML树中其它元素的 根元素
XElement
,可作为其嵌套节点的有:
XElement
XComment
节点:注释XAttribute
4.1.1.1 创建XML树
以下是使用XML API构建一个XML树的例子:
using System;
using System.Xml.Linq; // 需要命名空间
class Program
{
static void Main()
{
XDocment employess1 = new XElement( // 创建XML文档
new XElement("Employees", // 创建根元素,第一参数是名称,第二参数是内容
new XElement("Name", "Bob Smith"), // 创建元素
new XElement("Name", "Sally Jones") // 创建元素
)
);
employees1.Save("EmloyessFile.xml"); // 保存到文件
// 使用 XDocument.Load 方法将保存的文档加载到新变量中
XDocument employess2 = XDocument.Load("EmployessFile.xml");
Console.WriteLine(employees2); // 显示文档
}
}
4.1.1.2 获取XML树的元素
使用
Name
可以返回元素的名称
查询XML的方法如下
Nodes
:作用于Xdocument
或XElement
,返回类型IEnumberable<object>
,作用是:返回当前节点的所有子节点(不管什么类型)Elements
:作用于Xdocument
或XElement
,返回类型IEnumberable<XElement>
,作用是:返回所有当前节点的XElement
子节点,或所有具有某个名字的子节点Element
:作用于Xdocument
或XElement
,返回类型XElement
,作用是:返回当前节点的第一个XElement
子节点,或具有某个名字的子节点Descendants
:作用于XElement
,返回类型IEnumberable<XElement>
,作用是: 返回所有XElement
子代节点 或所有具有某个名字的XElement
子代节点,不管它们处于当前节点下嵌套多少个层次DescendantsAndSelf
:作用于XElement
,返回类型IEnumberable<XElement>
,作用是: 与Descendants
一样,但是包括当前节点Ancestors
:作用于XElement
,返回类型IEnumberable<XElement>
,作用是: 返回所有上级XElement
节点或者所有具有某个名字的上级XElement
节点AncestorsAndSelf
:作用于XElement
,返回类型IEnumberable<XElement>
,作用是: 和Ancestors
一样,但是包括当前节点Parent
:作用于XElement
,返回类型XElement
,作用是:返回当前节点的父节点
Nodes
方法有些返回的节点可能是不同类型,比如XElement
或 XComment
等,此时可以使用类型作为参数的方法 OfType(type)
来指定返回某个类型的节点,如只获取 XComment
节点:
IEnumerable<XComment> comments = xd.Nodes().OfType<XComment>();
4.1.1.2 增加节点以及操作XML树
添加节点XML的方法如下:
Add
:作用于父节点,在当前节点的既有子节点后增加新的子节点AddFirst
:作用于父节点,在当前节点的既有子节点前增加新的子节点AddBeforeSelf
AddAfterSelf
Remove
:删除当前所选的节点及其内容RemoveNodes
:删除当前所选的XElement
及其内容SetElement
:作用父节点,设置节点的内容ReplaceContent
:替换节点的内容
4.1.1.2 使用XML特性
特性提供有关 XElement
节点的额外信息,在创建XML树时,可以使用XAttribute
构造函数来增加特性,如:
XElement rt = new XElement("Root",
new XAttribute("color", "red"), // 特性构造函数
new XElement("first")
)
// 获取特性
XAttribute color = rt.Attribute("color");
// 删除特性
rt.Attribute("color").Remove();
// 添加特性
rt.SetAttributeValue("size", null);
4.1.2 使用LINQ to XML的LINQ查询
var xyz = from e in rt.Elements()
where e.Name.ToString().length == 5
select e;