如何使用XmlSerializer反序列化大型文档中的节点

我有一个大型的XML文档,我已经加载到XmlDocument ,我想使用XmlSerializer类将所选元素反序列化为使用xsd.exe生成的.NET类。

这是迄今为止我尝试过的MCVE; xsd和生成的类位于post的末尾。 正如代码中的注释所述,我收到了一个InvalidOperationException was not expected

 static string XmlContent = @"       "; static void TestMcve() { var doc = new XmlDocument(); doc.LoadXml(XmlContent); var nsMgr = new XmlNamespaceManager(doc.NameTable); nsMgr.AddNamespace("myns", "http://MyNamespace"); var rootSerializer = new XmlSerializer(typeof(RootNode)); var root = (RootNode) rootSerializer.Deserialize(new XmlNodeReader(doc)); Console.WriteLine(root.Cars[0].make); // Works fine so far var node = doc.DocumentElement.SelectSingleNode("myns:Cars", nsMgr); Console.WriteLine(node.OuterXml); var carSerializer = new XmlSerializer(typeof(Car)); using (var reader = new XmlNodeReader(node)) { // What I want is a list of Car instances deserialized from // the Car child elements of the Cars element. // The following line throws an InvalidOperationException // " was not expected" // If I change SelectSingleNode above to select "myns:Cars/myns:Car" // I get " was not expected" var result = carSerializer.Deserialize(reader); } } 

我还想随后更新我的Car类实例,并使用XmlSerializer将其插回到文档中,这是后续问题的主题如何使用XmlSerializer在大文档中插入节点 。

xsd和生成的类如下:

                 

xsd.exe生成的代码:

 using System.Xml.Serialization; ///  [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://MyNamespace")] [System.Xml.Serialization.XmlRootAttribute(Namespace="http://MyNamespace", IsNullable=false)] public partial class RootNode { private Car[] carsField; ///  [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] public Car[] Cars { get { return this.carsField; } set { this.carsField = value; } } } ///  [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://MyNamespace")] public partial class Car { private string makeField; ///  [System.Xml.Serialization.XmlAttributeAttribute()] public string make { get { return this.makeField; } set { this.makeField = value; } } } 

你有两个问题:

  1. var node = doc.DocumentElement.SelectSingleNode("myns:Cars", nsMgr); 位于元素 – 节点重复序列的容器元素 – 但您的XmlSerializer构造为反序列XmlSerializer的单个根元素。 尝试使用序列化程序对一系列汽车进行反序列化以对单个汽车进行反序列化是行不通的。

  2. 出于某种原因, xsd.exe为您的Car类型生成了一个没有XmlRoot属性的定义:

     [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://MyNamespace")] // Not included! //[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://MyNamespace")] public partial class Car { } 

    因此,如果您尝试将单个Car实例序列化或反序列化为XML文档的根XML元素,XmlSerializer将期望根元素不在任何名称空间中。 大型文档中的每个节点都位于"http://MyNamespace"默认命名空间中,因此尝试单独反序列化每个节点也不起作用。

    您可以手动将缺少的[XmlRoot(Namespace = "http://MyNamespace")]属性添加到Car ,但如果随后修改了XSD文件并且需要重新生成c#类型,则必须这样做会很麻烦。

要避免这两个问题,可以使用XmlNode.SelectNodes(String, XmlNamespaceManager)选择元素中的每个节点,然后通过构造带有覆盖XmlRootAttributeXmlSerializer反序列化每个XmlRootAttribute ,其中元素名称和命名空间为节点被反序列化。 首先,定义以下扩展方法:

 public static partial class XmlNodeExtensions { public static List DeserializeList(this XmlNodeList nodes) { return nodes.Cast().Select(n => n.Deserialize()).ToList(); } public static T Deserialize(this XmlNode node) { if (node == null) return default(T); var serializer = XmlSerializerFactory.Create(typeof(T), node.LocalName, node.NamespaceURI); using (var reader = new XmlNodeReader(node)) { return (T)serializer.Deserialize(reader); } } } public static class XmlSerializerFactory { // To avoid a memory leak the serializer must be cached. // https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer // This factory taken from // https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648 readonly static Dictionary, XmlSerializer> cache; readonly static object padlock; static XmlSerializerFactory() { padlock = new object(); cache = new Dictionary, XmlSerializer>(); } public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace) { if (serializedType == null) throw new ArgumentNullException(); if (rootName == null && rootNamespace == null) return new XmlSerializer(serializedType); lock (padlock) { XmlSerializer serializer; var key = Tuple.Create(serializedType, rootName, rootNamespace); if (!cache.TryGetValue(key, out serializer)) cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace }); return serializer; } } } 

然后反序列化如下:

 var nodes = doc.DocumentElement.SelectNodes("myns:Cars/myns:Car", nsMgr); var cars = nodes.DeserializeList(); 

必须缓存使用覆盖根元素名称或命名空间构造的序列化程序的节点,以避免内存泄漏,如Marc Gravell在本回答中所述 。

样本工作.Net小提琴 。