11.5 LINQ to XML

LINQ to XML是一种启用了LINQ的内存XML编程接口,使用它可以在.NET Framework编程语言中处理XML。它将XML文档置于内存中,这一点很像文档对象模型((Dcument Object Model, DOM)。可以通过它查询和修改XML文档,修改之后,可以将其另存为文件,也可以将其序列化,然后通过Internet发送。相比于DOM,它提供一种新的对象模型,这是一种更轻量的模型,使用也更方便。

除此之外,LINQ to XML还具有两个重要的优势:

1)LINQ to XML与语言集成查询((Lnguage Integrated Query, LINQ)的集成。这样,就可以对内存XML文档编写查询,以检索元素和属性的集合。

2)通过将查询结果用做XElement和XAttribute对象构造函数的参数,实现了一种功能强大的创建XML树的方法。这种方法称为“函数构造”,利用这种方法,开发人员可以方便地将XML树从一种形状转换为另一种形状。

总之,LINQ to XML提供了改进的XML编程接口。使用LINQ to XML,可以完成如下功能:

❑从文件或流加载XML;

❑将XML序列化为文件或流;

❑使用函数构造从头开始创建XML;

❑使用类似XPath的轴查询XML;

❑使用Add、Remove、ReplaceWith和SetValue等方法对内存XML树进行操作;

❑使用XSD验证XML树;

❑使用这些功能的组合,可将XML树从一种形状转换为另一种形状。

11.5.1 LINQ to XML类概述

System. Xml.Linq命名空间包含了所有的LINQ to XML的类,如表11-4所示。

figure_0422_0315

figure_0423_0316

11.5.2 创建XML

XDocument继承自XContainer类,使用它可以很简单地创建一个完整的XML文档。但值得注意的是,XDocument对象只能有一个子XElement节点。这反映了XML标准,即在XML文档中只能有一个根元素。如下面的示例所示:


private void WriteXML()

