You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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();
        }
    }
}

错误原因

  1. 节点类型转换错误:xQuery.Descendants("Ingredients").Nodes()返回的是XNode集合(包含空白节点、注释等),直接强制转换为XElement会导致非元素节点转换为null,后续访问xParent.HasElements就会触发空引用。
  2. 遍历逻辑混乱:xParent.DescendantsAndSelf(xParent.Name).Nodes()既遍历父节点本身又遍历子节点,且同样返回XNode,导致循环中处理了非预期节点。
  3. 节点引用失效:执行xChild.ReplaceWith()后,原xChild已经从文档树中移除,后续xChild.Element(concatName)必然返回null,调用AddBeforeSelf时就会报错。
  4. 冗余无效代码: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 19:24:19