【问题标题】:Adding the Parent id to Serialization as Object class将父 ID 作为对象类添加到序列化
【发布时间】:2016-05-13 11:27:41
【问题描述】:

我有以下 XML 文件,我使用 VSC#(windows forms) 代码将其保存为一个类:

<Steps >
  <Step id ="1" Name="S1">
    <Step id ="2" Name="S11">
      <Step id ="3" Name="S111" />
      <Step id ="4" Name="S112" />
        <Step id ="5" Name="S1121" />
    </Step >
    <Step id ="6" Name="S12" />
  </Step >
</Steps >

我写的代码是:

[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Steps
{
    [System.Xml.Serialization.XmlElementAttribute("Step")]
    public List<Step> Step { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Step
{
    [System.Xml.Serialization.XmlElementAttribute("Step")]
    public List<Step> Step1 { get; set; }
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string name { get; set; }
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string id { get; set; }
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string ParentID { get; set; }
}

我有两个问题:

  1. 如何将ParentID 放入子字段中 孩子?(对于带有id=1 的节点,只有null,否则 每个孩子都有自己的父母 ID)
  2. 第二个问题是在对象类中编码后,怎么可能 我插入一个想要的孩子并给出 id 名称?例如,我 想在之后插入一个带有id=4Cname=S112C 的孩子 带有id=4的节点?

更新:(在回答两个问题后)

假设我想在Step 中创建一个新字段为Hierarchy,它采用用户创建/给定的字符串值

Step.Hierarchy = // some strings ;

这意味着我想用ParentId 替换它。原因是因为有时在某些情况下我应该在某些步骤中插入两个空节点/组件(没有名称和 ID,如下所示)作为子节点

steps.Add(new Step { Id = " ", Name = " " }, "4");

其中一个空节点将是另一个空节点的子节点。然后我将很难为第二个节点(上述节点的子节点)提供PrentId 参考。

steps.Add(new Step { Id = " ", Name = " " }, " ");

这就是为什么我想创建一个像Hierarchy 这样的虚拟字段来为其分配任意值并将ParentId 引用到它而不是Id。然后每个 Step 都有一个非 null 引用。

如果你有一个想法,将不胜感激!

【问题讨论】:

  • @dbc 非常感谢您的评论和回答!在第二个问题中,我的意思是通过搜索给定的 Step 类将 Step 插入 Step 层次结构。

标签: c# xml xmlserializer


【解决方案1】:

如何确保反序列化后child.ParentId 始终等于parent.Id

在反序列化后设置Step.ParentId 的自然方法是在OnDeserialized 事件中这样做。不幸的是,XmlSerializer does not support deserialization events。鉴于此,您可能需要研究替代设计。

一种可能性是将您的List&lt;Step&gt; 替换为自定义集合,当子添加到父级时,该集合自动维护ParentId 引用,类似于Maintaining xml hierarchy (ie parent-child) information in objects generated by XmlSerializer。不幸的是,ObservableCollection 不适合这个目的,因为the list of old items is not included in the notification event when it is cleared。但是,通过子类化 System.Collections.ObjectModel.Collection&lt;T&gt; 来制作我们自己的非常容易。

因此,您的对象模型将变为以下内容。请注意,我已经修改了您的一些属性名称以遵循c# naming guidelines

[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Steps
{
    readonly ChildCollection<Step> steps;

    public Steps()
    {
        this.steps = new ChildCollection<Step>();
        this.steps.ChildAdded += (s, e) =>
        {
            if (e.Item != null)
                e.Item.ParentId = null;
        };
    }

    [System.Xml.Serialization.XmlElementAttribute("Step")]
    public Collection<Step> StepList { get { return steps; } }
}

[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Step
{
    readonly ChildCollection<Step> steps;

    public Step()
    {
        this.steps = new ChildCollection<Step>();
        this.steps.ChildAdded += (s, e) =>
        {
            if (e.Item != null)
                e.Item.ParentId = this.Id;
        };
    }

    [System.Xml.Serialization.XmlElementAttribute("Step")]
    public Collection<Step> StepList { get { return steps; } }

    [System.Xml.Serialization.XmlAttributeAttribute("Name")]
    public string Name { get; set; }
    [System.Xml.Serialization.XmlAttributeAttribute("id")]
    public string Id { get; set; }
    [System.Xml.Serialization.XmlAttributeAttribute("ParentID")]
    public string ParentId { get; set; }
}

public class ChildCollectionEventArgs<TChild> : EventArgs
{
    public readonly TChild Item;

    public ChildCollectionEventArgs(TChild item)
    {
        this.Item = item;
    }
}

public class ChildCollection<TChild> : Collection<TChild>
{
    public event EventHandler<ChildCollectionEventArgs<TChild>> ChildAdded;

    public event EventHandler<ChildCollectionEventArgs<TChild>> ChildRemoved;

    void OnRemoved(TChild item)
    {
        var removed = ChildRemoved;
        if (removed != null)
            removed(this, new ChildCollectionEventArgs<TChild>(item));
    }

    void OnAdded(TChild item)
    {
        var added = ChildAdded;
        if (added != null)
            added(this, new ChildCollectionEventArgs<TChild>(item));
    }

    public ChildCollection() : base() { }

    protected override void ClearItems()
    {
        foreach (var item in this)
            OnRemoved(item);
        base.ClearItems();
    }

    protected override void InsertItem(int index, TChild item)
    {
        OnAdded(item);
        base.InsertItem(index, item);
    }

    protected override void RemoveItem(int index)
    {
        if (index >= 0 && index < Count)
        {
            OnRemoved(this[index]);
        }
        base.RemoveItem(index);
    }

    protected override void SetItem(int index, TChild item)
    {
        OnAdded(item);
        base.SetItem(index, item);
    }
}

现在ParentId 将在将子级添加到父级时设置,无论是在反序列化之后还是在任何应用程序代码中。

(如果出于某种原因无法List&lt;Step&gt; 替换为Collection&lt;Step&gt;,则可以考虑序列化数组代理属性并在setter 中设置ParentId 值,如下所示的XML deserialization with parent object reference。但我认为在所有情况下自动设置父ID的设计更可取。)

如何通过指定ParentIdStep 添加到Step 对象树中?

您可以按照Efficient graph traversal with LINQ - eliminating recursion 的行创建遍历Step 层次结构的递归Linq 扩展:

public static class StepExtensions
{
    public static IEnumerable<Step> TraverseSteps(this Steps root)
    {
        if (root == null)
            throw new ArgumentNullException();
        return RecursiveEnumerableExtensions.Traverse(root.StepList, s => s.StepList);
    }

    public static IEnumerable<Step> TraverseSteps(this Step root)
    {
        if (root == null)
            throw new ArgumentNullException();
        return RecursiveEnumerableExtensions.Traverse(root, s => s.StepList);
    }

    public static bool TryAdd(this Steps root, Step step, string parentId)
    {
        foreach (var item in root.TraverseSteps())
            if (item != null && item.Id == parentId)
            {
                item.StepList.Add(step);
                return true;
            }
        return false;
    }

    public static void Add(this Steps root, Step step, string parentId)
    {
        if (!root.TryAdd(step, parentId))
            throw new InvalidOperationException(string.Format("Parent {0} not found", parentId));
    }
}

public static class RecursiveEnumerableExtensions
{
    // Rewritten from the answer by Eric Lippert https://stackoverflow.com/users/88656/eric-lippert
    // to "Efficient graph traversal with LINQ - eliminating recursion" http://stackoverflow.com/questions/10253161/efficient-graph-traversal-with-linq-eliminating-recursion
    // to ensure items are returned in the order they are encountered.

    public static IEnumerable<T> Traverse<T>(
        T root,
        Func<T, IEnumerable<T>> children)
    {
        yield return root;

        var stack = new Stack<IEnumerator<T>>();
        try
        {
            stack.Push((children(root) ?? Enumerable.Empty<T>()).GetEnumerator());

            while (stack.Count != 0)
            {
                var enumerator = stack.Peek();
                if (!enumerator.MoveNext())
                {
                    stack.Pop();
                    enumerator.Dispose();
                }
                else
                {
                    yield return enumerator.Current;
                    stack.Push((children(enumerator.Current) ?? Enumerable.Empty<T>()).GetEnumerator());
                }
            }
        }
        finally
        {
            foreach (var enumerator in stack)
                enumerator.Dispose();
        }
    }

    public static IEnumerable<T> Traverse<T>(
        IEnumerable<T> roots,
        Func<T, IEnumerable<T>> children)
    {
        return from root in roots
               from item in Traverse(root, children)
               select item;
    }
}

他们通过 ID 将孩子添加到特定的父母,你会这样做:

steps.Add(new Step { Id = "4C", Name = "S112C" }, "4");

原型fiddle

更新

如果您在将extension methods 添加到StepSteps 时遇到问题,因为它们是嵌套类,您可以添加TraverseSteps()Add() 作为对象方法:

public partial class Step
{
    public IEnumerable<Step> TraverseSteps()
    {
        return RecursiveEnumerableExtensions.Traverse(this, s => s.StepList);
    }
}

public partial class Steps
{
    public IEnumerable<Step> TraverseSteps()
    {
        return RecursiveEnumerableExtensions.Traverse(StepList, s => s.StepList);
    }

    public bool TryAdd(Step step, string parentId)
    {
        foreach (var item in TraverseSteps())
            if (item != null && item.Id == parentId)
            {
                item.StepList.Add(step);
                return true;
            }
        return false;
    }

    public void Add(Step step, string parentId)
    {
        if (!TryAdd(step, parentId))
            throw new InvalidOperationException(string.Format("Parent {0} not found", parentId));
    }
}

【讨论】:

  • 感谢您的回答。关于第一个问题,我有一个错误。 error CS0246: The type or namespace name 'Collection' could not be found。我可以请你帮忙吗?
  • Collection&lt;T&gt; 在命名空间System.Collections.ObjectModel 中,所以你需要using System.Collections.ObjectModel;
  • 对不起,我有,但有一个错字。顺便说一下第二个问题我有这个错误:error CS1109: Extension method must be defined in a top level static class; StepExtensions is a nested class。我把它放在主窗体之外,但我仍然有错误
  • @Royeh - 你必须仍然以某种方式嵌套它。见the fiddle。或者您可以将Traverse()Add()TryAdd() 作为实例方法添加到StepSteps,如更新后的答案所示。
  • 非常感谢您的完美回答。这是非常热心的一个。我可以请求您对更新部分的帮助和想法。我真的很感谢你的时间! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多