Search code examples
c#xmlxmlserializer

Adding the Parent id to Serialization as Object class


I have the following XML file which I am using the VSC#(windows forms) code to save it as a class:

<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 >

The code I wrote as:

[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; }
}

I have two questions:

  1. How can I get the ParentID into the child field for children?(there would be only null for node with id=1, otherwise each child has its parents id)
  2. The second question is that after coding in object class, how could I insert a desired child with giving the id name? For example, I would like to insert a child with id=4C and name=S112C after node with id=4?

Update:(after answering both question)

Let's we assume that I want to create a new field as Hierarchy in the Step which takes values of string created/given by user

Step.Hierarchy = // some strings ;

It means I want to replace it with ParentId. The reason is that because sometimes there are some situations which I should insert two empty nodes/components(There is no name and Id for it, as below) as a child for some steps

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

where one empty node will be child of other one. Then I will have difficulty for giving PrentId reference for the second node(child to the above node).

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

This is why I want to create a virtual field like Hierarchy to assign an arbitrary value to it and refer ParentId to it instead of Id. Then each Step has a non null reference.

If you have an idea that would be thankful!!


Solution

  • How can I ensure that child.ParentId always equals parent.Id after deserializing?

    The natural approach to setting Step.ParentId after deserialization would be to do so in an OnDeserialized event. Unfortunately, XmlSerializer does not support deserialization events. Given that, you may need to investigate an alternate design.

    One possibility is to replace your List<Step> with a custom collection that automatically maintains the ParentId reference when a child is added to a parent, along the lines of Maintaining xml hierarchy (ie parent-child) information in objects generated by XmlSerializer. Unfortunately, ObservableCollection is not suitable for this purpose, because the list of old items is not included in the notification event when it is cleared. However, it's quite easy to make our own by subclassing System.Collections.ObjectModel.Collection<T>.

    Thus, your object model would become the following. Note that I have modified some of your property names to follow 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);
        }
    }
    

    Now ParentId will be set whenever a child is added to a parent, both after deserialzation, and in any applications code.

    (If for whatever reason you cannot replace your List<Step> with a Collection<Step>, you could consider serializing an array proxy property and setting the ParentId values in the setter, along the lines of XML deserialization with parent object reference. But I think a design that automatically sets the parent id in all situations is preferable.)

    How can I add a Step to a tree of Step objects by specifying ParentId?

    You could create recursive Linq extensions that traverse the Step hierarchy, along the lines of Efficient graph traversal with LINQ - eliminating recursion:

    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;
        }
    }
    

    Them to add a child to a specific parent by ID, you would do:

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

    Prototype fiddle.

    Update

    If you somehow are having trouble adding extension methods to Step and Steps because they are nested classes, you could add TraverseSteps() and Add() as object methods:

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