C#图解教程 第十九章 LINQ

LINQ

什么是LINQ


在关系型数据库系统中,数据被组织放入规范化很好的表中,而且经过简单且强大的SQL语言来进行访问。由于数据在表中听从某些严格的规则,因此SQL能够和它们很好的配合使用。
然而,在程序中却与数据库相反,保存在类对象或结构中的数据差别很大。所以,没有通用的查询语言来从数据结构中获取数据。从对象获取数据的方法一直都是做为程序的一部分而设计的。然而使用LINQ能够很轻松地查询对象集合。
以下是LINQ的重要高级特性。css

  • LINQ(发音link)表明语言集成查询(Language Integrated Query)
  • LINQ是.NET框架的扩展,它容许咱们以使用SQL查询数据库的方式来查询数据集合
  • 使用LINQ,你能够从数据库、程序对象集合以及XML文档中查询数据

例:LINQ示例程序员

class Program
{
    static void Main()
    {
        int[] numbers={2,12,5,15};
        IEnumerable<int> lowNums=
                           from n in numbers
                           where n<10
                           select n;
        foreach(var x in lowNums)
        {
            Console.WriteLine(x);
        }
    }
}

LINQ提供程序


在以前的示例中,数据源只是int数组,它是程序在内存中的对象。然而,LINQ还能够和各类类型的数据源一块儿工做。然而,对于每种数据源类型,在其背后必定有根据该数据源类型实现LINQ查询的代码模块。这些代码模块叫作LINQ提供程序(provider)。
有关LINQ提供程序的要点以下数据库

  • 微软为一些常见的数据源类型提供了LINQ Provider
  • 第三方在不断提供针对各类数据源类型的LINQ Provider

本章中,咱们主要介绍LINQ并解释如何将其用于程序对象(LINQ to Object)和XML(LINQ to XML),其余细节和用法不作讨论。数组

匿名类型

在介绍LINQ查询特性的细节前,咱们先学习一个容许咱们建立无名类类型的特性。匿名类型(anonymous type)常常用于LINQ查询的结果中。
第6章介绍了对象初始化语句,它容许咱们在使用对象建立表达式时初始化新类实例的字段和属性。提醒一下,这种形式的对象建立表达式由三部分组成:new关键字、类名或构造函数以及对象初始化语句。对象初始化语句在一组大括号内包含了以逗号分隔的成员初始化列表。
建立匿名类型的变量使用相同的形式,可是没有类名和构造函数。以下的代码行演示了匿名类型的对象建立表达式:浏览器

没有类名
   ↓
new {FieldProp=InitExpr,FieldProp=InitExpr,...}
              ↑
        成员初始化语句

例:建立和使用匿名类型的示例。数据结构

class Program
{
    static void Main()
    {
     必须使用var
         ↓
        var student=new{Name="Mary Jones",Age=19,Major="History"};
        Console.WriteLine("{0},Age {1},Major: {2}",student.Name,student.Age,studeng.Major);
    }
}

须要了解的有关匿名类型的重要事项以下。框架

  • 匿名类型只能和局部变量配合使用,不能用于类成员
  • 因为匿名类型没有名字,咱们必须使用var关键字做为变量类型
  • 不能设置匿名类型对象的属性。编译器为匿名类型建立的属性是只读的

当编译器遇到匿名类型的对象初始化语句时,它建立一个有名字的新类类型。低于每一个成员初始化语句,它推断其类型并建立一个只读属性来访问它的值。属性和成员初始化语句具备相同名字。匿名类型被构造后,编译器建立了这个类型的对象。
除了对象初始化语句的赋值形式,匿名类型的对象初始化语句还有其余两种容许的形式:简单标识符和成员访问表达式。这两种形式叫作投影初始化语句(projection initializer)。下面的变量声明演示了3种形式。ide

var student=new{Age=19,Other.Name,Major};

例:使用3总初始化语句。注意,投影初始化语句必须定义在匿名类型声明以前。函数

