Linq:获取具有多个嵌套组的内部对象的小计

Posted

技术标签:

【中文标题】Linq:获取具有多个嵌套组的内部对象的小计【英文标题】:Linq: Getting subtotals for the inner objects with multiple nested groups 【发布时间】:2021-01-09 18:23:17 【问题描述】:

我需要根据从 SQL 存储过程中检索到的数据集创建一些报告。这是数据的表示:

+--------------+-------------------+-----------------+--------------------+
| CustomerName | CustInventoryAcct | ProductCategory | ProductSubCategory |
+--------------+-------------------+-----------------+--------------------+
| ABC          |             12345 | Books           | Fiction            |
| ABC          |             12345 | Books           | Fiction            |
| ABC          |             12345 | Books           | Non-Fiction        |
| ABC          |             67890 | Magazines       | Sports             |
+--------------+-------------------+-----------------+--------------------+

该数据集还包括许多与实际产品相关的数据字段,为简洁起见,我省略了这些字段 - 以上是我需要分组的四个标准。

报告需要对这四个字段中的每一个进行分组,因此使用上表,输出将如下所示:

Customer Header: ABC
    Inventory Header: 12345
        Category Header: Books
            Sub Category Header: Fiction
                Detail rows for each of the items in this group (2 using the sample data above)
            Sub Category Footer: Fiction (Item Count : 2)
            Sub Category Header: Non-Fiction
                Detail row
            Sub Category Footer: Non Fiction (Item Count: 1)
        Category Footer: Books (Item Count: 3)
    Inventory Footer: 12345 (Item Count: 3)
    Inventory Header: 67890
        Category Header: Magazines
            Sub Category Header: Sports
                Detail row
            Sub Category Footer: Sports (Item Count: 1)
        Category Footer: Magazines (Item Count: 1)
    Inventory Footer: 67890 (Item Count: 1)
Customer Footer: ABC (Item Count: 4)

我有一个用于分组的 Linq 查询(不确定这是否是最好的方法):

var groupedData = rawData
    .GroupBy(x => new  x.ProductSubCategory, x.ProductCategory, x.CustInventoryAcct, x.Customer )
    .GroupBy(x => new  x.Key.ProductCategory, x.Key.CustInventoryAcct, x.Key.Customer )
    .GroupBy(x => new  x.Key.CustInventoryAcct, x.Key.Customer )
    .GroupBy(x => new  x.Key.Customer )

我使用嵌套的foreach 循环来构建报告结构:

foreach (var customer in groupedData)

    GenerateHeader(customer.Key);
    foreach (var custInventory in customer)
    
        GenerateHeader(custInventory.Key.CustInventoryAcct);
        foreach(var category in custInventory)
        
            GenerateHeader(category.Key.ProductCategory);
            foreach(var subCategory in category)
            
                GenerateHeader(subCategory.Key.ProductSubCategory);
                foreach (var item in subCategory)
                    // Populate detail rows
                // Generate footer
             
             // Generate footer
        
        // Generate footer
    
    // Generate footer    

// Generate footer

这部分工作得很好——问题是当我试图获取每个页脚的项目数时。我可以使用Count() 获得最后一个嵌套级别(子类别)的项目计数,但如果我在更高级别尝试,它可以预见地给我该级别的组数,而不是底部的基础项目等级制度。我不知道如何向下钻取,以便为每个嵌套级别创建小计。

我可以通过使用一些int 计数器来解决这个问题,但我想知道在 Linq 中是否有办法做到这一点。我遇到的困难让我想知道我的分组查询是否不是在 Linq 中处理嵌套组的正确方法。

我已经阅读了很多关于使用 GroupBy 的 SO 答案和文章,但仍然无法弄清楚 - 有人可以帮助我了解完成我想做的事情的最佳方法吗?

【问题讨论】:

【参考方案1】:

我建议不要预先做 4 个级别的 Group,而是边做边做。这样你就可以Count()每组给你一个小计:

