How to deserialize a node in a large document using XmlSerializer

岁酱吖の 提交于 2019-12-02 04:12:38

You have two problems here:

  1. The var node = doc.DocumentElement.SelectSingleNode("myns:Cars", nsMgr); is positioned at the <Cars> element -- the container element for the repeating sequence of <Car> nodes -- but your XmlSerializer is constructed to deserialize a single root element named <Car>. Trying to deserialize a sequence of cars with a serializer constructed to deserialize a single car will not work.

  2. For some reason xsd.exe generated a definition for your Car type without an XmlRoot attribute:

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

    Thus if you attempt to serialize or deserialize a single instance of a Car as the root XML element of an XML document then XmlSerializer will expect that root element to not be in any namespace. Each <Car> node in your large document is in the "http://MyNamespace" default namespace, so attempting to deserialize each one individually also will not work.

    You could manually add the missing [XmlRoot(Namespace = "http://MyNamespace")] attribute to Car, but having to do this can be a nuisance if the XSD files are subsequently modified and the c# types need to be regenerated.

To avoid both issues, you can use XmlNode.SelectNodes(String, XmlNamespaceManager) to select every <Car> nodes inside the <Cars> element, then deserialize each one by constructing an XmlSerializer with an override XmlRootAttribute with the element name and namespace of the node being deserialized. First, define the following extension methods:

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;
        }
    }
}

Then deserialize as follows:

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

Node that a serializer constructed with an override root element name or namespace must be cached to avoid a memory leak as explained in this answer by Marc Gravell.

Sample working .Net fiddle.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!