使用Linq to Xml合并XElement父子节点名值遇空引用异常求助
排查XML节点扁平化通用代码的NullReferenceException错误
问题背景
手里有个约1万条记录的XML文档,已经通过手动编码实现了Price、Cost这类父节点的扁平化——把父节点名称和子节点名称合并,将子节点值移到父节点同级位置,再删除原父节点。现在想写通用代码复用这个逻辑,但运行时在xChild.ReplaceWith()处抛出System.NullReferenceException,请求排查解决。
原始XML示例
<?xml version="1.0" encoding="utf-8"?> <Home> <Kitchen> <Pantry> <Ingredients> <Name>Tomato</Name> <Price> <ID>1</ID> <Name>SALES</Name> </Price> <Cost> <ID>2</ID> <Name>COGS</Name> </Cost> </Ingredients> </Pantry> </Kitchen> </Home>
手动实现的工作代码
// source programmatically generated XElement xml = XElement.Parse(xml.ToString()); foreach (XElement el in xQuery.Descendants("Price")) { XElement id = el.Element("ID"); id.ReplaceWith(new XElement("Price_ID", el.Element("ID").Value)); XElement name = el.Element("Name"); name.ReplaceWith(new XElement("Price_Name", el.Element("Name").Value)); XElement Price_ID = el.Element("Price_ID"); XElement Price_Name = el.Element("Price_Name"); el.AddBeforeSelf(Price_ID); el.AddBeforeSelf(Price_Name); } foreach (XElement el in xQuery.Descendants("ItemInventoryRet")) { if (el.Element("Price") != null) { XElement Price= el.Element("Price"); Price.Remove(); } } // same manual procedure for Cost element..
期望输出结果
<?xml version="1.0" encoding="utf-8"?> <Home> <Kitchen> <Pantry> <Ingredients> <Name>Tomato</Name> <Price_ID>1</Price_ID> <Price_Name>SALES</Price_Name> <Cost_ID>2</Cost_ID> <Cost_Name>COGS</Cost_Name> </Ingredients> </Pantry> </Kitchen> </Home>
出错的通用代码
// source programmatically generated XElement xml = XElement.Parse(xml.ToString()); foreach (XElement xParent in xQuery.Descendants("Ingredients").Nodes()) { if (xParent.HasElements) { string parentName = xParent.Name.ToString(); foreach (XElement xChild in xParent.DescendantsAndSelf(xParent.Name).Nodes()) { string childName = xChild.Name.ToString(); string concatName = xParent.Name.ToString() + "_" + xChild.Name.ToString(); XElement child = xChild.Element(childName); xChild.ReplaceWith(new XElement(concatName, xChild.Value.ToString())); // 此处抛出NullReferenceException XElement concatElement = xChild.Element(concatName); xChild.AddBeforeSelf(concatElement); } if (xParent.Element(parentName) != null) { XElement parentEl = xParent.Element(parentName); parentEl.Remove(); } } }
错误原因
- 节点类型转换错误:
xQuery.Descendants("Ingredients").Nodes()返回的是XNode集合(包含空白节点、注释等),直接强制转换为XElement会导致非元素节点转换为null,后续访问xParent.HasElements就会触发空引用。 - 遍历逻辑混乱:
xParent.DescendantsAndSelf(xParent.Name).Nodes()既遍历父节点本身又遍历子节点,且同样返回XNode,导致循环中处理了非预期节点。 - 节点引用失效:执行
xChild.ReplaceWith()后,原xChild已经从文档树中移除,后续xChild.Element(concatName)必然返回null,调用AddBeforeSelf时就会报错。 - 冗余无效代码:
XElement child = xChild.Element(childName);完全多余,xChild本身就是目标子元素,不需要再自己查找自己。
修复后的通用代码
// 假设xQuery是你的XML文档根节点或查询结果 XElement xml = XElement.Parse(xQuery.ToString()); // 遍历Ingredients下所有包含子元素的节点(比如Price、Cost),ToList避免集合变更异常 foreach (XElement parentNode in xml.Descendants("Ingredients").Elements().Where(e => e.HasElements).ToList()) { string parentName = parentNode.Name.LocalName; // 遍历当前父节点的所有直接子元素,生成合并命名的新节点 foreach (XElement childNode in parentNode.Elements().ToList()) { string concatName = $"{parentName}_{childNode.Name.LocalName}"; // 将新节点插入到原父节点的前面 parentNode.AddBeforeSelf(new XElement(concatName, childNode.Value)); } // 删除原父节点(比如Price、Cost) parentNode.Remove(); }
代码说明
- 使用
.ToList()将查询结果转为列表,避免遍历过程中修改文档树导致的集合变更异常。 - 通过
.Where(e => e.HasElements)只处理包含子节点的元素,跳过Name这类叶子节点。 - 直接用
childNode.Value获取子节点的文本值,无需额外查找。 - 生成新节点后直接插入到原父节点的同级位置,最后删除原父节点,逻辑清晰且高效。
内容的提问来源于stack exchange,提问作者RickyBelmont
相关产品推荐
相关产品推荐

