【问题标题】:LINQ to Object - How to implement dynamic SELECT projection of sub elementsLINQ to Object - 如何实现子元素的动态 SELECT 投影
【发布时间】:2021-08-19 08:13:29
【问题描述】:

我有几类业务逻辑:

public class Client {
    public string Code { get; set; } = string.Empty;
    public string Status { get; set; } = string.Empty;
    public string Account { get; set; } = string.Empty;
    public Total Total { get; set; } = new Total();
    public List<Month> Months { get; set; } = new List<Month>();
}

public class Month {
    public int Number { get; set; } = 0;
    public string Name { get; set; } = string.Empty;
    public DateTime Start { get; set; } = new DateTime();
    public DateTime End { get; set; } = new DateTime();
    public Total Summary { get; set; } = new Total();
}

public class Total {
    public int Count { get; set; } = 0;
    public decimal Sum { get; set; } = 0.0m;
}

实例如下:

List<Client> clients = new List<Client>() {
    new Client {
        Code = "7002.70020604",
        Status = "Active",
        Account = "7002.915940702810005800001093",
        Total = new Total {
            Count = 9,
            Sum = 172536.45m
        },
        Months = new List<Month>() {
            new Month {
                Number = 0,
                Name = "January",
                Start = new DateTime(2021, 1, 1, 0, 0, 0),
                End = new DateTime(2021, 1, 31, 23, 59, 59),
                Summary = new Total {
                    Count = 6,
                    Sum = 17494.50m
                }
            },
            new Month {
                Number = 1,
                Name = "February",
                Start = new DateTime(2021, 2, 1, 0, 0, 0),
                End = new DateTime(2021, 2, 28, 23, 59, 59),
                Summary = new Total {
                    Count = 3,
                    Sum = 155041.95m
                }
            },
            new Month {
                Number = 2,
                Name = "March",
                Start = new DateTime(2021, 3, 1, 0, 0, 0),
                End = new DateTime(2021, 3, 31, 23, 59, 59),
                Summary = new Total {
                    Count = 0,
                    Sum = 0.0m
                }
            }   
        }
    },
    new Client {
        Code = "7002.70020604",
        Status = "Active",
        Account = "7002.800540702810205800001093",
        Total = new Total {
            Count = 4,
            Sum = 16711.21m
        },
        Months = new List<Month>() {
            new Month {
                Number = 0,
                Name = "January",
                Start = new DateTime(2021, 1, 1, 0, 0, 0),
                End = new DateTime(2021, 1, 31, 23, 59, 59),
                Summary = new Total {
                    Count = 0,
                    Sum = 0.0m
                }
            },
            new Month {
                Number = 1,
                Name = "February",
                Start = new DateTime(2021, 2, 1, 0, 0, 0),
                End = new DateTime(2021, 2, 28, 23, 59, 59),
                Summary = new Total {
                    Count = 0,
                    Sum = 0.0m
                }
            },
            new Month {
                Number = 2,
                Name = "March",
                Start = new DateTime(2021, 3, 1, 0, 0, 0),
                End = new DateTime(2021, 3, 31, 23, 59, 59),
                Summary = new Total {
                    Count = 4,
                    Sum = 16711.21m
                }
            }   
        }
    }
};

我正在尝试像这样安排视图的聚合数据:

+---------------+--------+------------------+-------------------+------------------+-------------------+
|      Code     | Status |      January     |      February     |       March      |       Total       |
|               |        +-------+----------+-------+-----------+-------+----------+-------+-----------+
|               |        | Count |    Sum   | Count |    Sum    | Count |    Sum   | Count |    Sum    |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
| 7002.70020604 | Active |   6   | 17494.50 |   3   | 155041.95 |   4   | 16711.21 |   13  | 189247.66 |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+

像这样使用投影:

clients
    .GroupBy(x => x.Code)
    .Select(y => new {
        Code = y.First().Code,
        Status = y.First().Status,
        Account = y.First().Account,
        Total = new {
            Count = y.Sum(z => z.Total.Count),
            Sum = y.Sum(z => z.Total.Sum)
        },
        Months = new {
            /*
                ?
            */
        }
    });

但我无法按月预测数据。假设日期范围(月)可以不仅仅是这个例子。请帮忙!

dotnetfiddle 上的完整互动 code listing

【问题讨论】:

    标签: c# linq aggregate projection linq-to-objects


    【解决方案1】:

    您可以使用SelectManyy 中获取月份,然后按月份分组,就像按代码分组一样:

    //...
    
    Months = y
        .SelectMany(client => client.Months)
        .GroupBy(month => month.Name, (_, months) => new {
            Number = months.First().Number,
            Name = months.First().Name,
            Start = months.First().Start,
            End = months.First().End,
            Summary = new {
                Count = months.Sum(z => z.Summary.Count),
                Sum = months.Sum(z => z.Summary.Sum)
            }
        }).ToList()
    
    //...
    

    话虽如此,我不建议在每个函数中多次使用y.First()months.First(),因为它在每次使用时都会进行枚举。一般来说,以下应该有更好的性能:

    (_, months) => {
        var month = months.First();
        return new {
            Number = month.Number,
            Name = month.Name,
            Start = month.Start,
            End = month.End,
            Summary = new {
                Count = months.Sum(z => z.Summary.Count),
                Sum = months.Sum(z => z.Summary.Sum)
            }
        }
    }
    

    这也不理想,因为我们仍在此处进行 3 个枚举(.First() 中的 1 个枚举和每个 .Sum(...) 中的 1 个枚举)。

    更好的方法是使用Aggregate 函数,它只会进行一次枚举:

    (_, months) => months
        .Aggregate((res, nextVal) => new Month {
            Number = nextVal.Number,
            Name = nextVal.Name,
            Start = nextVal.Start,
            End = nextVal.End,
            Summary = new Total {
                Count = res.Summary.Count + nextVal.Summary.Count,
                Sum = res.Summary.Sum + nextVal.Summary.Sum
            }
        })
    

    【讨论】:

    • Maxim,非常感谢您非常详细的回复。它真的帮助了我!
    • 我唯一不太明白的是如何使用(_, months) =&gt; ...。您能告诉我可以使用什么关键字来查找有关它的信息吗?
    • 这是answer by John Skeet 我的问题的答案吗?
    • 我是否正确理解这是关于Lambda discard parameters
    • GroupBy 运算符采用(key, values) =&gt; value 函数。这里我们不使用键(即月份名称),因此我们明确表示我们将忽略它。
    【解决方案2】:

    这个 LINQ 查询应该为可视化准备数据:

    clients
        .GroupBy(x => new {x.Code, x.Status})
        .Select(g => new 
        {
            Code = g.Key
            MonthsSummary = g.SelectMany(x => x.Months)
                .OrderBy(x => x.Start)
                .GroupBy(x => new {x.Start, x.Name})
                .Select(gm => new 
                {
                    gm.Key.Name,
                    Count = gm.Sum(x => x.Summary.Count),
                    Sum = gm.Sum(x => x.Summary.Sum),
                })
                .ToList()
        });
    

    【讨论】:

      猜你喜欢
      • 2021-12-16
      • 1970-01-01
      • 2014-06-08
      • 1970-01-01
      • 2015-09-12
      • 1970-01-01
      • 2011-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多