【问题标题】:How can I merge the results of a group by Linq-to-XML query?如何通过 Linq-to-XML 查询合并组的结果?
【发布时间】:2015-11-24 03:54:00
【问题描述】:

我正在尝试构建一个执行以下步骤的 Linq-to-XML 查询:

  1. 对 XDocument 中的所有后代节点进行分组
  2. 聚合每组中的不同项目
  3. 按照我选择的顺序将每个组中最后一项的父项中的元素替换为聚合项
  4. 删除每个组中的所有原始项目

到目前为止,我已经完成了使用以下代码的前两个步骤。请注意,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


【解决方案1】:

事实证明,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

这会导致查询只返回属于不同父元素的分组。只是为了让事情更清楚,我的代码目标 XML 文件返回了超过 200,000 个分组。使用附加的“where”子句,分组的数量下降到大约 150 个!最后的结果是一样的。

【讨论】:

    【解决方案2】:

    也许删除这样做的父母(以及孩子)?

    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);
    }
    }
    

    【讨论】:

    • 我试过这个,但不认为它可以工作,因为 group 是一个匿名类型的 Enumerable,所以它没有单亲。不幸的是,我也不能保证 group.items 成员中的所有元素都具有相同的父元素...
    • 考虑在事后循环并删除所有空节点?
    • 您可能对此有所了解,但是当我尝试实施您的建议时,它会导致删除除根以外的所有元素的意外后果。有关更多信息,请参阅我的更新。
    猜你喜欢
    • 1970-01-01
    • 2021-06-02
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 2013-02-24
    相关资源
    最近更新 更多