将 Parent id 添加到序列化中作为 Object class
Adding the Parent id to Serialization as Object class
我有以下 XML 文件,我正在使用 VSC#(windows 形式) 代码将其保存为 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 >
我写的代码为:
[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
放入 child 字段中
children?(id=1
节点只有null
,否则
每个 child 都有其 parents id)
- 第二个问题,在objectclass编码后,怎么可能
我插入一个想要的 child 并给出 id 名称?例如,我
想在
id=4C
和 name=S112C
之后插入 child
id=4
? 节点
更新:(回答两个问题后)
让我们假设我想在 Step
中创建一个新字段 Hierarchy
,它采用用户
的字符串 created/given 的值
Step.Hierarchy = // some strings ;
意思是我要换成ParentId
。原因是因为有时候有些情况我应该插入两个空的nodes/components(没有名称和ID,如下所示)作为child用于某些步骤
steps.Add(new Step { Id = " ", Name = " " }, "4");
其中一个空节点将是另一个空节点的 child。那么我将很难为第二个节点提供 PrentId
引用(child 到上述节点)。
steps.Add(new Step { Id = " ", Name = " " }, " ");
这就是为什么我想创建一个像 Hierarchy
这样的虚拟字段来为其分配任意值并引用 ParentId
而不是 Id
。然后每个 Step 都有一个非 null
引用。
如果你有一个想法,将不胜感激!!
如何确保反序列化后child.ParentId
总是等于parent.Id
?
在反序列化后设置 Step.ParentId
的自然方法是在 OnDeserialized
event. Unfortunately, XmlSerializer
does not support deserialization events 中这样做。鉴于此,您可能需要研究替代设计。
一种可能性是用自定义集合替换您的 List<Step>
,当添加 child 时,自动 维护 ParentId
引用a parent,按照 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>
.
因此,您的 object 模型将变为以下内容。请注意,我已经修改了您的一些 属性 名称以遵循 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);
}
}
现在,只要将 child 添加到 parent,无论是在反序列化之后还是在任何应用程序代码中,都会设置 ParentId
。
(如果出于某种原因你 不能 将你的 List<Step>
替换为 Collection<Step>
,你可以考虑序列化一个数组代理 属性 和按照 XML deserialization with parent object reference 的方式在 setter 中设置 ParentId
值。但我认为在所有情况下自动设置 parent id 的设计是更可取的。)
如何通过指定 ParentId
将 Step
添加到 Step
object 的树中?
您可以创建遍历 Step
层次结构的递归 Linq
扩展,按照 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://whosebug.com/users/88656/eric-lippert
// to "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 添加一个 child 到特定的 parent,你会这样做:
steps.Add(new Step { Id = "4C", Name = "S112C" }, "4");
原型fiddle.
更新
如果您在将 extension methods 添加到 Step
和 Steps
时遇到问题,因为它们是嵌套的 类,您可以添加 TraverseSteps()
和 Add()
作为 object 方法:
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));
}
}
我有以下 XML 文件,我正在使用 VSC#(windows 形式) 代码将其保存为 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 >
我写的代码为:
[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
放入 child 字段中 children?(id=1
节点只有null
,否则 每个 child 都有其 parents id) - 第二个问题,在objectclass编码后,怎么可能
我插入一个想要的 child 并给出 id 名称?例如,我
想在
id=4C
和name=S112C
之后插入 childid=4
? 节点
更新:(回答两个问题后)
让我们假设我想在 Step
中创建一个新字段 Hierarchy
,它采用用户
Step.Hierarchy = // some strings ;
意思是我要换成ParentId
。原因是因为有时候有些情况我应该插入两个空的nodes/components(没有名称和ID,如下所示)作为child用于某些步骤
steps.Add(new Step { Id = " ", Name = " " }, "4");
其中一个空节点将是另一个空节点的 child。那么我将很难为第二个节点提供 PrentId
引用(child 到上述节点)。
steps.Add(new Step { Id = " ", Name = " " }, " ");
这就是为什么我想创建一个像 Hierarchy
这样的虚拟字段来为其分配任意值并引用 ParentId
而不是 Id
。然后每个 Step 都有一个非 null
引用。
如果你有一个想法,将不胜感激!!
如何确保反序列化后child.ParentId
总是等于parent.Id
?
在反序列化后设置 Step.ParentId
的自然方法是在 OnDeserialized
event. Unfortunately, XmlSerializer
does not support deserialization events 中这样做。鉴于此,您可能需要研究替代设计。
一种可能性是用自定义集合替换您的 List<Step>
,当添加 child 时,自动 维护 ParentId
引用a parent,按照 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>
.
因此,您的 object 模型将变为以下内容。请注意,我已经修改了您的一些 属性 名称以遵循 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);
}
}
现在,只要将 child 添加到 parent,无论是在反序列化之后还是在任何应用程序代码中,都会设置 ParentId
。
(如果出于某种原因你 不能 将你的 List<Step>
替换为 Collection<Step>
,你可以考虑序列化一个数组代理 属性 和按照 XML deserialization with parent object reference 的方式在 setter 中设置 ParentId
值。但我认为在所有情况下自动设置 parent id 的设计是更可取的。)
如何通过指定 ParentId
将 Step
添加到 Step
object 的树中?
您可以创建遍历 Step
层次结构的递归 Linq
扩展,按照 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://whosebug.com/users/88656/eric-lippert
// to "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 添加一个 child 到特定的 parent,你会这样做:
steps.Add(new Step { Id = "4C", Name = "S112C" }, "4");
原型fiddle.
更新
如果您在将 extension methods 添加到 Step
和 Steps
时遇到问题,因为它们是嵌套的 类,您可以添加 TraverseSteps()
和 Add()
作为 object 方法:
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));
}
}