Добавление идентификатора родителя в сериализацию как класс объекта

У меня есть следующий 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=4C а также name=S112C после узла с id=4?

Обновление:(после ответа на оба вопроса)

Давайте предположим, что я хочу создать новое поле как Hierarchy в Step которая принимает значения строки, созданной / предоставленной пользователем

Step.Hierarchy = // some strings ;

Это означает, что я хочу заменить его ParentId, Причина в том, что иногда возникают ситуации, в которых я должен вставить два пустых узла / компонента (для них нет имени и идентификатора, как показано ниже) как дочерний элемент для некоторых шагов.

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

где один пустой узел будет дочерним по отношению к другому. Тогда мне будет трудно дать PrentId ссылка на второй узел (дочерний по отношению к вышеуказанному узлу).

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

Вот почему я хочу создать виртуальное поле, как Hierarchy присвоить ему произвольное значение и сослаться на него ParentId к этому вместо Id, Тогда каждый шаг имеет не null ссылка.

Если у вас есть идея, которая была бы благодарна!

1 ответ

Решение

Как я могу гарантировать, что child.ParentId всегда равняется parent.Id после десериализации?

Естественный подход к установке Step.ParentId после десериализации будет делать это в OnDeserialized событие. К несчастью, XmlSerializer не поддерживает события десериализации. Учитывая это, вам может понадобиться изучить альтернативный дизайн.

Одной из возможностей является замена вашего List<Step> с пользовательской коллекцией, которая автоматически поддерживает ParentId ссылка, когда дочерний элемент добавляется к родительскому элементу, в соответствии с информацией о сохранении иерархии xml (т.е. parent-child) в объектах, созданных XmlSerializer. К несчастью, ObservableCollection не подходит для этой цели, поскольку список старых элементов не включается в событие уведомления при его очистке. Тем не менее, это довольно легко сделать наши собственные подклассы System.Collections.ObjectModel.Collection<T>,

Таким образом, ваша объектная модель станет следующей. Обратите внимание, что я изменил некоторые из ваших имен свойств в соответствии с правилами именования C#:

[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 значения в установщике, в соответствии с десериализацией XML со ссылкой на родительский объект. Но я думаю, что дизайн, который автоматически устанавливает родительский идентификатор во всех ситуациях, предпочтительнее.)

Как я могу добавить Step к дереву Step объекты путем указания ParentId ?

Вы могли бы создать рекурсивный Linq расширения, которые пересекают 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://stackru.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;
    }
}

Их, чтобы добавить ребенка к определенному родителю по идентификатору, вы бы сделали:

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

Прототип скрипки.

Обновить

Если у вас возникли проблемы с добавлением методов расширения в Step а также Steps потому что они являются вложенными классами, вы можете добавить 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));
    }
}
Другие вопросы по тегам