class Other
{
    static public string Name="Mary Jones";
}
class Program
{
    static void Main()
    {
        string Major="History";
        var student=new{Age=19,Other.Name,Major};
        Console.WriteLine("{0},Age {1},Major: {2}",student.Name,student.Age,studeng.Major);
    }
}

若是编译器遇到了另外一个具备相同的参数名、相同的推断类型和相同顺序的匿名类型,它会重用这个类型并直接建立新的实例,不会建立新的匿名类型。工具

方法语法和查询语法


咱们在写LINQ查询时可使用两种形式的语法:方法语法和查询语法。

  • 方法语法(method syntax)使用标准的方法调用。这些方法是一组标准查询运算符的方法
  • 查询语法(query syntax)看上去和SQL语句类似
  • 在一个查询中能够组合两种形式

方法语法是命令式(imperative)的,它指明了查询方法调用的顺序。
查询语法是声明式(declarative)的,即查询描述的是你想返回的东西,但并么有指明如何执行这个查询。
编译器会将使用查询语法表示的查询翻译为方法调用的形式。这两种形式在运行时没有性能上的差别。
微软推荐使用查询语法,由于它更易读,能更清晰地代表查询意图,所以也更不容易出错。然而,有些运算符必须使用方法语法来书写。

例:方法语法和查询语法演示

class Program
{
    static void Main()
    {
        int[] numbers={2,5,28,31,17,16,42};
        var numsQuery=from n in numbers         //查询语法
                      where n<20
                      select n;
        var numsMethod=numbers.Where(x=>x<20);  //方法语法
        int numsCount=(from n in numbers        //两种形式组合
                       where n<20
                       select n).Count();
        foreach(var x in numsQuery)
        {
            Console.Write("{0}, ",x);
        }
        Console.WriteLine();
        foreach(var x in numsMethod)
        {
            Console.Write("{0}, ",x);
        }
        Console.WriteLine();
        Console.WriteLine(numsCount);
    }
}

查询变量


LINQ查询能够返回两种类型的结果–能够是一个枚举(可枚举的一组数据,不是枚举类型),它知足查询参数的项列表;也能够是一个叫作标量(scalar)的单一值,它是知足查询条件的结果的某种摘要形式。

例:查询变量示例

int[] numbers={2,5,28};
IEnumerable<int> lowNums=from n in numbers //返回枚举数
                         where n<20
                         select n;
int numsCount=(from n in numbers           //返回一个整数
               where n<20
               select n).Count();

理解查询变量的用法很重要。在执行前面的代码后,lowNums查询变量不会包含查询的结果。相反,编译器会建立可以执行这个查询的代码。
查询变量numCount包含的是真实的整数值,它只能经过真实运行查询后得到。
区别在于查询执行的时间,可总结以下:

  • 若是查询表达式返回枚举,查询直处处理枚举时才会执行
  • 若是枚举被处理屡次,查询就会执行屡次
  • 若是在进行遍历后,查询执行以前数据有改动,则查询会使用新的数据
  • 若是查询表达式返回标量,查询当即执行,而且把结果保存在查询变量中

查询表达式的结构


查询表达式由查询体后的from子句组成。有关查询表达式须要了解一些重要事项:

  • 子句必须按照必定顺序出现
  • from子句和select…group子句这两部分是必需的
  • LINQ查询表达式中,select子句在表达式最后。C#这么作的缘由之一是让Visual Studio智能感应能在咱们输入代码时给咱们更多选项
  • 能够有任意多的from…let…where子句

from子句

from子句指定了要做为数据源使用的数据集合。它还引入了迭代变量。有关from子句的要点以下:

  • 迭代变量逐个表示数据源的每一个元素
  • from子句的语法以下
    • Type是集合中元素的类型。这是可选的,由于编译器能够从集合来推断类型
    • Item是迭代变量的名字
    • Items是要查询的集合的名字。集合必须是可枚举的,见第18章
from Type Item in Items

