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

时间:2009-11-16 21:02:32

标签: linq linq-to-objects hierarchical-grouping

如何编写LINQ查询,该查询采用分层源数据并对其进行转换以便分组被反转?

假设我有一个Topic对象列表,每个对象都包含一组标签,这些标签代表该主题的元数据标签。我需要的是编写一个LINQ查询来基本上翻转层次结构,这样我就有了一个标签列表,每个标签都有一组标记有特定标签的主题。

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 个答案:

答案 0 :(得分:4)

您正在寻找的是 Pivot。

Is it possible to Pivot data using LINQ?

This source 包含Linq Pivot扩展方法的C#代码:

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

}

答案 1 :(得分: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);

答案 2 :(得分: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,
    };

我会推迟接受我自己的答案,以便就哪个解决方案解决我所提出的最佳问题进行辩论。