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

时间:2018-02-06 21:30:10

标签: c# .net xml xsd xmlserializer

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

这是迄今为止我尝试过的MCVE; xsd和生成的类位于帖子的末尾。如代码中的评论所述,我收到了InvalidOperationException - <Cars xmlns:'http://MyNamespace' /> was not expected

static string XmlContent = @"
    <RootNode xmlns=""http://MyNamespace"">
        <Cars>
        <Car make=""Volkswagen"" />
        <Car make=""Ford"" />
        <Car make=""Opel"" />
        </Cars>
    </RootNode>";

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
        // "<Cars xmlns:'http://MyNamespace' /> was not expected"
        // If I change SelectSingleNode above to select "myns:Cars/myns:Car"
        // I get "<Car xmlns:'http://MyNamespace' /> was not expected"
        var result = carSerializer.Deserialize(reader);
    }
}

我还希望随后更新我的Car类实例,然后使用XmlSerializer将其重新插入到文档中,这是后续问题How to insert a node in a large document using XmlSerializer 的主题。< / p>

xsd和生成的类如下:

<xs:schema xmlns="http://MyNamespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
           targetNamespace="http://MyNamespace" 
           elementFormDefault="qualified" attributeFormDefault="unqualified" 
           version="3.9.0.8">
  <xs:complexType name="Cars">
    <xs:sequence>
      <xs:element name="Car" type="Car" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="Car">
    <xs:attribute name="make" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType name="RootNode">
    <xs:sequence>
      <xs:element name="Cars" type="Cars" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="RootNode" type="RootNode" />
</xs:schema>

xsd.exe生成的代码:

using System.Xml.Serialization;


/// <remarks/>
[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;

    /// <remarks/>
    [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)]
    public Car[] Cars {
        get {
            return this.carsField;
        }
        set {
            this.carsField = value;
        }
    }
}

/// <remarks/>
[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;

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string make {
        get {
            return this.makeField;
        }
        set {
            this.makeField = value;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

这里有两个问题:

  1. var node = doc.DocumentElement.SelectSingleNode("myns:Cars", nsMgr);位于<Cars>元素 - 重复<Car>个节点序列的容器元素 - 但您的XmlSerializer构造为反序列化名为<Car>的单个根元素。尝试使用用于反序列化单个汽车的序列化程序来反序列化一系列汽车将不起作用。

  2. 由于某种原因,xsd.exeCar类型生成了没有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将期望根元素不在任何名称空间中。大型文档中的每个<Car>节点都在"http://MyNamespace"默认命名空间中,因此尝试单独反序列化每个节点也不起作用。

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

  3. 要避免这两个问题,您可以使用XmlNode.SelectNodes(String, XmlNamespaceManager)选择<Car>元素中的每个<Cars>个节点,然后使用覆盖{{1}将每个节点反序列化constructing an XmlSerializer使用要反序列化的节点的元素名称和名称空间。首先,定义以下扩展方法:

    XmlRootAttribute

    然后按如下方式反序列化:

    public static partial class XmlNodeExtensions
    {
        public static List<T> DeserializeList<T>(this XmlNodeList nodes)
        {
            return nodes.Cast<XmlNode>().Select(n => n.Deserialize<T>()).ToList();
        }
    
        public static T Deserialize<T>(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<Tuple<Type, string, string>, XmlSerializer> cache;
        readonly static object padlock;
    
        static XmlSerializerFactory()
        {
            padlock = new object();
            cache = new Dictionary<Tuple<Type, string, string>, 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;
            }
        }
    }
    

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

    示例工作.Net fiddle