下图演示了from子句的语法。类型说明符是可选的。能够有任意多个join子句。

尽管LINQ的from子句和foreach语句很是类似,但主要不一样点以下:

  • foreach语句命令式地指定了从第一个到最后一个按顺序地访问集合中的项。而from子句则声明式地规定集合中的每一个项都要被访问,但并无假定以什么样的顺序
  • foreach语句在遇到代码时就执行其主体,而from子句什么也不执行。只有在程序的控制流遇到访问查询变量的语句时,才会执行查询

join子句

LINQ中的join子句和SQL中的JOIN(联结)子句类似。不一样的是,咱们如今不但能够在数据库的表上进行联结,还能够在集合对象上进行该操做。若是你不熟悉联结,那么下面的内容会帮你理清思路。
须要先了解有关联结的语法:

  • 使用联结来结合两个多多个集合中的数据
  • 联结操做接受两个集合而后建立一个临时的对象集合,每一个对象包含原始集合对象中的全部字段

联结语法以下

关键字        关键字           关键字      关键字
 ↓              ↓              ↓           ↓
join Identifier in Collection2 on Field1 equals Field1
                       ↑
              指定另外的集合和ID引用它
var query=from s in students
          join c in studentsInCourses on s.StID equals c.StID

什么是联结

LINQ中的join接受两个集合而后建立一个新的集合,每一个元素包含两个原始集合中的原始成员。

例:联结示例

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()
    {
        var query=from s in students
                  join c in studentsInCourses on s.StID equals c.STID
                  where c.CourseName=="History"
                  select.LastName;
        foreach(var q in query)
        {
            Console.WriteLine("Student taking History:{0}",q);
        }
    }
}

查询主体中的from…let…where片断

可选的from…let…where部分是查询主体的第一部分,能够由任意数量的3个子句来组合–from子句、let子句和where子句。

from子句

查询表达式从必需的from子句开始,后面跟查询主体。主体自己能够从任何数量的其余from子句开始,每一个from子句都指定了一个额外的源数据集合并引入了要在以后运算的迭代变量,全部from子句的语法和含义都同样。

例:from子句示例

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     from b in groupB
                     where a>4&&b<=8
                     select new{a,b,sum=a+b};//匿名类型对象
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

let子句

let子句接受一个表达式的运算而且把它赋值给一个须要在其余运算中使用的标识符。let子句的语法以下:

let Identifier=Expression

例:let子句示例

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=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 a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

where子句

where子句根据以后的运算来筛选指定项。
只要是在from…let…where部分中,查询表达式能够有多个where。

例:where子句示例

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=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 a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

orderby子句

orderby子句根据表达式按顺序返回结果项。
orderby子句语法以下图。可选的ascending和descending关键字设置了排序方向。表达式一般是项的一个字段。该字段不必定非得是数值字段,也能够是字符串这样的可排序类型。

  • orderby子句默认是升序
  • 能够有任意多子句,它们必须用逗号分隔

例:按照学生年龄排序

class Program
{
    static void Main()
    {
        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("{0},{1}: {2} - {3}",s.LName,s.FName,s.Age,s.Major);
        }
    }
}

select…group子句

select…group子句的功能以下所示。

  • select子句指定所选对象的哪部分应该被select。它能够指定下面的任意一项
    • 整个数据项
    • 数据项的一个字段
    • 数据项的几个字段组成的新对象(或相似其余值)
  • group…by子句是可选的,用来指定选择的项如何分组

例:select整个数据项

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        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 s in query)
        {
            Console.WriteLine("{0},{1}: {2} , {3}",s.LName,s.FName,s.Age,s.Major);
        }
    }
}

var query=from s in students
          select s.LName;
foreach(var s in query)
{
    Console.WriteLine(s);
}

查询中的匿名类型

查询结果能够由原始集合的项、项的某些字段或匿名类型组成。
例:使用select建立一个匿名类型

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        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 s in query)
        {
            Console.WriteLine("{0} {1} -- {2} , {3}",s.FName,s.LName,s.Major);
        }
    }
}