{

XDocument doc=new XDocument(

new XDeclaration("1.0","utf-8","yes"),

new XComment("创建时间@"+System.DateTime.Now),

new XElement("MyBook",

new XElement("book",

new XAttribute("id","1"),

new XAttribute("name","易学C#"),

new XElement("description","一本C#学习书籍"),

new XElement("price","45"),

new XElement("press","人民邮电出版社")

),

new XElement("book",

new XAttribute("id","2"),

new XAttribute("name","ASP.NET4程序设计"),

new XElement("description","一本ASP.NET学习书籍"),

new XElement("price","100"),

new XElement("press","机械工业出版社")

);

doc.Save(Server.MapPath("XMyBook.xml"));

}


创建的XMyBook.xml文档结果如图11-12所示。

figure_0423_0317

图 11-12 示例运行结果

上面的示例使用了函数构造的形式来创建了一个完整的XML文档。其实,在很多情况下,并不需要通过创建XDocument来创建XML文档,而是通常使用XElement根节点来创建XML树。除非具有创建文档的具体要求(例如,必须在顶级创建处理指令和注释,或者必须支持文档类型等),否则使用XElement作为根节点通常会更方便,直接使用XElement是一种比较简单的编程模型。如下面的示例所示:


XElement xdoc=new XElement("book",

new XAttribute("id","2"),

new XAttribute("name","ASP.NET4程序设计"),

new XElement("description","一本ASP.NET学习书籍"),

new XElement("price","100"),

new XElement("press","机械工业出版社")

);


11.5.3 读取与查询XML

在对XML文档的读取方面,XDocument简化了对XML内容的读取与导航。可以使用它的Load方法从文件、URI或者流中读取XML文档,也可以使用它的Parse方法从一个字符串中加载XML内容。其中,XDocument的Load方法原型如下:

❑public static XDocument Load(Stream stream):使用指定的流创建一个新的XDocument实例。

❑public static XDocument Load(string uri):从文件创建新XDocument。

❑public static XDocument Load(TextReader textReader):从TextReader创建新的XDocument。

❑public static XDocument Load(XmlReader reader):从XmlReader创建新XDocument。

❑public static XDocument Load(Stream stream, LoadOptions options):使用指定流创建新的XDocument实例,也可以选择保留空白,设置基URI和保留行信息。

❑public static XDocument Load(string uri, LoadOptions options):从文件创建新XDocument,还可以选择保留空白和行信息以及设置基URI。

❑public static XDocument Load(TextReader textReader, LoadOptions options):从TextReader创建新XDocument,还可以选择保留空白和行信息以及设置基URI。

❑public static XDocument Load(XmlReader reader, LoadOptions options):从XmlReader加载XElement,还可以选择设置基URI和保留行信息。

得到一个含有内容的XDocument之后,就可以使用XElement类来深入节点树。XElement类提供了许多方法,可以使用该类创建元素,快速查找,更改元素内容,添加、更改或删除子元素,向元素中添加属性或者以文本格式序列化元素内容。同时,还可以与System.Xml中的其他类,如XmlReader、XslCompiledTransform和XmlWriter类之间进行互操作。

下面的示例演示如何使用XDocument与XElement来读取XML文档。


private string ReadXML()

{

StringBuilder str=new StringBuilder();

XDocument doc=

XDocument.Load(Server.MapPath("XMyBook.xml"));

foreach(XElement element in doc.Element("MyBook").Elements())

{

str.Append("<br>"+element.Attribute("name").Value);

str.Append("<br>"+element.Element("description"));

str.Append("<br>"+element.Element("price"));

str.Append("<br>"+element.Element("press")+"<hr>");

}

return str.ToString();

}


在上面的代码中,通过XElement的Element方法取出所需要的元素并通过Elements方法对嵌入的XElement对象集合进行迭代,然后从节点元素中取出所需要的内容。示例运行结果如图11-13所示。

其实,LINQ to XML最强大之处在于它可以通过前面所讲的LINQ表达式对XML进行查询。即把元素集合放到LINQ表达式之后,就可以使用排序、过滤、分组、投影等操作取得所希望得到的数据。如下面的示例代码所示:


XDocument doc=XDocument.Load(Server.MapPath("XMyBook.xml"));

IEnumerable<XElement>result=

from el in doc.Descendants("book")

where(int)el.Attribute("id")==1

select el;


上面的示例代码查询结果为:


<book id="1"name="易学C#">

<description>一本C#学习书籍</description>

<price>45</price>

<press>人民邮电出版社</press>

</book>


可以将这些查询结果直接放到相关的数据控件上进行显示,如下面的示例将查询结果绑定到GridView控件上。


private void SearchNode()

{

XDocument doc=

XDocument.Load(Server.MapPath("XMyBook.xml"));

var result=

from el in doc.Descendants("book")

where(int)el.Attribute("id")<5

select new

{

书名=el.Attribute("name").Value,

价格=el.Element("price").Value,

出版社=el.Element("press").Value,

描述=el.Element("description").Value

};

GridView1.DataSource=result;

GridView1.DataBind();

}


示例运行结果如图11-14所示。

figure_0425_0318

图 11-13 示例运行结果

figure_0425_0319

图 11-14 示例运行结果

除此之外,还可以直接利用XElement进行查询。如下面的代码所示:


XElement root=XElement.Load(Server.MapPath("XMyBook.xml"));

IEnumerable<XElement>result=

from el in root.Elements("book")

where(int)el.Attribute("id")==1

select el;


11.5.4 添加XML元素、属性和节点

可以利用XElement类提供的添加方法来添加XML元素、节点与属性。其中,这些方法原型如下所示:

❑public void Add(Object content)与public void Add(params Object[]content)

将指定的内容添加为此XContainer的子级。该方法将引发Changed和Changing事件。

❑public void AddAfterSelf(Object content)与public void AddAfterSelf(params Object[]content)

紧跟在此节点之后添加指定的内容。该方法将引发Changed和Changing事件。

❑public void AddAnnotation(Object annotation)

将对象添加到此XObject的批注列表。

❑public void AddBeforeSelf(Object content)与public void AddBeforeSelf(params Object[]content)

紧邻此节点之前添加指定的内容。该方法将引发Changed和Changing事件。

❑public void AddFirst(Object content)与public void AddFirst(params Object[]content)

将指定的内容作为此文档或元素的第一个子级添加。该方法将引发Changed和Changing事件。添加示例如下面的代码所示:


private void AddNode()

{

string xml=Server.MapPath("XMyBook.xml");

XElement xe=XElement.Load(xml);

var result=from el in xe.Descendants("book")

select el;

foreach(var author in result)

{

author.Add(new XElement("author","马伟"));

}

xe.Save(xml);

}


在上面的代码中,首先通过LINQ表达式查询出所有book节点,然后循环在book节点下添加一个author节点。示例运行结果如图11-15所示。

figure_0426_0320

图 11-15 示例运行结果

11.5.5 修改XML元素、属性和节点

对于XML元素、属性和节点的修改,XElement类提供了如下方法:

❑public void ReplaceAll(Object content)

与public void ReplaceAll(params Object[]content)

使用指定的内容替换此元素的子节点和属性。该方法首先移除现有的内容和属性,然后添加指定的内容。它使用快照语义,也就是在使用新内容替换当前元素的内容之前,创建新内容的单独副本。这意味着可以查询当前元素的内容,并可以将查询结果用做指定的新内容。

❑public void ReplaceAttributes(Object content)与public void ReplaceAttributes(params Object[]content)

使用指定的内容替换此元素的属性。该方法首先移除现有的属性,然后添加指定的内容。

❑public void ReplaceNodes(Object content)与public void ReplaceNodes(params Object[]content)

使用指定的内容替换此文档或元素的子节点。此方法具有快照语义,它首先创建新内容的副本,然后移除此节点的所有子节点。最后,它将新内容作为子节点添加。这意味着可以使用对子节点自身执行的查询来替换子节点。同时,此方法将引发Changed和Changing事件。

❑public void ReplaceWith(Object content)与public void ReplaceWith(params Object[]content)

使用指定的内容替换此节点。该方法首先从节点父级中移除此节点,然后将指定的内容添加到此节点在父级中的位置。XContainer将其子节点存储为XNode对象的单链接列表。这意味着ReplaceWith方法必须遍历父容器下的直接子节点列表。因此,使用此方法可能会影响性能。同时,此方法将引发Changed和Changing事件。

❑public void SetAttributeValue(XName name, Object value)

设置属性的值、添加属性或移除属性。该方法旨在简化将名称/值对列表用做属性集时的维护,维护列表时,需要添加对、修改对或删除对。如果调用该方法将不存在的名称作为属性传递,则该方法会创建一个新属性;如果调用该方法来传递现有属性的名称,则该方法会将属性的值修改为指定的值;如果为value传递了null,则该方法会移除该属性。同时,该方法将引发Changed和Changing事件。

❑public void SetElementValue(XName name, Object value)

设置子元素的值、添加子元素或移除子元素。与SetAttributeValue方法相似,该方法旨在简化将名称/值对列表用做子元素集时的维护,维护列表时,需要添加对、修改对或删除对。如果调用该方法将不存在的名称作为子元素传递,则此方法会创建一个子元素;如果调用此方法来传递一个现有子元素的名称,则该方法会将此子元素的值更改为指定的值;如果为value传递了null,则该方法会移除子元素。

❑public void SetValue(Object value)

设置此元素的值。该方法将引发Changed和Changing事件。

下面的示例代码将选择id属性值等于1的book节点,并将该book节点的name属性值修改成“易学C#(马伟)”。


private void UpdateNode()

{

string xml=Server.MapPath("XMyBook.xml");

XElement xe=XElement.Load(xml);

var result=from el in xe.Descendants("book")

where el.Attribute("id").Value=="1"

select el;

var name=result.Single<XElement>();

name.SetAttributeValue("name","易学C#(马伟)");

xe.Save(xml);

}


11.5.6 删除XML元素、属性和节点

对于XML元素、属性和节点的删除,XElement类提供了如下方法:

❑public void Remove()

从节点父级中删除此节点。在LINQ to XML编程中,不应该在对一组节点中的节点进行查询的同时操作或修改这组节点,这意味着不应该循环访问一组节点并移除它们。相反,应该使用ToList<TSource>扩展方法将其具体化为List<T>,然后循环访问节点列表以移除它们。

或者,如果要删除一组节点,则建议使用Extensions.Remove方法。此方法将节点复制到列表,然后循环访问该列表以移除节点。该方法将引发Changed和Changing事件。

❑public void RemoveAll()

从该XElement中移除节点和属性。该方法将引发Changed和Changing事件。

❑public void RemoveAnnotations(T ype type)与public void RemoveAnnotations<T>()where T:class

从该XObject移除指定类型的批注。该方法将引发Changed和Changing事件。

❑public void RemoveAttributes()

移除该XElement的属性。该方法将引发Changed和Changing事件。

❑public void RemoveNodes()

从该文档或元素中移除子节点。如果在包含特性的元素上调用此方法,则该方法将不移除这些特性。若要移除元素的特性,请使用RemoveAttributes。该方法将引发Changed和Changing事件。

下面的示例将删除所有的author节点。


private void DeleteNode()

{

string xml=Server.MapPath("XMyBook.xml");

XElement xe=XElement.Load(xml);

var result=from el in xe.Descendants("book")select el;

foreach(variin result)

{

var authors=from author in i.Descendants("author")

select author;

authors.Remove();

}

xe.Save(xml);

}