使用lambda表达式按多列进行分组

166

如何使用Lambda按多列进行分组?

我看到了使用linq to entities的示例,但我正在寻找lambda形式。

5个回答

359
var query = source.GroupBy(x => new { x.Column1, x.Column2 });

这真的能行吗?我认为每个你要分组的对象的相等性测试会失败,因为它们是对象而不是结构体。 - Jacob
6
匿名类型是带有正确重写 GetHashCodeEquals 方法的不可变类。它们被设计用于这种特定的用例。 - Enigmativity
6
GroupBy返回的是一个IEnumerable<IGrouping<TKey, TSource>>类型,本质上是一个带有内部可枚举对象Key属性的IEnumerable<IEnumerable<TSource>>类型。这样描述是否有助于您理解组中项的"IEnumerable"呢? - Enigmativity
如果我的“source”变量是一个字典集合,这个方法就不起作用了。有什么建议吗? - Joao Paulo
1
“new {}” 关键字似乎是 Entity 和 C# 的“新”黄金工具,做得好! - Antoine Pelletier
显示剩余2条评论

15

我提出了一种定义类的混合方法,类似于David的回答,但不需要Where类。大致如下:

var resultsGroupings = resultsRecords.GroupBy(r => new { r.IdObj1, r.IdObj2, r.IdObj3})
                                    .Select(r => new ResultGrouping {
                                        IdObj1= r.Key.IdObj1,
                                        IdObj2= r.Key.IdObj2,
                                        IdObj3= r.Key.IdObj3,
                                        Results = r.ToArray(),
                                        Count = r.Count()
                                    });



private class ResultGrouping
        {
            public short IdObj1{ get; set; }
            public short IdObj2{ get; set; }
            public int IdObj3{ get; set; }

            public ResultCsvImport[] Results { get; set; }
            public int Count { get; set; }
        }

其中resultRecords是我要进行分组的初始列表,它是一个List<ResultCsvImport>。请注意,这里的想法是按照3列进行分组,即IdObj1、IdObj2和IdObj3。


这个无法在查询中运行,而问题是关于在查询内对多列执行GroupBy的。 - ErroneousFatality

4
如果您的表格是这样的:
rowId     col1    col2    col3    col4
 1          a       e       12       2
 2          b       f       42       5
 3          a       e       32       2
 4          b       f       44       5


var grouped = myTable.AsEnumerable().GroupBy(r=> new {pp1 =  r.Field<int>("col1"), pp2 = r.Field<int>("col2")});

16
非常重要的一点是,AsEnumerable 在对表进行分组之前会将整个表加载到内存中。这在某些表上确实很重要。请参阅此答案以获取更多见解:https://dev59.com/_GMl5IYBdhLWcg3w_bDj#17996264 - Brandon Barkley

4
     class Element
        {
            public string Company;        
            public string TypeOfInvestment;
            public decimal Worth;
        }

   class Program
    {
        static void Main(string[] args)
        {
         List<Element> elements = new List<Element>()
            {
                new Element { Company = "JPMORGAN CHASE",TypeOfInvestment = "Stocks", Worth = 96983 },
                new Element { Company = "AMER TOWER CORP",TypeOfInvestment = "Securities", Worth = 17141 },
                new Element { Company = "ORACLE CORP",TypeOfInvestment = "Assets", Worth = 59372 },
                new Element { Company = "PEPSICO INC",TypeOfInvestment = "Assets", Worth = 26516 },
                new Element { Company = "PROCTER & GAMBL",TypeOfInvestment = "Stocks", Worth = 387050 },
                new Element { Company = "QUASLCOMM INC",TypeOfInvestment = "Bonds", Worth = 196811 },
                new Element { Company = "UTD TECHS CORP",TypeOfInvestment = "Bonds", Worth = 257429 },
                new Element { Company = "WELLS FARGO-NEW",TypeOfInvestment = "Bank Account", Worth = 106600 },
                new Element { Company = "FEDEX CORP",TypeOfInvestment = "Stocks", Worth = 103955 },
                new Element { Company = "CVS CAREMARK CP",TypeOfInvestment = "Securities", Worth = 171048 },
            };

            //Group by on multiple column in LINQ (Query Method)
            var query = from e in elements
                        group e by new{e.TypeOfInvestment,e.Company} into eg
                        select new {eg.Key.TypeOfInvestment, eg.Key.Company, Points = eg.Sum(rl => rl.Worth)};



            foreach (var item in query)
            {
                Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Points.ToString());
            }


            //Group by on multiple column in LINQ (Lambda Method)
            var CompanyDetails =elements.GroupBy(s => new { s.Company, s.TypeOfInvestment})
                               .Select(g =>
                                            new
                                            {
                                                company = g.Key.Company,
                                                TypeOfInvestment = g.Key.TypeOfInvestment,            
                                                Balance = g.Sum(x => Math.Round(Convert.ToDecimal(x.Worth), 2)),
                                            }
                                      );
            foreach (var item in CompanyDetails)
            {
                Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Balance.ToString());
            }
            Console.ReadLine();

        }
    }

3

接着aduchis上面的答案 - 如果你需要根据这些分组键进行过滤,你可以定义一个类来包装这些键。

return customers.GroupBy(a => new CustomerGroupingKey(a.Country, a.Gender))
                .Where(a => a.Key.Country == "Ireland" && a.Key.Gender == "M")
                .SelectMany(a => a)
                .ToList();

CustomerGroupingKey是用于获取组键的关键字:

    private class CustomerGroupingKey
    {
        public CustomerGroupingKey(string country, string gender)
        {
            Country = country;
            Gender = gender;
        }

        public string Country { get; }

        public string Gender { get; }
    }

2
可能能为某些人节省一些时间:最好使用带有对象初始化程序的默认构造函数。上面示例代码中的方法将不会被像EF Core这样的ORM很好地处理。 - Konstantin
奇怪。我现在正在使用.NET 6。但是使用类似.GroupBy(a => new CustomerGroupingKey(a.Country, a.Gender))这样的东西不起作用。但是当我使用匿名类似.GroupBy( a => new { a.Country, a.Gender}时,分组函数就可以工作了。 - Lam Le

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