group子句

group子句把select的对象根据一些标准进行分组。例如,以前示例的学士数组,程序能够根据它们的主修课程进行分组。

  • 若是项包含在查询的结果中,它们就能够根据某个字段的值进行分组。做为分组依据的属性叫作(key)
  • group子句返回的不是原始数据源中项的枚举,而是返回能够枚举已经造成的项的分组的可枚举类型
  • 分组自己是可枚举类型,它们能够枚举实际的项

例:根据学士的主修课程进行分组

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        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
                  group s by s.Major;
        foreach(var s in query)
        {
            Console.WriteLine("{0}",s.Key);
            foreach(var t in s)
            {
                Console.WriteLine("      {0},{1}",t.LName,t.FName);
            }
        }
    }
}

查询延续:into子句

查询延续子句能够接受查询的一部分结果并赋予一个名字,从而能够在查询的另外一部分中使用。

例:链接groupA和groupB并命名为groupAandB

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     join b in groupB on a equals b
                     into groupAandB
                     from c in groupAandB
                     select c;
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

输出:6

标准查询运算符


标准查询运算符由一系列API方法组成,它能让咱们查询任何.NET数组或集合。
标准查询运算符的重要特性以下:

  • 被查询的集合对象叫作序列,它必须实现IEnumerable<T>接口,T是类型
  • 标准查询运算符使用方法语法
  • 一些运算符返回IEnumerable对象(或其余序列),而其余的一些运算符返回标量。返回标量的运算符当即执行,并返回一个值
  • 不少操做都以一个谓词做为参数。谓词是一个方法,它以对象为参数,根据对象是否知足某条件而返回true或false

例:Sum和Count运算符的使用

class Program
{
    static int[] numbers=new int[]{2,4,6};
    static void Main()
    {
        int total=numbers.Sum();
        int howMany=number.Count();
        Console.WriteLine("Total: {0},Count: {1}",total,howMany);
    }
}

标准查询运算符可用来操做一个或多个序列。序列指实现了IEnumerable<>接口的类型,包括List<>、Dictionary<>、Stack<>、Array等。

标准查询运算符的签名

System.Linq.Enumerable类声明了标准查询运算符方法。这些方法不只是一些方法,它们是扩展了IEnumerable<T>泛型类的扩展方法。
第7章和第17章介绍类扩展方法,在本节是学习如何使用扩展方法的好机会。
简单回顾一下。扩展方法是公共的静态方法,尽管定义在一个类中,但目的是为另外一个类(第一个形参)增长功能。该参数前必须有关键字this。

例:3个标准查询运算符的签名

始终是public static       名字和泛型参数    第一个参数
     ↓                         ↓             ↓
public static      int       Count<T>(this IEnumerable<T> source);
public static       T        First<T>(this IEnumerable<T> source);
public static IEnumerable<T> Where<T>(this IEnumerable<T> source,...);

例:直接调用扩展方法和将其做为扩展进行调用的不一样

using System.Linq;
...
static void Main()
{
    int[] intArray=new int[]{3,4,5,6,7,9};
    //方法语法
    var count1=Enumerable.Count(intArray);
    var firstNum1=Enumerable.First(intArray)
    //扩展语法
    var count2=intArray.Count();
    var firstNum2=intArrya.First();
    Console.WriteLine("Count: {0},FirstNumber: {1}",count1,firstNum1);
    Console.WriteLine("Count: {0},FirstNumber: {1}",count2,firstNum2);
}

查询表达式和标准查询运算符

查询表达式和方法语法能够组合。编译器把每一个查询表达式翻译成标准查询运算符的形式。

class Program
{
    static void Main()
    {
        var numbers=new int[]{2,6,4,8,10};
        int howMany(from n in numbers
                    where n<7
                    select n).Count();
        Console.WriteLine("Count: {0}",howMany);
    }
}

将委托做为参数

