将父ID添加到序列化作为对象类

时间:2016-05-13 11:27:42

标签: c# xml xmlserializer

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

<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放入子字段中 孩子?(对于null的节点,只有id=1,否则 每个孩子都有自己的父母身份)
  2. 第二个问题是在对象类编码后,怎么可能 我插入了一个想要给出id名称的孩子?例如,我 我想插入一个id=4Cname=S112C之后的孩子 节点id=4
  3. 更新:(在回答完这两个问题后)

    我们假设我想在Hierarchy中创建一个新的字段Step,其中包含用户创建/给出的字符串值

    Step.Hierarchy = // some strings ;
    

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

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

    其中一个空节点将是另一个空节点的子节点。然后我将难以为第二个节点(子节点到上述节点)提供PrentId引用。

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

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

    如果你有一个会感恩的想法!!

1 个答案:

答案 0 :(得分:1)

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

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

一种可能性是将自己的List<Step>替换为自动自动维护ParentId引用的自定义集合,将子项添加到父级,沿{{{ 3}}。遗憾的是,ObservableCollection不适用于此目的,因为Maintaining xml hierarchy (ie parent-child) information in objects generated by XmlSerializer。但是,通过继承the list of old items is not included in the notification event when it is cleared来创建自己的很容易。

因此,您的对象模型将成为以下内容。请注意,我修改了一些属性名称以跟随System.Collections.ObjectModel.Collection<T>

[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<Step>替换Collection<Step>,您可以考虑序列化数组代理属性并设置ParentId值setter,沿着c# naming guidelines的行。但我认为在所有情况下自动设置父ID的设计是可取的。)

如何通过指定StepStep添加到ParentId个对象树中?

您可以创建遍历Linq层次结构的递归Step扩展,沿XML deserialization with parent object reference行:

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");

原型Efficient graph traversal with LINQ - eliminating recursion

<强>更新

如果您以某种方式将fiddle添加到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));
    }
}