Wie kann ich sicherstellen, dass child.ParentId
nach der Deserialisierung immer gleich parent.Id
ist?
Der natürliche Ansatz zur Einstellung Step.ParentId
nach Deserialisierung wäre dies in einem OnDeserialized
Ereignis zu tun. Leider XmlSerializer
does not support deserialization events. In Anbetracht dessen müssen Sie möglicherweise ein alternatives Design untersuchen.
Eine Möglichkeit ist, Ihr List<Step>
mit einer benutzerdefinierten Sammlung zu ersetzen, die automatisch die ParentId
Referenzaufrechterhält, wenn ein Kind mit einem Elternteil hinzugefügt wird, entlang der Linien von Maintaining xml hierarchy (ie parent-child) information in objects generated by XmlSerializer. Leider ist ObservableCollection
für diesen Zweck nicht geeignet, weil the list of old items is not included in the notification event when it is cleared.Es ist jedoch ziemlich einfach, durch Unterklassen System.Collections.ObjectModel.Collection<T>
unser eigenes zu erstellen.
So würde Ihr Objektmodell das folgende werden. Beachten Sie, dass ich einige Ihrer Eigenschaftsnamen geändert c# naming guidelines folgen:
[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);
}
}
Jetzt ParentId
wird gesetzt, wenn ein Kind mit einem Elternteil hinzugefügt wird, sowohl nach deserialzation und in jeder Code-Anwendungen.
(Wenn Sie aus irgendeinem Grund nicht können Ihre List<Step>
mit einem Collection<Step>
ersetzen, könnten Sie ein Array-Proxy-Eigenschaft und die Einstellung der ParentId
Werte in der Setter, entlang der Linien von XML deserialization with parent object reference betrachten Serialisierung. Aber ich denke, ein Design, das automatisch id setzt die Eltern in allen Situationen vorzuziehen.)
Wie kann ich ein Step
an einen Baum von Step
Objekte hinzufügen, indem ParentId
Angabe?
Sie könnten rekursiv erstellen Linq
Erweiterungen, die die Step
Hierarchie durchqueren, entlang der Linien von 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 ein Kind zu einem bestimmten Elternteil von ID hinzufügen möchten, würden Sie tun:
steps.Add(new Step { Id = "4C", Name = "S112C" }, "4");
Prototyp fiddle.
aktualisieren
Wenn Sie irgendwie Probleme mit der Zugabe von extension methods-Step
und Steps
weil sie Klassen verschachtelt sind, können Sie TraverseSteps()
und Add()
als Objektmethoden könnte hinzufügen:
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));
}
}
@dbc Vielen Dank für deinen Kommentar und deine Antwort! In der zweiten Frage meine ich, einen Schritt in die Step-Hierarchie einzufügen, indem ich nach einer bestimmten Step-Klasse suche. – Royeh