前面咱们看到,每一个运算符的第一个参数是IEnumerable<T>对象的引用,以后的参数能够是任何类型。不少运算符接受泛型委托做为参数(第17章)。泛型委托用于给运算符提供用户自定义代码。

为了解释这一点,咱们首先从演示Count运算符的几种使用方式的示例开始。
Count运算符被重载且有两种形式,第一种以前示例中用过,它有一个参数,返回集合中元素的个数。

public static int Count<T>(this IEnumerable<T> source);

然而,假设咱们但愿看看数组中奇数元素的总数。Count方法必须可以检测整数是否为奇数。
咱们须要使用Count方法的第二种形式。以下所示,它有一个泛型委托做为参数。调用时,咱们提供一个接受单个T类型的输入参数并返回布尔值的委托对象。委托代码的返回值必须指定元素是否包含在总数中。

public static int Count<T>(this IEnumerable<T> source,Func<T,bool> predicate);
class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        var countOdd=intArray.Count(n=>n%2!=0);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

LINQ预约义的委托类型

和前面示例中的Count运算符差很少,不少LINQ运算符须要咱们提供代码来指示运算符如何执行它的操做。咱们经过委托对象做为参数来实现。
LINQ定义了两套泛型委托类型与标准查询运算符一块儿使用,即Func委托和Action委托,各有17个成员。

  • 咱们用做实参的委托对象必须是这些类型或这些形式之一
  • TR表明返回值,而且老是在类型参数列表中的最后一个
public delegate TR Func<in T1,in T2,out TR>(T1 a1,T2 a2);
                 ↑               ↑              ↑
              返回类型         类型参数        方法参数

注意返回类型参数有out关键字,使之能够协变,便可以接受声明的类型或从这个类型派生的类型。输入参数有in关键字,使之能够逆变,即你能够接受声明的类型或从这个类型派生的类型。

使用委托参数的示例

class Program
{
    static bool IsOdd(int x)
    {
        return x%2!=0;
    }
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        Func<int,bool>myDel=new Func<int,bool>(IsOdd);
        var countOdd=intArray.Count(myDel);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

使用Lamba表达式参数的示例

以前示例使用独立的方法和委托来把代码附加到运算符上。这须要声明方法和委托对象,而后把委托对象传递给运算符。若是下面的条件任意一个成立,这种方法是不错的方案:

  • 方法还必须在程序的其余地方调用,而不只仅是用来初始化委托对象的地方
  • 函数体中的代码语句多于一条

若是这两个条件都不成立,咱们可能但愿使用更简洁和更局部化的方法来给运算符提供代码,那就是Lambda表达式。

例:用Lambda表达式修改以前的示例

class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        var countOdd=intArray.Count(n=>n%2!=0);//Lambda表达式
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

咱们也能够用匿名方法来替代Lambda表达式。然而,这种方式比较累赘,并且Lambda表达式在语义上与匿名方法彻底等价,且更简洁,所以没有理由再去使用匿名方法了。

class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        Func<int,bool> myDel=delegate(int x)   //匿名方法
                             {
                                 return x%2!=0;
                             };
        var countOdd=intArray.Count(myDel);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

LINQ to XML


可扩展标记语言(XML)是存储和交换数据的重要方法。LINQ为语言增长了一些特性,使得XML用起来比XPath和XSLT容易得多。

  • 可使用单一语句自顶向下建立XML树
  • 能够不是用包含树的XML文档在内存中建立并操做XML
  • 能够不是用Text子节点来建立和操做字符串节点
  • 搜索XML树时,不须要遍历它。只须要查询树并让它返回想要的结果

尽管本书不会完整介绍XML,但在接受LINQ to XML前,我会先简单介绍一下XML。

标记语言

标记语言(markup language)是文档中的一组标签,它提供有关文档的信息并组织其内容。即标记标签不是文档的数据–它们包含关于数据的数据。有关数据的数据称为元数据
标记语言是被定义的一组标签,旨在传递有关文档内容的特定类型的元数据。例如,HTML是众所周知的标记语言。标签中的元数据包含了Web页面如何在浏览器中呈现已经如何使用超连接在页面中导航的信息。
XML中仅有少许预约义标签,其余由程序员定义,来表示特定文档类型须要的任何元数据。只要数据的读者和编写者都知道标签的含义,标签就能够包含任何设计者但愿的有用信息。

XML基础

XML文档中的数据包含了一个XML树,它主要由嵌套元素组成。
元素是XML树的基本要素。每一个元素都有名字且包含数据,一些元素还包含其余被嵌套元素。元素由开始和关闭标签进行划分。任何元素包含的数据都必须介于开始和关闭标签之间。

