如何使用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; } } }
你有两个问题:
-
var node = doc.DocumentElement.SelectSingleNode("myns:Cars", nsMgr);
位于
元素 –
节点重复序列的容器元素 – 但您的XmlSerializer
构造为反序列XmlSerializer
为
的单个根元素。 尝试使用序列化程序对一系列汽车进行反序列化以对单个汽车进行反序列化是行不通的。 -
出于某种原因,
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)
选择
元素中的每个
节点,然后通过构造带有覆盖XmlRootAttribute
的XmlSerializer
反序列化每个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小提琴 。
- 使用LINQ查询获取索引值的集合
- 为什么Request.Form.ToString()的返回值与NameValueCollection.ToString()的结果不同
- 如何限制十进制数?
- Microsoft Enterprise Library类型加载exception无法加载Microsoft.Practices.EnterpriseLibrary.Common.Configuration.EnterpriseLibraryContainer
- 当父NT服务被杀/崩溃时杀死子进程
- 如何使PictureBox使用最近邻重采样?
- 如何检索Crystal Report中使用的SQL SELECT语句?
- WebServicesClientProtocol在安全标头中将EncodingType添加到Nonce
- 将double值乘以100.0会引入舍入误差?