【发布时间】:2015-11-24 03:54:00
【问题描述】:
我正在尝试构建一个执行以下步骤的 Linq-to-XML 查询:
- 对 XDocument 中的所有后代节点进行分组
- 聚合每组中的不同项目
- 按照我选择的顺序将每个组中最后一项的父项中的元素替换为聚合项
- 删除每个组中的所有原始项目
到目前为止,我已经完成了使用以下代码的前两个步骤。请注意,MyGroupByKeyFunction 的编写方式保证(除其他外)每个组中的所有元素都将具有相同的深度(这就是 orderby 起作用的原因)。
var groups =
from e in doc.Root.Descendants()
group e by MyGroupByKeyFunction(e) into g
orderby g.First().Ancestors().Count() descending
select new {
agg = g.Aggregate(new List<XElement>(), (list, el) => {
list.Add(el);
return list;
}).Distinct(new MyCustomXElementEqualityComparer()),
items = g,
target = g.Last().Parent
};
最后两个步骤是我卡住的地方。我尝试了以下方法,但效果并不理想。
foreach (var group in groups)
{
group.items.Remove();
foreach (var item in group.merge)
{
group.target.Add(item);
}
}
group.items 中的元素已成功删除并填充了目标,但如果对 group.items.Remove() 的调用导致父元素被删除,我也希望删除 group.items 中元素的父元素清空。因此,我尝试用以下内容替换该行:
foreach (var delete in group.items)
{
if (delete.Parent.Elements().Count() == 1)
delete.Parent.Remove();
else
delete.Remove();
}
这样做的问题是,此循环结果的连续迭代可能会导致 NullReferenceException,因为父元素可能作为原始查询结果的另一个组中的项目存在!这当然会导致 delete.Parent 为 null,因为它之前已从 XML 树中分离出来。
我该如何解决这个问题?
更新
根据 Falanor 的建议,我尝试将代码修改为以下内容。但是,这会导致 XDocument 的最终结果仅包含根元素。我不知道为什么会这样。对此问题有什么想法或更好的解决方案吗?
HashSet<XElement> removed = new HashSet<XElement>();
foreach (var group in groups)
{
removed.UnionWith(group.items.Select(el => el.Parent).Where(el => !el.Parent.Equals(group.target)));
group.items.Remove();
foreach (var item in group.merge)
{
if (!removed.Contains(item))
group.target.Add(item);
}
}
removed.Where(el => el.Parent != null).Remove();
【问题讨论】:
-
只是一个小风格点,
g.Aggregate(...).Distinct()最好表示为g.Distinct().ToList(...) -
谢谢,我没有意识到你可以这样执行查询。这无疑使代码更简洁(可能也更高效,不是吗?)
-
再想一想……奇怪的是,当我尝试这个时,我的代码的执行时间显着增加了……大约增加了 20 倍。知道为什么它比使用像我最初在我的代码中那样聚合?
标签: c# xml linq linq-to-xml