  • 开始标签 <ElementName>
  • 结束标签 </ElementName>
  • 无内容的单个标签 <ElementName/>

例:

   开始标签        内容        结束标签
      ↓            ↓            ↓
<EmployeeName>Sally Jones</EmployeeName>
<PhoneNumber/>  ←没有内容的元素

有关XML的重要事项:

  • XML文档必须有一个根元素包含全部其余元素
  • XML标签必须合理嵌套
  • 与HTML标签不一样,XML标签是区分大小写的
  • XML特性是名字/值的配对,它包含了元素的额外元数据。特性的值部分必须包含在引号内,单引号双引号皆可
  • XML文档中的空格是有效的。这与把空格做为当个空格输出的HTML不一样
<Employees>
    <Employee>
        <Name>Bob Smith</Name>
        <PhoneNumber>408-555-1000</PhoneNumber>
        <CellPhone/>
    </Employee>
    <Employee>
        <Name>Sally Jones</Name>
        <PhoneNumber>415-555-2000</PhoneNumber>
        <PhoneNumber>415-555-2001</PhoneNumber>
    </Employee>
</Employees>

XML类

LINQ to XML能够以两种方式和XML配合使用。第一种是做为简化的XML操做API,第二种是使用本章前面看到的LINQ查询工具。
我会先介绍API方式。
LINQ to XML API由不少表示XML树组件的类组成。咱们主要使用3个类,XElement、XAttribute和XDocument。
下图演示了用于构造XML树的类以及它们如何被嵌套。

  • 可做为XDocument节点的直接子节点
    • 大多数状况下,下面每一个节点类型各有一个:XDeclaration节点、XDocumentType节点以及XElement节点
    • 任何数量的XProcessingInstruction节点
  • 若是在XDocument中有最高级别的XElement节点,那么它就是XML树中其余元素的根
  • 根元素能够包含任意数量的XElement、XComment或XProcessingInstruction节点,在任何级别上嵌套

除了XAttribute类,大多数用于建立XML树的类都从一个叫作XNode的类继承,通常在书中也叫作“XNodes”。

建立、保存、加载和显式XML文档

例:建立一个包含Employees节点的XML树

using System;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument employees1=
            new XDocument(                    //建立XML文档
                new XElement("Employees",
                    new XElement("Name","Bob Smith"),
                    new XElement("Name","Sally Jones")
                )
            );
        employees1.Save("EmployeesFile.xml"); //保存到文件
        XDocument employees2=XDocument.Load("EmployeesFile.xml");
                                       ↑
                                   静态方法
        Console.WriteLine(employees2);         //显式文件
    }
}

建立XML树

例:建立XML树

using System;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument employeeDoc=
            new XDocument(                    //建立XML文档
                new XElement("Employees",
                    new XElement("Employee",
                        new XElement("Name","Bob Smith"),
                        new XElement("PhoneNumber","408-555-1000")),
                    new XElement("Employee",
                        new XElement("Name","Sally Jones"),
                        new XElement("PhoneNumber","415-555-2000"),
                        new XElement("PhoneNumber","415-555-2001"))
                )
            );
        Console.WriteLine(employeeDoc);
    }
}

使用XML树的值

当咱们遍历XML树来获取或修改值时才体现了XML的强大。下表给出了用于获取数据的主要方法。