foreach(var customer in rawData.GroupBy(x => new  x.ProductSubCategory, x.ProductCategory, x.CustInventoryAcct, x.CustomerName ))

    Console.WriteLine(customer.Key.CustomerName);
    foreach(var inventory in customer.GroupBy(x => new  x.ProductCategory, x.CustInventoryAcct, x.CustomerName ))
    
        Console.WriteLine($"\tinventory.Key.CustInventoryAcct");
        foreach(var category in inventory.GroupBy(x => new  x.CustInventoryAcct, x.CustomerName ) )
        
            // etc
                   
        Console.WriteLine($"\tNumber of items: inventory.Count()");   
    
    Console.WriteLine($"Number of items: customer.Count()");

现场示例:https://dotnetfiddle.net/YtShT3

【讨论】:

谢谢,不知道为什么我没有想到!这帮助我走上了正轨! 快速提问 - 你知道我将如何检查迭代是否在这些 foreach 循环中的最后一项上吗?我以前能够做类似if (category != inventory.Last()) 的事情,但现在我不知道如何引用我正在迭代的集合。我想我可以使用常规的 for 循环,但如果你有一个巧妙的方法来做到这一点,我很乐意听到它! @Jim 使用for 循环而不是foreach,如果循环控制变量等于Count-1,您就在最后一项。 感谢您的帮助!【参考方案2】:

尝试以下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace ConsoleApplication169

    class Program
    
        static void Main(string[] args)
        
            DataTable dt = new DataTable();
            dt.Columns.Add("CustomerName", typeof(string));
            dt.Columns.Add("CustInventoryAcct", typeof(string));
            dt.Columns.Add("ProductCategory", typeof(string));
            dt.Columns.Add("ProductSubCategory", typeof(string));

            dt.Rows.Add(new object[]  "ABC", "12345", "Books", "Fiction" );
            dt.Rows.Add(new object[]  "ABC", "12345", "Books", "Fiction" );
            dt.Rows.Add(new object[]  "ABC", "12345", "Books", "Non-Fiction" );
            dt.Rows.Add(new object[]  "ABC", "67890", "Magazines", "Sports" );

            foreach(var name in dt.AsEnumerable().GroupBy(x => x.Field<string>("CustomerName")))
            
                Console.WriteLine("Customer Header: 0", name.Key);
                foreach (var header in name.GroupBy(x => x.Field<string>("CustInventoryAcct")))
                
                    Console.WriteLine("    Inventory Header: 0", header.Key);
                    foreach (var category in name.GroupBy(x => x.Field<string>("ProductCategory")))
                    
                        Console.WriteLine("        Category Header: 0", category.Key);
                        foreach (var subCategory in name.GroupBy(x => x.Field<string>("ProductSubCategory")))
                        
                            Console.WriteLine("            Sub Category Header: 0", subCategory.Key);
                            foreach (DataRow row in subCategory)
                            
                            
                            Console.WriteLine("            Sub Category Footer: 0", subCategory.Key);
                        
                        Console.WriteLine("        Category Footer: 0", category.Key);
                    
                    Console.WriteLine("    Inventory Footer: 0", header.Key);
                
                Console.WriteLine("Customer Footer: 0", name.Key);
            
            Console.ReadLine();
        
    
  

【讨论】:

赞成,因为您有与 Jamiec 提出的相同概念 - 这个特定的答案对我不起作用,我在尝试使用 Field 语法时遇到错误......这个应用程序在 C# 6 上,这是更高版本的功能吗?感谢您的回答 Jamiec 和我的解决方案之间存在差异,我的解决方案是为 DataTable 设计的。

以上是关于Linq:获取具有多个嵌套组的内部对象的小计的主要内容,如果未能解决你的问题,请参考以下文章

linq 问题:查询嵌套集合

多个组的 jQuery 数据表总和/小计

具有多个嵌套组的 Select2

LINQ:获取包含具有特定名称和值的属性的数组中的对象

如何使用 linq 查询具有嵌套列表的对象?

在 C# 中,如何为具有多个嵌套数组的 JSON 对象建模?