Добавление идентификатора родителя в сериализацию как класс объекта
У меня есть следующий 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; }
}
У меня есть два вопроса:
- Как я могу получить
ParentID
в детское поле для детей?(было бы толькоnull
для узла сid=1
иначе у каждого ребенка есть свой родительский идентификатор) - Второй вопрос заключается в том, что после кодирования в классе объекта, как я могу вставить желаемого потомка с указанием имени идентификатора? Например, я хотел бы вставить ребенка с
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));
}
}