Xml 循环引用

Xml circular reference

提问人:Raheel Khan 提问时间:8/14/2023 更新时间:8/14/2023 访问量:40

问:

我正在尝试使用下面的类结构生成以下 XML。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VirtualDesktopManager>
  <Categories>
    <Category Name="Category 1">
      <Desktops>
        <Desktop Name="Desktop 1">
          <Applications Name="Application 1" />
          <Applications Name="Application 2" />
        </Desktop>
      </Desktops>
    </Category>
  </Categories>
</VirtualDesktopManager>

执行以下代码时,出现异常:。这些类本身没有任何循环引用,所以我一定做错了什么。System.ArgumentException: 'Cannot insert a node or any ancestor of that node as a child of itself.'

private static void Main ()
{
    var database = new Database();
    var category = new VirtualDesktopCategory();
    var desktop = new VirtualDesktop();
    var application = new VirtualDesktopApplication();

    category = new VirtualDesktopCategory() { Name = "Cat 1", };
    database.Categories.Add(category);
    desktop = new VirtualDesktop() { Name = "Desktop 1", };
    category.Desktops.Add(desktop);
    application = new VirtualDesktopApplication() { Name = "Application 1", };
    desktop.Applications.Add(application);
    application = new VirtualDesktopApplication() { Name = "Application 2", };
    desktop.Applications.Add(application);

    database.ToXmlDocument().InnerText.Dump();
}

public class Database
{
    public string Name { get; set; } = "";
    public List<VirtualDesktopCategory> Categories { get; private set; } = new();

    public XmlDocument ToXmlDocument()
    {
        var document = new XmlDocument();
        var xml = $@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes"" ?><VirtualDesktopManager></VirtualDesktopManager>";

        document.LoadXml(xml);
        document.DocumentElement?.AppendChild(this.ToXmlElement(document, document.DocumentElement));

        return document;
    }

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementCategories = document.CreateElement("Categories");

        elementParent.AppendChild(elementCategories);

        foreach (var category in this.Categories)
        {
            // System.ArgumentException: Cannot insert a node or any ancestor of that node as a child of itself.
            elementCategories.AppendChild(category.ToXmlElement(document, elementCategories));
        }

        return elementCategories;
    }
}

public class VirtualDesktopCategory
{
    public string Name { get; set; } = "";
    public List<VirtualDesktop> Desktops { get; private set; } = new();

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementCategory = document.CreateElement("Category");
        var elementDesktops = document.CreateElement("Desktops");

        elementCategory.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementCategory);
        elementCategory.AppendChild(elementDesktops);

        foreach (var desktop in this.Desktops)
        {
            elementDesktops.AppendChild(desktop.ToXmlElement(document, elementDesktops));
        }

        return elementCategory;
    }
}

public class VirtualDesktop
{
    public string Name { get; set; } = "";
    public List<VirtualDesktopApplication> Applications { get; private set; } = new();

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementDesktop = document.CreateElement("Desktop");
        var elementApplications = document.CreateElement("Applications");

        elementDesktop.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementDesktop);
        elementDesktop.AppendChild(elementApplications);

        foreach (var application in this.Applications)
        {
            elementApplications.AppendChild(application.ToXmlElement(document, elementApplications));
        }

        return elementParent;
    }
}

public class VirtualDesktopApplication
{
    public string Name { get; set; } = "";

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementApplication = document.CreateElement("Application");

        elementApplication.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementApplication);

        return elementApplication;
    }
}

public static class Extensions
{
    public static XmlAttribute AppendAttribute(this XmlElement element, XmlDocument document, string name, string value)
    {
        var attribute = document.CreateAttribute(name);

        attribute.Value = value;
        element.Attributes.Append(attribute);

        return attribute;
    }
}

任何指点将不胜感激。

C# .NET XML xmlDocument 循环引用

评论

0赞 user246821 8/14/2023
您可能对以下内容感兴趣: stackoverflow.com/a/73640395/10024425
1赞 canton7 8/14/2023
不是特别相关,但您不使用 XmlSerializer 的任何原因?这将删除您那里的大部分样板
0赞 Raheel Khan 8/14/2023
@canton7:是的,这只是示例代码,但我需要更好地控制写入的内容和读取的内容,因此基于属性的序列化不适合我的情况。

答:

1赞 canton7 8/14/2023 #1
public class VirtualDesktop
{
    public string Name { get; set; } = "";
    public List<VirtualDesktopApplication> Applications { get; private set; } = new();

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementDesktop = document.CreateElement("Desktop");
        var elementApplications = document.CreateElement("Applications");

        elementDesktop.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementDesktop);
        elementDesktop.AppendChild(elementApplications);

        foreach (var application in this.Applications)
        {
            elementApplications.AppendChild(application.ToXmlElement(document, elementApplications));
        }

        return elementParent;
    }
}

你要返回 ,而不是 。elementParentelementDesktop

您错误地标识了引发异常的行。它实际上是:

elementDesktops.AppendChild(desktop.ToXmlElement(document, elementDesktops));

所以,有些东西是原因。快速的试错表明,注释掉所有内容并不能解决它,所以它介于 和 ...等。。。desktop.ToXmlElementVirtualDesktop.ToXmlElementvar elementDesktop = document.CreateElement("Desktop")return elementParent;


您还错误地序列化了 XmlDocument。您需要执行以下操作:

using var sr = new StringWriter();
using (var writer = XmlWriter.Create(sr))
{
    database.ToXmlDocument().Save(writer);
}
Console.WriteLine(sr.ToString());

但是,这将为您提供:

<?xml version="1.0" encoding="utf-16" standalone="yes"?>

要修复编码,您需要执行如下操作: https://stackoverflow.com/a/1564727/1086121


工作示例注释掉了由 @EyesShriveledToRaisins 标识的重复行。

评论

0赞 Raheel Khan 8/14/2023
我知道这一定是一个愚蠢的错误。事实上,正如你所指出的,回归需要是.谢谢!elementParentelementDesktop
0赞 canton7 8/14/2023
@RaheelKhan,FWIW 通过删除代码直到只剩下生成异常的代码来生成最小可重现示例(如最小可重现示例),这将使问题变得非常明显。我通过注释掉行有效地做了同样的事情,并在不到一分钟的时间内发现了问题。
2赞 EyesShriveledToRaisins 8/14/2023 #2

除了 canton7 的答案之外,看看你是如何处理(某些)方法的返回节点的。ToXmlElement

在这些方法中,将要返回的节点添加到给定的父元素。然后,在相应循环中的这些方法之外,再次将该节点添加到父元素中。ToXmlElementforeach

评论

0赞 canton7 8/14/2023
你是对的,但有趣的是,这似乎不会对生成的 XML 产生任何不利影响。
2赞 EyesShriveledToRaisins 8/14/2023
@canton7,真的。我刚刚检查了文档,它指出“如果 newChild 已经在树中,则将其从其原始位置移除并添加到其目标位置。因此,从技术上讲,这不会产生不良后果,因为节点被添加到父节点,然后从中移除,然后再次添加到父节点中,而其他节点则被添加/删除到中间的同一父节点。尽管如此,这仍表明问题中的代码存在设计问题。我现在在思考我是否应该删除我的答案或让它留下来,即使它并没有真正解决问题......AppendChild
0赞 canton7 8/14/2023
我说离开它 - 它增加了有用的信息,我已经为它投了赞成票
0赞 Raheel Khan 8/14/2023
谢谢@EyesShriveledToRaisins。我说离开它,也给它投了赞成票。