在一个大型XML文档中反序列化单个元素:由于命名空间问题,xmlSerializer.Deserialize(xmlReader.ReadSubtree())失败。

7
我正在尝试使用 XmlReader 处理大型 XML 文档,并使用 XmlSerializer 只反序列化其中的某些元素。以下是一些代码和一个小的模拟 XML 文档,展示了我如何尝试实现这一目标。
使用 XmlReader 的原因如下: 1. 我处理的是非常大的 XML 文档(10-250 MB),因此不想将其加载到内存中。因此,XmlDocument 不可行。 2. 我只想提取特定的元素。通常情况下,我可以忽略大部分其他内容。XmlReader 看起来为跳过无关内容提供了有效的方法。 3. 我事先不知道我能够处理的所有元素是否都存在;因此,我不使用一堆基于 XPath/XQuery 或 LINQ to XML 的查询,因为我只想在 XML 文件上进行一次遍历(由于它们的大小)。
public class ElementOfInterest { }
…

var xml = @"<?xml version='1.0' encoding='utf-8' ?>
            <Root xmlns:ex='urn:stakx:example'
                  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
              <ElementOfInterest xsi:type='ex:ElementOfInterest' />
            </Root>";

var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(xml));
reader.ReadToFollowing("ElementOfInterest");

var serializer = new System.Xml.Serialization.XmlSerializer(typeof(ElementOfInterest));
serializer.Deserialize(reader.ReadSubtree());

代码的最后一行会抛出以下内部异常:
InvalidOperationException: "命名空间前缀ex未定义。"
显然,XmlSerializer无法识别xsi:type属性值中的ex命名空间前缀。
这只是我遇到的一个错误,但说实话,更大的问题是我不知道如何处理整个命名空间问题。我只是想方便地反序列化XML文档中的单个节点,但似乎必须手动注册/管理命名空间,并将其从XmlReader转发到XmlSerializer。
有人能演示如何从使用XmlReader读取的XML文档中反序列化单个节点吗?可以指出我的代码中的错误,或者展示一种替代方法吗?

3
寻找一个关于XmlNamespaceManager的示例。这里有一个可以作为起点。 - kennyzx
@kennyzx:我已经查看了XmlNamespaceManagerXmlNameTableXmlParserContext等内容,但是在我的情况下,我根本不知道它们应该如何配合使用。你能否为我演示一下它们的用法? - stakx - no longer contributing
1个回答

8
以下是有效的:
using System.IO;
using System.Xml;
using System.Xml.Serialization;

static void Main()
{
    var xml = @"<?xml version='1.0' encoding='utf-8' ?>
                <Root
                  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
                  xmlns:ex='urn:stakx:example'
                >
                  <ex:ElementOfInterest xsi:type='ex:ElementOfInterest' />
                </Root>";

    var nt = new NameTable();
    var mgr = new XmlNamespaceManager(nt);
    mgr.AddNamespace("ex", "urn:stakx:example");
    var ctxt = new XmlParserContext(nt, mgr, "", XmlSpace.Default);
    var reader = XmlReader.Create(new StringReader(xml), null, ctxt);
    var serializer = new XmlSerializer(typeof(ElementOfInterest));

    reader.ReadToFollowing("ElementOfInterest", "urn:stakx:example");
    var eoi = (ElementOfInterest)serializer.Deserialize(reader.ReadSubtree());
}

[XmlRoot(Namespace = "urn:stakx:example")]
public class ElementOfInterest { }

请注意输入中的命名空间:<ex:ElementOfInterest>

你能解释一下为什么你改变了输入文档(即向元素添加命名空间前缀)吗?是为了让你的代码工作,还是因为我的示例输入版本存在格式错误或无效? - stakx - no longer contributing
1
两个都有。好吧,你的输入文档说明了结果对象应该在urn:stakx:example命名空间中。你的目标类ElementOfInterest没有反映出这一点,所以加入XmlRoot(Namespace=...)类属性是第一个变化。现在,当你再次序列化一个ElementOfInterest对象时,生成的XML元素也会在urn:stakx:example命名空间中。为了使反序列化和序列化对称,我必须把元素放入那个命名空间中。 - Tomalak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接