关于上表,须要注意的一些事项以下:

  • Nodes Nodes方法返回IEnumerable<object>类型的对象,由于返回的节点多是不一样的类型,好比XElement、XComment等。咱们可使用以类型做为参数的方法OfType(type)来指定返回某类型的节点。例如,以下代码只能获取XComment节点
    • IEnumerable<XComment> comments=xd.Nodes().OfType<XComment>()
  • Elements 因为获取XElement是很是广泛的需求,就出现了`Nodes.OfType(XElement)()``表达式的简短形式–Elements方法
    • 无参数的Elements方法返回全部子XElements
    • 单个name参数的Elements方法返回具备这个名字的子XElements。例如,以下代码返回具备名字PhoneNumber的子XElement节点
    • IEnumerable<XElement> empPhones=emp.Elements("PhoneNumber");
  • Element 这个方法只获取当前节点的第一个子XElement。若是无参数,获取第一个XElement节点,若是带一个参数,获取第一个具备此名字的子XElement
  • Descendants和Ancestors 这些方法和Elements以及Parent方法差很少,只不过它们不返回直接的子元素和父元素,而是忽略嵌套级别,包括全部之下或者之上的节点
using System;
using System.Collections.Generic;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument employeeDoc=
            new XDocument(                    //建立XML文档
                new XElement("Employees",
                    new XElement("Employee",
                        new XElement("Name","Bob Smith"),
                        new XElement("PhoneNumber","408-555-1000")),
                    new XElement("Employee",
                        new XElement("Name","Sally Jones"),
                        new XElement("PhoneNumber","415-555-2000"),
                        new XElement("PhoneNumber","415-555-2001"))
                )
            );
        //获取第一个名为“Employees”的子XElement 
        XElement root=employeeDoc.Element("Employees");
        IEnumerable<XElement> employees=root.Elements();
        foreach(XElement emp in employees)
        {
            XElement empNameNode=emp.Element("Name");
            Console.WriteLine(empNameNode.Value);
            IEnumerable<XElement> empPhones=emp.Elements("PhoneNumber");
            foreach(XElement phone in empPhones)
            {
                Console.WriteLine(phone.Value);
            }
        }
    }
}

增长节点以及操做XML

咱们可使用Add方法位现有元素增长子元素。

using System;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                new XElement("first")
            )
        );
        Console.WriteLine("Original tree");
        Console.WriteLine(xd);
        Console.WriteLine();
        XElement rt=xd.Element("root");
        rt.Add(new XElement("second"));
        rt.Add(new XElement("third"),
               new XComment("Important Comment"),
               new XElement("fourth"));
        Console.WriteLine("Modified tree");
        Console.WriteLine(xd);
    }
}

下表列出了最重要的一些操做XML的方法。

使用XML特性

特性提供了有关XElement节点的额外信息,它放在XML元素的开始标签中。
咱们以函数方法构造XML树时,只需在XElement的构造函数中包含XAttribute构造函数来增长特性。XAttribute构造函数有两种形式一种是接受name和value,另外一种是接受现有XAttribute的引用。

例:为root增长两个特性。

XDocument xd=new XDocument(
    new XElement("root",
            new XAttribute("color","red"),
            new XAttribute("size","large"),
        new XElement("first"),
        new XElement("second")
    )
);

例:获取特性

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        Console.WriteLine(xd);
        Console.WriteLine();
        XElement rt=xd.Element("root");
        XAttribute color=rt.Attribute("color");
        XAttribute size=rt.Attribute("size");
        Console.WriteLine("color is {0}",color.Value);
        Console.WriteLine("size is {0}",size.Value);
    }
}

例:移除特性

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        XElement rt=xd.Element("root");
        rt.Attribute("color").Remove();//移除color特性
        rt.SetAttributeValue("size",null);//移除size特性
        Console.WriteLine(xd);
    }
}

例:增长或改变特性的值

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        XElement rt=xd.Element("root");
        rt.SetAttributeValue("size","midium");  //改变特性值
        rt.SetAttributeValue("width","narrow"); //添加特性
        Console.WriteLine(xd);
    }
}

节点的其余类型

XComment

XML注释由<!--和-->记号间的文本组成。记号间的文本会被XML解析器忽略。咱们可使用XComment类向一个XML文档插入文本。以下面代码所示: 

 new XComment("This is a comment")  

这段代码产生以下XML文档:
 <!--This is a comment--> 

XDeclaration

XML文档从包含XML使用的版本号、字符编码类型以及文档是否依赖外部引用的一行开始。这是有关XML的信息,所以它实际上是有关数据的元数据。这叫作XML声明,可使用XDeclaration类来插入,以下代码给出了XDeclaration的示例:
 new XDeclaration("1.0","uff-8","yes")  
这段代码产生以下XML文档:
 <?xml version="1.0" encoding="utf-8 " standalone="yes"?> 

XProecssingInstruction

XML处理指令用于提供XML文档如何被使用和翻译的额外数据,最多见的就是把处理指令用于关联XML文档和一个样式表。
咱们可使用XProecssingInstruction构造函数来包含处理指令。它接受两个字符串参数:目标和数据串。如歌处理指令接受多个数据参数,这些参数必须包含在XProecssingInstruction构造函数的第二个字符串参数中,以下的构造函数代码所示。

 new XProecssingInstruction("xml-stylesheet",@"href=""stories"",type=""text/css""") 

这段代码产生以下XML文档:
 <?xml-stylesheet href="stories.css" type="text/css"?> 

例:

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XDeclaration("1.0","uff-8","yes"),
            new XComment("This is a comment"),
            new XProecssingInstruction("xml-stylesheet",@"href=""stories"",type=""text/css"""),
            new XElement("root",
                new XElement("first"),
                new XElement("second")
            )
        );
    }
}

代码会产生以下的输出文件。然而若是使用WriteLine(xd),声明语句不会被打印出来。

使用LINQ to XML的LINQ 查询

如今,咱们能够把LINQ XML API和LINQ查询表达式组合为简单而强大的XML树搜索。

例:建立示例用XML树

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("MyElements",
                new XElement("first",
                    new XAttribute("color","red"),
                    new XAttribute("size","small")),
                new XElement("second",
                    new XAttribute("color","red"),
                    new XAttribute("size","midium")),
                new XElement("third",
                    new XAttribute("color","blue"),
                    new XAttribute("size","large"))
            )
        );
        Console.WriteLine(xd);
        xd.Save("SimpleSample.xml");
    }
}

例:LINQ to XML

class Program
{
    static void Main()
    {
        XDocument xd=XDocument.Load("SimpleSample.xml");
        XElement rt=xd.Element("MyElements");
        var xyz=from e in rt.Elements()
                where e.Name.ToString().Length==5
                select e;
        foreach(XElement x in xyz)
        {
            Console.WriteLine(x.Name.ToString());
        }
        Console.WriteLine();
        foreach(XElement x in xyz)
        {
            Console.WriteLine("Name: {0}, color: {1}, size: {2}",
                              x.Name,
                              x.Attribute("color").Value,
                              x.Attribute("size").Value);
        }
    }
}

例:获取XML树的全部顶层元素,并为每一个元素建立了匿名类型对象

using System;
using System.Linq;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument xd=XDocument.Load("SimpleSample.xml");
        XElement rt=xd.Element("MyElements");
        var xyz=from e in rt.Elements()
                select new{e.Name,color=e.Attribute("color")};
                //建立匿名类型
        foreach(var x in xyz)
        {
            Console.WriteLine(x);
        }
        Console.WriteLine();
        foreach(var x in xyz)
        {
            Console.WriteLine("{0,-6},    color:{1,-7}",x.Name,x.color.Value);
        }
    }
}

从这些示例咱们能够看到,能够轻易地组合XML API和LIQN查询工具来产生强大的XML查询能力。

相关文章
相关标签/搜索