65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.31/5 (20投票s)

2008 年 2 月 27 日

CPOL

9分钟阅读

viewsIcon

75499

downloadIcon

370

敏捷、高效的无模式 XML 数据绑定。

摘要

本文介绍了一种全新的 C#-XML 数据绑定技术,它完全基于 VTD-XMLXPath。这种新方法与传统的 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 数据绑定过程大致包括以下步骤。

  1. 观察 XML 文档并写下与感兴趣的数据字段对应的 XPath 表达式。
  2. 定义类文件和成员变量,这些数据字段将映射到其中。
  3. 重构步骤 (1) 中的 XPath 表达式以降低导航成本。
  4. 编写基于 XPath 的数据绑定例程,执行对象映射。XPath 1.0 允许 XPath 评估为四种数据类型:字符串、布尔值、双精度浮点数和节点集。字符串类型可以进一步转换为其他数据类型。
  5. 如果 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 日首次发布。
© . All rights reserved.