【问题标题】:How to flatten a list using linq c#如何使用 linq c# 展平列表
【发布时间】:2016-11-14 12:05:10
【问题描述】:

我的场景如下:

我有以下数据 -

ID、名称、类型、位置、GroupID

1, samename, Rock, New York, 12
2, samename, Jazz, Sydney, 12
3, samename, Rock, Sydney, 12
4, samename, Jazz, New York, 12    
5, name3, Opera House, Sydney, 14
6, name3, Opera House, London, 14
7, name2, Emirates, London, 13

我想根据下面的 GroupID 将其展平

ID、姓名、地点、位置、GroupID

1, samename, {Rock,Jazz}, {New York,Sydney}, 12
5, name3, Opera House, {Sydney,London}, 14
7, name2, Emirates, London, 13

这是我继承的非常糟糕的设计 - 我正在努力使它变得更好......而不破坏旧代码。

我相信答案与 SelectMany 有关 - 但我无法确定语法 - 我尝试了几种不同的方法。

我尝试的解决方案 - 没有扁平化..

var q3 = Data.Where(b=>b.GroupID != null).GroupBy(x=> new { x.GroupID }, (key, group) => new 
{ 
  GroupID = key.GroupID,  
  Result =  group.Select(g=> new 
                         {                            
                           Type = g.Type, 
                           Location = g.Location,                                                  
                         }).ToList()
});

【问题讨论】:

  • 你正试图做与扁平化相反的事情。
  • 做了什么?在任何情况下,您发布的内容都不是“扁平化”,您是按名称分组然后应用各种聚合,例如 ID 的最小值,位置、位置的扁平化,GroupID 的最小值
  • 一个简单的循环与字典名称可能是最简单和最清晰的方法
  • @ClaudioRedi 不是真的。你所说的“用字典循环”产生的正是GroupBygroup by。然后呢?
  • 列表 {Rock,Jazz} 是否链接到 {New York,Sydney}?它将如何反映摇滚 - 纽约的存在,但如果有伦敦 - 爵士,但没有伦敦 - 摇滚 - 将如何表示,或者这两个列表只是简单的列表,它们之间没有联系?

标签: c# .net entity-framework linq linq-to-sql


【解决方案1】:

试试这个:

var answer = data.GroupBy(x => x.GroupID).Select(x => new { 
     ID = x.Min(y => y.ID),
     Name = x.Select(y => y.Name).ToList(),
     Type = x.Select(y => y.Type).ToList(),
     Location = x.Select(y => y.Location).ToList(),
     GroupID = x.Key
}).ToList();

【讨论】:

  • x.Min 是做什么的?
  • 它为您提供组的最小 ID,即分别为 1、5 和 7,您也可以通过x.First().ID获取第一项的 ID
【解决方案2】:

试试这个:

 var q = Data.Where(b => b.GroupID != null).GroupBy((x) => x.GroupID).Select((y) => new
                {
                    GroupID = y.First().ID,
                    Name = string.Join(",", y.Select((k) => k.Name).Distinct()),
                    Type = string.Join(",", y.Select(( k) => k.Type).Distinct()),
                    Location = string.Join(",", y.Select(( k) => k.Location).Distinct()),
                });

如果要动态加载列,请使用以下代码:

        var q2 = Data.Where(b => b.GroupID != null).GroupBy((x) => x.GroupID).Select((y) => new
        {
            GroupID = y.First().ID,
            result = DynamicGroup(y)
        });

    private static string DynamicGroup(IGrouping<string,DataD> y)
    {
        string result=null;
        foreach (System.Reflection.PropertyInfo pInfo in typeof(DataD).GetProperties().Where(x=>x.Name!="GroupID" && x.Name!="ID"))
        {
            result += string.Format(" {0} ; ",string.Join(",", y.Select((k) => pInfo.GetValue(k, null)).Distinct()));
        }
        return result;
    }

【讨论】:

  • 一个非常简洁的答案。这很棒! string.Join 有效 :) - 我的表实际上有 10 个其他列 - 我怎样才能将其余列添加到 q - 而不必命名每一列?
  • 是的,有反射。
  • 嗨 - 这有点尴尬 - 但我正在使用 linq 到实体,我收到以下错误。 {"LINQ to Entities 无法识别方法 'System.String Join(System.String, System.Collections.Generic.IEnumerable`1[System.String])' 方法,并且此方法无法转换为存储表达式。" } - 我正在使用 linqpad 和 linqtosql 进行测试,这对我来说有点傻。
【解决方案3】:

如果您的元素序列包含一系列内部元素,并且您希望将所有内部元素序列作为一个序列访问,则使用 SelectMany。 SelectMany 类似如下代码:

List<InnerClass> result = new List<InncerClass>()
foreach (var outerElement in outerSequence)
{
    foreach (InnerClass innerElement in outerSequence.InnerSequence)
    {
        // note that InnserSequence is an IEnummerable in every InnerElement
        result.Add(innerElement);
    }
}

您希望相反:您希望将集合中的多个元素组合成一个新序列。源代码中使用的所有名称都应生成一个包含多个字段的元素:

  • Name 是所有同名元素的名称
  • Id 是该名称所有元素中最低的 Id
  • Place 是您的序列中使用此名称(摇滚、爵士、酋长国)的所有类型的序列(有点不吉利的名字)
  • Lo​​cation 是具有 Name 的所有 Locations 的序列
  • GroupId 是具有 Name 的项目的 GroupId

在您的示例中,名称对应于 GroupId。您确定不能有两个具有“同名”和不同 GroupId 的元素吗?

第一步是使用 GroupBy 对所有同名的元素进行分组:

var result1 = sourceCollection.GroupBy(sourceElement => sourceElement.Name);

现在您有一个 IGrouping 项的集合,每个 IGrouping 项都是具有相同名称的源元素序列,每个 IGrouping 项都有一个包含此相互名称的 Key 属性。

第二步,将每组中的所有元素转换为Id序列、Place序列和GroupId序列:

var result2 = result1.Select(group => new
{
    Name = group.Key,
    AllIds = group.Select(groupElement => groupElement.Id),
    Places = group.Select(groupElement => groupElement.Place),
    Locations = group.Select(groupElement => groupElement.Location),
    GroupIds = group.Selelect(groupElement => groupElement.GroupId),
};

"从result1中的每个组中,新建一个对象,其属性为Name,其中包含组的Key(这是所有groupElements中的通用名称)。这个创建的对象还有一个属性Places,它来自每个Place 组中的元素。每个创建对象还有一个属性 Locations,即...等"

现在您要做的就是从 AllIds 中获取最低值,并从 Groupids 中获取唯一值:

var result3 = result2.Select(item => new
{
    Name = item.Name,
    LowestId = item.AllIds.Orderby(id => id).First(),
    Places = item.Places,
    Locations = item.Locations,
    OneAndOnlyGroupId = item.GroupId.First(),
};

如果您不确定所有具有相同名称的元素是否具有相同的 GroupId,请考虑按new {Name = sourceElement.Name, GrouId = sourceElement.GroupId) 进行分组,以创建具有相同{Name, GroupId} 的组,或者让您的最终OneAndOnlyGroupId 成为一个序列,如果GroupIds

【讨论】:

  • 感谢您的回答 - 我也喜欢详尽的解释。但是我不得不将另一个标记为 anser。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-07
  • 1970-01-01
  • 2015-03-19
  • 2012-01-19
  • 2018-12-21
  • 1970-01-01
相关资源
最近更新 更多