如何编写一个LINQ查询以反转分层数据源的分组?

4

如何编写一个LINQ查询,使其将分层源数据转换为反向分组?

假设我有一个Topic对象列表,每个对象都包含一组标记(Tags),这些标记表示该主题上的元数据标记。 我需要编写一个LINQ查询,以基本上颠倒层次结构,以便我拥有一个标记(Tag)列表,每个标记(Tag)都有一个包含使用该特定标记(Tag)进行标记的主题(Topic)集合。

Topic { Title = "Political Debate #1", Posted = 01/02/2008 }
   Tag { Name = "Contraversial", Color = "Red" }
   Tag { Name = "Politics", Color = "LightBlue" }
Topic { Title = "iPhone to support SiliverLight!", Posted = 02/23/2009 }
   Tag { Name = "BleedingEdge", Color = "LightBlue" }
   Tag { Name = "Contraversial", Color = "Red" }
   Tag { Name = ".NET", Color = "LightGreen" }
Topic { Title = "Fed Chairman admits guilt for causing second Great Depression", Posted = 06/15/2010 }
   Tag { Name = "Politics", Color = "LightBlue" }
   Tag { Name = "Contraversial", Color = "Red" }

我希望上述数据的展示效果与下面的结果类似。
Tag { Name = "Contraversial", Color = "Red" }
    Topic { Title = "Political Debate #1", Posted = 01/02/2008 }
    Topic { Title = "iPhone to support SiliverLight!", Posted = 23/02/2009 }
    Topic { Title = "Fed Chairman admits guilt for causing second Great Depression", Posted = 06/15/2010 }
Tag { Name = "Politics", Color = "LightBlue" }
    Topic { Title = "Political Debate #1", Posted = 01/02/2008 }
    Topic { Title = "Fed Chairman admits guilt for causing second Great Depression", Posted = 06/15/2010 }
Tag { Name = ".NET", Color = "LightGreen" }
    Topic { Title = "iPhone to support SiliverLight!", Posted = 23/02/2009 }

您可以假设任何重复的数据都是参考唯一的,即在内存中只有一个实例,并且这些只是对同一对象的几个引用。此外,合理的答案可以使用匿名类来生成投影,因为我意识到反转后类的形状可能会略有不同。

更新:我添加了下面的代码,设置了示例数据。我正在使用LinqPad尝试发布的答案和自己的一些想法。

var tags = new[]
{
    new { Name = "Contraversial", Color = "Red" },
    new { Name = "Politics", Color = "LightBlue" },
    new { Name = ".NET", Color = "LightGreen" },
    new { Name = "BleedingEdge", Color = "LightBlue" }

};

var topics = new[]
{
    new 
    { 
        Title = "Political Debate #1", 
        Posted = DateTime.Parse("01/02/2008"), 
        Tags = (from t in tags where new []{"Contraversial", "Politics"}.Contains(t.Name) select t),
    },
    new 
    { 
        Title = "iPhone to support SiliverLight!", 
        Posted = DateTime.Parse("02/23/2009"), 
        Tags = (from t in tags where new []{"BleedingEdge", "Contraversial", ".NET", }.Contains(t.Name) select t),
    },
    new 
    { 
        Title = "Fed Chairman admits guilt for causing second Great Depression", 
        Posted = DateTime.Parse("06/15/2010"), 
        Tags = (from t in tags where new []{"Contraversial", "Politics"}.Contains(t.Name) select t),
    },
};
3个回答

4
你需要的是一个“透视表(Pivot)”。
这个链接是关于使用LINQ透视数据的讨论。
这个来源提供了一个C#代码实现的Linq透视表扩展方法。
public static class LinqExtensions 
{

    public static Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>> Pivot<TSource, TFirstKey, TSecondKey, TValue>(this IEnumerable<TSource> source, Func<TSource, TFirstKey> firstKeySelector, Func<TSource, TSecondKey> secondKeySelector, Func<IEnumerable<TSource>, TValue> aggregate) 
    {
        var retVal = new Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>>();

        var l = source.ToLookup(firstKeySelector);
        foreach (var item in l) 
        {
            var dict = new Dictionary<TSecondKey, TValue>();
            retVal.Add(item.Key, dict);
            var subdict = item.ToLookup(secondKeySelector);
            foreach (var subitem in subdict) 
            {
                dict.Add(subitem.Key, aggregate(subitem));
            }
        }

        return retVal;
    }

}

一个枢轴可能适用于我指定的特定反转情况,但我正在寻找的是一种将任意层次结构“重新分组”为不同组织方式的方法。 - jpierson

0
IDictionary<Topic, IList<Tag>> data;
var n = data.SelectMany(x => x.Value.Select(y => new { Topic = x.Key, Tag = y }))
  .GroupBy(x => x.Tag, x => x.Topic);

这可能适用于字典示例,但我更需要一个适用于自然对象层次结构的示例。请参阅我的更新帖子,其中包含一个示例设置。 - jpierson

0

在 LinqPad 中玩了一会儿后,我认为我可能已经找到了一个合适的解决方案。

这是一个简单的例子。

var topicsByTags = 
    from topic in topics
    from tag in topic.Tags
    group topic by tag;

为了消除每个主题下多余的标签集合,我们可以执行以下操作:
var topicsByTags = 
    from topic in topics
    from tag in topic.Tags
    group new 
    {
        Title = topic.Title,
        Color = topic.Posted,
    } by tag into g
    select new
    {
        g.Key.Name,
        g.Key.Color,
        Topics = g,
    };

更新:下面是另一种利用投影中的分组本身的替代方法。优点是查询略微更清晰,缺点是即使不使用该组,组键也会与该组保持在一起。

var topicsByTags = 
    from topic in topics
    from tag in topic.Tags
    group new 
    {
        Title = topic.Title,
        Color = topic.Posted,
    } by tag into g
    select new
    {
        g.Key.Name,
        g.Key.Color,
        Topics = g,
    };

我将暂缓接受自己的答案,以允许对哪个解决方案最好地解决了我提出的问题进行一些辩论。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接