如何从平面XDocument GroupJoin?

时间:2011-12-28 21:25:06

标签: c# linq-to-xml

我有一个XDocument,其片段类似于:

<Data>
    <Row Id="0" ParentId="-1">
        <!-- stuff -->
    </Row>
    <Row Id="1" ParentId="0">
        <!-- stuff -->
    </Row>
    <Row Id="2" ParentId="0">
        <!-- stuff -->
    </Row>
    <Row Id="3" ParentId="-1">
        <!-- stuff -->
    </Row>
    <Row Id="4" ParentId="3">
        <!-- stuff -->
    </Row>
</Data>

假设嵌套仅限于上面的示例。 我想创建一个数据结构 - IDictionary<Parent, List<Child>>。我似乎无法正确加入任何东西。我现在要说的是:

// get lists of data nodes
List<XElement> pRows = xData.Elements(XName.Get("Row"))
                            .Where(e => e.Attribute(XParentId).Value == "-1")
                            .Select(e => e)
                            .ToList();
List<XElement> cRows = xData.Elements(XName.Get("Row"))
                            .Where(e => e.Attribute(XParentId).Value != "-1")
                            .Select(e => e)
                            .ToList();

var dataSets = pRows.GroupJoin(cRows,
                               p => p,
                               c => c.Attribute(XParentId).Value,
                               (p, children) => new {
                                 ParentID = p.Attribute(XId).Value,
                                 Children = children.Select(c => c)
                               });

编译器抱怨:

  

方法的类型参数   “System.Linq.Enumerable.GroupJoin(System.Collections.Generic.IEnumerable,   System.Collections.Generic.IEnumerable,   System.Func,System.Func,   System.Func,TResult&GT)”   无法从使用中推断出来。尝试指定类型参数   明确。

我跟踪了MSDN using the GroupJoin的样本。我不想使用2个列表 - 我更喜欢使用包含所有行的List<XElement>的单个列表。

2 个答案:

答案 0 :(得分:1)

我认为2列表方法更简洁,但我会避免在最后一步或真正需要列表之前调用ToList()。你可以将它变成一个陈述,但这将是漫长而艰难的。

要修复您的查询,您需要将pRows的外键选择器从p => p更改为p => p.Attribute(XId).Value,这是实际ID。目前,它选择整个元素,因为它们是不同类型,所以无法与c => c.Attribute(XParentId).Value进行比较。更新的查询将是:

var dataSets = pRows.GroupJoin(cRows,
                               p => p.Attribute(XId).Value,
                               c => c.Attribute(XParentId).Value,
                               (p, children) => new {
                                 ParentID = p.Attribute(XId).Value,
                                 Children = children.Select(c => c)
                               });

为了避免使用2个列表,您可以修改上面的查询,并将pRowscRows替换为各自的查询,但这会让您的眼睛变得越来越难。在这种特殊情况下,我更喜欢使用查询语法表达GroupJoin,因为它比流利的语法更容易阅读:

var query = from root in xData.Elements("Row").Where(e => e.Attribute("ParentId").Value == "-1")
            join child in xData.Elements("Row").Where(e => e.Attribute("ParentId").Value != "-1")
            on root.Attribute("Id").Value equals child.Attribute("ParentId").Value
            into rootChild
            select new 
            {
                ParentId = root.Attribute("Id").Value,
                Children = rootChild.Select(o => o)
            };

var dict = query.ToDictionary(o => o.ParentId, o => o.Children.ToList());

如果你的真正问题有更多的嵌套,LINQ可能不是理想的解决方案。

答案 1 :(得分:0)

首先,要编译代码,您可能希望将p => p中的参数GroupJoin替换为p => p.Attribute(XId).Value,选择要进行比较的密钥。

因此,您将获得一个带有对象的IEnumerable

  • ParentID = 0,IEnumerable<XElement> {Row Id = 1,Row Id = 2}
  • ParentID = 3,IEnumerable<XElement> {Row Id = 4}

当然,您也可以更改.Select(c => c)以仅使用ID(List<string>)返回.Select(c => c.Attribute(XId).Value).ToList(),但您仍然缺少 ParentID = -1 你没有字典。

如果你想包括ParentID = -1并且还得到一个Dictionary<string,List<string>>,那么你可能想尝试这个(作为开始点),它使用不同的方法:

// get all ParentIds
var allParentIds = xData.Elements(XName.Get("Row"))
                        .Select(e => e.Attribute(XParentId).Value)
                        .Distinct();
// then use them to get all Ids where the ParentId is in the "list" of all ParentIds
var parentIdsAndChilds = from givenIds
                         in allParentIds
                         select new {
                             Id = givenIds,
                             Childs = xData.Elements(XName.Get("Row")).Where(e => e.Attribute(XParentId).Value == givenIds).Select(e => e.Attribute(XId).Value).ToList()
                         };
// if needed, convert it to a Dictionary
Dictionary<string, List<string>> dict = parentIdsAndChilds.ToDictionary(k => k.Id, v => v.Childs);

我希望这会有所帮助,或者可能为进一步研究提供一个起点。

一方面注意:您可能知道,LINQ使用延迟执行,因此您可能不希望在每一步调用ToList()以节省执行时间(取决于总数据量) ,最后使用ToList()可能是一个好主意,因为它可能会在处理过程中节省内存,但这取决于实际数据。)