无模式 C#-XML 数据绑定与 VTD-XML






4.31/5 (20投票s)
敏捷、高效的无模式 XML 数据绑定。
摘要
本文介绍了一种全新的 C#-XML 数据绑定技术,它完全基于 VTD-XML 和 XPath。这种新方法与传统的 C# XML 数据绑定工具不同之处在于,它不强制要求模式,利用了 XML 固有的松散编码,并且更加灵活高效。
基于模式的 XML 数据绑定的局限性
XML 数据绑定 API 是一类 XML 处理工具,它能自动将 XML 数据映射到自定义的、强类型对象或数据结构中,从而将 XML 开发人员从繁琐的 DOM 或 SAX 解析中解脱出来。为了使传统的、静态的 XML 数据绑定工具(例如 .NET 中的各种绑定框架、Liquid Technology、Castor.NET)正常工作,开发人员需要假设文档的 XML 模式(或其等效物)是可用的。在第一步中,大多数 XML 数据绑定器会将 XML 模式编译成一组类文件,然后调用应用程序会包含这些类文件来执行相应的“解组”。
然而,处理 XML 文档的开发人员并不总是手头有其模式。而且,即使 XML 模式可用,对其进行的微小更改(通常由于不断变化的业务需求)也需要重新生成类文件。此外,XML 数据绑定在处理浅层、形状规则的 XML 数据时最有效。当 XML 文档的底层结构复杂时,仍然需要手动导航类型化的层次结构树,这仍然可能需要大量的编码。
XML 数据绑定的大多数局限性都可归因于其对 XML 模式的严格依赖。与许多二进制数据格式不同,XML 主要旨在作为一种无模式的数据格式,其灵活性足以表示几乎任何类型的信息。对于高级用法,XML 也是松散编码的:应用程序可以使用它们需要的部分 XML 文档,并跳过它们不关心的部分。由于 XML 的松散编码,Web Services 和 SOA 应用程序在面对更改时不太可能中断。
有趣的是,XML 的无模式性质对 XML 数据绑定具有微妙的性能影响。在许多情况下,只需要 XML 文档中的一小部分数据子集(而不是整个数据集)来驱动应用程序逻辑。然而,传统方法不加选择地将整个数据集转换为对象,从而产生不必要的内存和处理开销。
使用 VTD-XML 和 XPath 绑定 XML
动机
虽然 XML 数据绑定的概念自 XML 早期以来基本保持不变,但 XML 处理领域已经发生了显著变化。值得注意的是,XML 数据绑定 API 的主要目的是将 XML 映射到对象,而 XML 模式的存在仅仅有助于减轻 XML 处理的编码工作。换句话说,如果将 XML 映射到对象变得足够简单,你不仅不需要模式,而且还有强烈的动机去避免模式,因为它会带来所有这些问题。
正如你可能已经从本节标题中猜到的那样,VTD-XML 和 XPath 的结合非常适合无模式数据绑定。
为什么选择 XPath 和 VTD-XML?
XPath 之所以适用于我们的新方法,主要有三个原因。首先,如果编写得当,你的数据绑定代码只需要 XML 树结构的近似知识(例如,拓扑、标签名称等),这些知识可以通过查看 XML 数据来确定。XML 模式不再是强制性的。此外,XPath 允许你的应用程序绑定相关数据项并过滤掉所有其他内容,从而避免了不必要的对象创建。最后,基于 XPath 的代码易于理解、编写和调试,并且通常非常易于维护。
但 XPath 仍然需要 XML 的解析树才能工作。VTD-XML 优于 DOM 和 SAX,提供了大量与数据绑定相关的功能和优势,其中一些如下所示。
- 高性能、低内存使用和易用性:在选择 XML 处理 API 时,你需要将这三个因素(性能、内存消耗和可用性)作为一个整体来考虑。SAX 解析器声称无论文档大小如何,内存使用量都是恒定的。然而,由于它不导出 XML 的层次结构,因此难以使用,甚至不支持 XPath。DOM 解析器构建内存中的树,更易于使用,并支持 XPath。但是,它也非常慢,并产生过高的内存使用量。作为基于最先进的“非提取”标记化技术的下一代 XML 处理 API,VTD-XML 将 XML 处理提升到一个以前认为不可能达到的全新水平。与 DOM 一样,VTD-XML 构建内存中的树,并且能够随机访问。但是,它仅消耗 DOM 内存的 1/5。在性能方面,VTD-XML 不仅比 DOM 快 5 到 12 倍,而且通常比具有空内容处理程序(最大性能)的 SAX 快两倍。基准比较可以在此处找到。
- 非阻塞 XPath 实现:VTD-XML 还率先实现了增量、非阻塞的 XPath 评估。与传统 XPath 引擎一次性返回整个评估节点集不同,VTD-XML 基于 AutoPilot 的引擎在节点评估完成后立即返回合格节点,从而实现无与伦比的性能和灵活性。欲了解更多信息,请访问此链接。
- 原生 XML 索引:VTD-XML 也是一个原生 XML 索引器,它允许你的应用程序无需解析即可运行 XPath 查询。
- 增量更新:VTD-XML 是唯一允许你更新 XML 内容而无需触及 XML 文档无关部分的 XML 处理 API(使用 VTD-XML 提高 XPath 效率),从另一个角度提高了性能和效率。
进程
我们新的无模式 XML 数据绑定过程大致包括以下步骤。
- 观察 XML 文档并写下与感兴趣的数据字段对应的 XPath 表达式。
- 定义类文件和成员变量,这些数据字段将映射到其中。
- 重构步骤 (1) 中的 XPath 表达式以降低导航成本。
- 编写基于 XPath 的数据绑定例程,执行对象映射。XPath 1.0 允许 XPath 评估为四种数据类型:字符串、布尔值、双精度浮点数和节点集。字符串类型可以进一步转换为其他数据类型。
- 如果 XML 处理需要读写,请使用 VTD-XML 的
XMLModifier
更新 XML 的内容。请注意,你可能需要记录更多信息以利用 VTD-XML 的增量更新功能。
一个示例项目
让我向您展示如何将这种新的 XML 绑定付诸实践。这个用 C# 编写的项目遵循上述步骤来创建简单的数据绑定路由。该项目的第一部分是创建只读对象,这些对象不受应用程序逻辑的修改。第二部分提取更多信息,允许 XML 文档进行增量更新。最后一部分还添加了 VTD+XML 索引。我在本例中使用的 XML 文档如下所示
<CATALOG>
<CD>
<TITLE>Empire Burlesque</TITLE>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR>
</CD>
<CD>
<TITLE>Still got the blues</TITLE>
<ARTIST>Gary More</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>Virgin redords</COMPANY>
<PRICE>10.20</PRICE>
<YEAR>1990</YEAR>
</CD>
<CD>
<TITLE>Hide your heart</TITLE>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR>
</CD>
<CD>
<TITLE>Greatest Hits</TITLE>
<ARTIST>Dolly Parton</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>RCA</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1982</YEAR>
</CD>
</CATALOG>
只读
假设应用程序逻辑由 1982 年到 1990 年(不含)之间的 CD 记录对象驱动,对应 XPath "/CATALOG/CD[ YEAR < 1990 and YEAR>1982]
"。类定义(如下所示)包含四个字段,分别对应 CD 的标题、艺术家、价格和年份。
using System;
using System.Collections.Generic;
using System.Text;
namespace bind1
{
public class CDRecord
{
public String title;
public String artist;
public double price;
public int year;
}
}
对象成员与其对应的 XPath 表达式之间的映射如下所示
- "
title
" 字段对应于 "/CATALOG/CD[ YEAR < 1990 and YEAR>1982]/TITLE
" - "
artist
" 字段对应于 "/CATALOG/CD[ YEAR < 1990 and YEAR>1982]/ARTIST
" - "
price
" 字段对应于 "/CATALOG/CD[ YEAR < 1990 and YEAR>1982]/PRICE
" - "
year
" 字段对应于 "/CATALOG/CD[ YEAR < 1990 and YEAR>1982]/YEAR
"
出于效率考虑,XPath 表达式可以进一步重构如下
- 使用 "
/CATALOG/CD[ YEAR < 1990 and YEAR>1982]
" 导航到 "CD
" 节点。 - 使用 "
TITLE
" 提取 "title
" 字段(字符串)。 - 使用 "
ARTIST
" 提取 "artist
" 字段(字符串)。 - 使用 "
PRICE
" 提取 "price
" 字段(双精度浮点数)。 - 使用 "
YEAR
" 提取 "year
" 字段(整数)。
代码(如下所示)有两个方法:“bind()
”接受 XML 文件名作为输入,通过插入上述 XPath 表达式执行数据绑定,并返回包含对象引用的数组列表。“main()
”方法调用绑定例程并打印出对象的内容。
using System;
using System.Collections;
using System.Text;
using com.ximpleware;
namespace bind1
{
class XMLBinder
{
ArrayList bind(String fileName)
{
ArrayList al = new ArrayList();
VTDGen vg = new VTDGen();
AutoPilot ap0 = new AutoPilot();
AutoPilot ap1 = new AutoPilot();
AutoPilot ap2 = new AutoPilot();
AutoPilot ap3 = new AutoPilot();
AutoPilot ap4 = new AutoPilot();
ap0.selectXPath("/CATALOG/CD[YEAR > 1982 and YEAR <1990]");
ap1.selectXPath("TITLE");
ap2.selectXPath("ARTIST");
ap3.selectXPath("PRICE");
ap4.selectXPath("YEAR");
if (vg.parseFile(fileName, false))
{
VTDNav vn = vg.getNav();
ap0.bind(vn);
ap1.bind(vn);
ap2.bind(vn);
ap3.bind(vn);
ap4.bind(vn);
while (ap0.evalXPath() != -1)
{
CDRecord cdr = new CDRecord();
cdr.title = ap1.evalXPathToString();
cdr.artist = ap2.evalXPathToString();
cdr.price = ap3.evalXPathToNumber();
// evalXPathToNumber evaluates to a double
cdr.year = (int)ap4.evalXPathToNumber();
al.Add(cdr);
}
ap0.resetXPath();
}
return al;
}
public static void Main(String[] args)
{
XMLBinder xb = new XMLBinder();
ArrayList al = xb.bind("d:/codeProject/app1/bind1/bind1/catalog.xml");
IEnumerator ie = al.GetEnumerator();
//Iterator it = al.Iterator();
while (ie.MoveNext())
{
CDRecord cdr = (CDRecord)ie.Current;
Console.WriteLine("===================");
Console.WriteLine("TITLE: ==> " + cdr.title);
Console.WriteLine("ARTIST: ==> " + cdr.artist);
Console.WriteLine("PRICE: ==> " + cdr.price);
Console.WriteLine("YEAR: ==> " + cdr.year);
}
}
}
}
读写
项目的这一部分处理相同的 XML 文档,但应用程序逻辑将每个 CD 的“price
”降低 1。为此,需要进行两个更改:CD 记录类文件现在有一个新字段(名为 priceIndex
),其中包含“PRICE
”文本节点的 VTD 索引。
using System;
using System.Collections.Generic;
using System.Text;
namespace bind1
{
public class CDRecord
{
public String title;
public String artist;
public double price;
public int year;
}
}
新的 "bind()
" 现在接受一个 VTDNav
对象作为输入,但返回相同的数组列表。主方法调用 "bind(...)
",迭代对象,并使用 XMLModifer
创建 "cd_updated.xml"。
using System;
using System.Collections;
using System.Text;
using com.ximpleware;
namespace XMLBinder2
{
class XMLBinder2
{
ArrayList bind(VTDNav vn)
{
ArrayList al = new ArrayList();
AutoPilot ap0 = new AutoPilot();
AutoPilot ap1 = new AutoPilot();
AutoPilot ap2 = new AutoPilot();
AutoPilot ap3 = new AutoPilot();
AutoPilot ap4 = new AutoPilot();
ap0.selectXPath("/CATALOG/CD[YEAR > 1982 and YEAR < 1990]");
ap1.selectXPath("TITLE"); /*/CATALOG/CD[YEAR > 1982 and YEAR < 1990]/TITLE */
ap2.selectXPath("ARTIST"); /* /CATALOG/CD[YEAR > 1982 and YEAR < 1990]/ARTIST */
ap3.selectXPath("PRICE"); /* /CATALOG/CD[YEAR > 1982 and YEAR < 1990]/PRICE */
ap4.selectXPath("YEAR"); /* /CATALOG/CD[YEAR > 1982 and YEAR < 1990]/YEAR */
ap0.bind(vn);
ap1.bind(vn);
ap2.bind(vn);
ap3.bind(vn);
ap4.bind(vn);
while (ap0.evalXPath() != -1)
{
CDRecord2 cdr = new CDRecord2();
cdr.title = ap1.evalXPathToString();
cdr.artist = ap2.evalXPathToString();
vn.push();
ap3.evalXPath();
cdr.priceIndex = vn.getText();
cdr.price = vn.parseDouble(cdr.priceIndex);
ap3.resetXPath();
vn.pop();
cdr.year = (int)ap4.evalXPathToNumber();
al.Add(cdr);
}
ap0.resetXPath();
return al;
}
public static void Main(String[] args)
{
XMLBinder2 xb = new XMLBinder2();
VTDGen vg = new VTDGen();
XMLModifier xm = new XMLModifier();
if (vg.parseFile("D:\\codeProject\\app1\\XMLBinder2" +
"\\XMLBinder2\\catalog.xml", false))
{
VTDNav vn = vg.getNav();
ArrayList al2 = xb.bind(vn);
IEnumerator ie2 = al2.GetEnumerator();
xm.bind(vn);
while (ie2.MoveNext())
{
CDRecord2 cdr = (CDRecord2)ie2.Current; // reduce prices by 1
xm.updateToken(cdr.priceIndex, "" + (cdr.price - 1));
}
xm.output("D:\\codeProject\\app1\\XMLBinder2\\XMLBinder2\\cd_update.xml");
}
}
}
}
更新后的 XML,包含新的价格,如下所示
<CATALOG>
<CD>
<TITLE>Empire Burlesque</TITLE>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>9.9</PRICE>
<YEAR>1985</YEAR>
</CD><br/>
<CD>
<TITLE>Still Got the Blues</TITLE>
<ARTIST>Gary More</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>Virgin Records</COMPANY>
<PRICE>10.20</PRICE>
<YEAR>1990</YEAR>
</CD>
<CD>
<TITLE>Hide Your Heart</TITLE>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>8.9</PRICE>
<YEAR>1988</YEAR>
</CD><br/>
<CD>
<TITLE>Greatest Hits</TITLE>
<ARTIST>Dolly Parton</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>RCA</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1982</YEAR>
</CD>
</CATALOG>
读、写和索引
自 VTD-XML 2.0 引入原生 XML 索引功能后,你甚至无需解析 XML。如果“catalog.xml”已预索引,只需加载它,即可立即让绑定例程开始工作!main()
可以快速重写如下,完全绕过解析
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using com.ximpleware;
namespace XMLBinder3
{
class XMLBinder3
{
// convert an XML file into a .vxl file here
void indexDocument(String XMLFileName, String indexName)
{
VTDGen vg = new VTDGen();
if (vg.parseFile(XMLFileName, true))
{
vg.writeIndex(indexName);
}
}
ArrayList bind(VTDNav vn)
{
ArrayList al = new ArrayList();
AutoPilot ap0 = new AutoPilot();
AutoPilot ap1 = new AutoPilot();
AutoPilot ap2 = new AutoPilot();
AutoPilot ap3 = new AutoPilot();
AutoPilot ap4 = new AutoPilot();
ap0.selectXPath("/CATALOG/CD[YEAR > 1982 and YEAR < 1990]");
ap1.selectXPath("TITLE"); /*/CATALOG/CD[YEAR > 1982 and YEAR < 1990]/TITLE */
ap2.selectXPath("ARTIST"); /* /CATALOG/CD[YEAR > 1982 and YEAR < 1990]/ARTIST */
ap3.selectXPath("PRICE"); /* /CATALOG/CD[YEAR > 1982 and YEAR < 1990]/PRICE */
ap4.selectXPath("YEAR"); /* /CATALOG/CD[YEAR > 1982 and YEAR < 1990]/YEAR */
ap0.bind(vn);
ap1.bind(vn);
ap2.bind(vn);
ap3.bind(vn);
ap4.bind(vn);
while (ap0.evalXPath() != -1)
{
CDRecord2 cdr = new CDRecord2();
cdr.title = ap1.evalXPathToString();
cdr.artist = ap2.evalXPathToString();
vn.push();
ap3.evalXPath();
cdr.priceIndex = vn.getText();
cdr.price = vn.parseDouble(cdr.priceIndex);
ap3.resetXPath();
vn.pop();
cdr.year = (int)ap4.evalXPathToNumber();
al.Add(cdr);
}
ap0.resetXPath();
return al;
}
public static void Main(String[] args)
{
XMLBinder3 xb = new XMLBinder3();
VTDGen vg = new VTDGen();
XMLModifier xm = new XMLModifier();
xb.indexDocument("D:\\codeProject\\app1\\XMLBinder3\\XMLBinder3\\catalog.xml",
"D:\\codeProject\\app1\\XMLBinder3\\XMLBinder3\\catalog.vxl");
VTDNav vn = vg.loadIndex("D:\\codeProject\\app1\\" +
"XMLBinder3\\XMLBinder3\\catalog.vxl");
ArrayList al2 = xb.bind(vn);
IEnumerator ie2 = al2.GetEnumerator();
xm.bind(vn);
while (ie2.MoveNext())
{
CDRecord2 cdr = (CDRecord2)ie2.Current; // reduce prices by 1
xm.updateToken(cdr.priceIndex, "" + (cdr.price - 1));
}
xm.output("D:\\codeProject\\app1\\XMLBinder3\\XMLBinder3\\cd_update.xml");
}
}
}
优点
采用这种新的 XML 绑定将立即为您的 XML 应用程序提供动力。无论是解析、索引、增量更新,还是非阻塞 XPath,或者避免不必要的对象创建,VTD-XML 不仅能出色地完成许多任务,在许多情况下,VTD-XML 甚至是唯一能够满足您的下一个 SOA 项目性能要求的工具。
而且,你也可以告别所有与模式相关的缺点。您的 XML/SOA 应用程序将更不容易崩溃,并且更能适应变化。
换句话说,官方宣布:XML 的性能问题不复存在。欢迎来到 SOA 时代!
结论
希望本文能帮助您理解新 XML 数据绑定的过程和优势。从根本上讲,XML 模式是达到目的的手段,而不是目的本身。正如您可能已经看到的,当 XML 处理变得足够简单时,XML 模式对于数据绑定来说大多是不好的。为什么创建那些从未使用过的对象呢?找到一个好的解决方案再次需要我们首先改变问题。您的 SOA 成功始于第一步,始于基础。通过将 XPath 与 VTD-XML 结合,您的应用程序现在可以摆脱 XML 模式的束缚,并实现无与伦比的效率和敏捷性。
历史
- 2008 年 2 月 27 日首次发布。