如何通过 Linq-to-XML 查询合并一组结果?
How can I merge the results of a group by Linq-to-XML query?
我正在尝试构建执行以下步骤的 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 时删除 group.items 中元素的父元素。 Remove() 导致父对象被清空。所以,我尝试用以下内容替换该行:
foreach (var delete in group.items)
{
if (delete.Parent.Elements().Count() == 1)
delete.Parent.Remove();
else
delete.Remove();
}
问题在于此循环结果的连续迭代可能会导致 NullReferenceException,因为父元素可能作为原始查询结果中另一组中的项目存在!这当然会导致 delete.Parent 为空,因为它之前已从 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();
也许删除 parents(以及 children)这样做?
foreach (var group in groups)
{
if(group.Parent.Elements().Count() == 1)
group.Parent.Remove();
else
group.items.Remove();
foreach (var item in group.merge)
{
group.target.Add(item);
}
}
事实证明 Falanor 的想法是正确的,我只是在编写解决方案的方式上有一个小错误导致它无法运行。对 UnionWith 的方法调用应该是:
removed.UnionWith(group.items.Select(el => el.Parent).Where(el => !el.Equals(group.target)));
请注意错误出现在 where 子句中。
此外,对于任何感兴趣的人,我意识到我可以通过将以下 'where' 子句添加到我的初始查询(就在最终 'select' 语句之前)来显着减少代码的执行时间:
where g.Select(p => p.Parent).Distinct().Count() > 1
这会导致查询仅 return 属于不同父项的元素分组。为了正确看待事情,XML 文件我针对我的代码 returned 超过 200,000 个分组。有了额外的 'where' 子句,分组的数量下降到大约 150!和最后的结果一样。
我正在尝试构建执行以下步骤的 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 时删除 group.items 中元素的父元素。 Remove() 导致父对象被清空。所以,我尝试用以下内容替换该行:
foreach (var delete in group.items)
{
if (delete.Parent.Elements().Count() == 1)
delete.Parent.Remove();
else
delete.Remove();
}
问题在于此循环结果的连续迭代可能会导致 NullReferenceException,因为父元素可能作为原始查询结果中另一组中的项目存在!这当然会导致 delete.Parent 为空,因为它之前已从 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();
也许删除 parents(以及 children)这样做?
foreach (var group in groups)
{
if(group.Parent.Elements().Count() == 1)
group.Parent.Remove();
else
group.items.Remove();
foreach (var item in group.merge)
{
group.target.Add(item);
}
}
事实证明 Falanor 的想法是正确的,我只是在编写解决方案的方式上有一个小错误导致它无法运行。对 UnionWith 的方法调用应该是:
removed.UnionWith(group.items.Select(el => el.Parent).Where(el => !el.Equals(group.target)));
请注意错误出现在 where 子句中。
此外,对于任何感兴趣的人,我意识到我可以通过将以下 'where' 子句添加到我的初始查询(就在最终 'select' 语句之前)来显着减少代码的执行时间:
where g.Select(p => p.Parent).Distinct().Count() > 1
这会导致查询仅 return 属于不同父项的元素分组。为了正确看待事情,XML 文件我针对我的代码 returned 超过 200,000 个分组。有了额外的 'where' 子句,分组的数量下降到大约 150!和最后的结果一样。