Linq查询对字典对象进行分组和求和

时间:2013-03-19 15:47:32

标签: c# .net vb.net linq

我们需要将以下逻辑形式的数据存储到类中:

"description 1", "activity A", "service A", Month 1 cost, month 2 cost, month 3 cost etc....

所以我有一个类对象,如下所示:

Public Class EntityTableRow
    Public Description As String
    Public Activity As String
    Public M1 as Double
    Public M2 as Double
    .....
End Class

M ...属性将保留每月费用,具体取决于源数据中有多少个月(Excel数据源)。从逻辑上讲,上述类将保存类似于上述逻辑形式的数据

现在我需要根据相同的列对行进行分组,并总结月份成本。

为此,我尝试使用下面的Linq查询:

Dim a As New List(Of EntityTableRow) 
        a = myTable1.TableRows 
        Dim lFinal2 = From el In a Group el By Key = New With {Key el.Description, Key el.Activity} Into Group _ 
                      Select New With {.Activity = Key.Description, _ 
                                       .Country = Key.Activity, _ 
                                       .M1 = Group.Sum(Function(x) x.M1), _ 
                                       .M2 = Group.Sum(Function(x) x.M2)} 

这似乎工作正常,现在如何更改上面的Linq查询,对于下面修改过的类,我需要在字典中存储月份成本并仍然获得分组行,并在不同月份列上求和?

Public Class EntityTableRow
        Public Description As String
        Public Activity As String
        Public MonthCosts As New Dictionary(Of Integer, Double)
    End Class

2 个答案:

答案 0 :(得分:1)

Dim lFinal2 = From el In a
              Group el By Key = New With {Key el.Description, Key el.Activity} Into Group
              Select New With {
                  .Activity = Key.Description,
                  .Country = Key.Activity,
                  .MonthCost =
                      (From k In Group.SelectMany(Function(g) g.MonthCosts.Keys).Distinct()
                       Select New With {
                            .Month = k,
                            .Sum = Group.Sum(Function(g) If(g.MonthCosts.ContainsKey(k), g.MonthCosts(k), 0))
                       }).ToDictionary(Function(i) i.Month, Function(i) i.Sum)
              }

简单的测试数据:

Dim a As New List(Of EntityTableRow) From {
    New EntityTableRow With {.Activity = "A", .Description = "D", .MonthCosts = New Dictionary(Of Integer, Double) From {{1, 20}, {2, 20}, {3, 20}}},
    New EntityTableRow With {.Activity = "A", .Description = "D", .MonthCosts = New Dictionary(Of Integer, Double) From {{2, 20}, {3, 20}, {4, 20}}}
}

结果: enter image description here

答案 1 :(得分:-3)

听起来我的初始数据类型应采用

形式
Public Class EntityTableRow
    Public Description As String
    Public Activity As String
    Public MonthCosts As IEnumerable(Of Decimal)
End Class

我将向您展示如何从订单上的IEnumerable(Of Decimal)和组中进行选择。这应该说明IEnumerable可以表示有序数据,具体取决于实现者。首先,我将扩展EntityTableRow类定义。有很大的benefits when using immutable classes,您应该同意直接暴露您的会员变量没有属性被认为是不好的做法。

Public Class EntityTableRow

    Private ReadOnly _descritption As String
    Private ReadOnly _activity As String
    Private ReadOnly _monthCosts As IList(Of Decimal)

    Public Sub New( _
            ByVal description As String,
            ByVal activity As String, _
            ByVal monthCosts As IEnumerable(Of Decimal))
        Me._description = description
        Me._activity = activity
        Me._monthCosts = New List(Of Decimal)(monthCosts)
    End Sub

    Public ReadOnly Property Description() As String
        Get
            Return Me._description
        End Get
    End Property

    Public ReadOnly Property Activity() As String
        Get
            Return Me._activity
        End Get
    End Property

    Public ReadOnly Property MonthCosts() As IEnumerable(Of Decimal)
        Get
            Return Me._monthCosts
        End Get
    End Property
End Class

您可以看到该类仅公开IEnumerable(Of Decimal),但此序列确实表示有序数据。现在,我将展示如何使用linq使用此定义对来自IEnumerable(Of EntityTableRow)的数据进行分组。

首先,我将创建一些测试数据。

Dim testData As IEnumerable(Of EntityTableRow) = _
    {
        New EntityTableRow("A", "B", New Decimal() {20, 20, 20}),
        New EntityTableRow("A", "B", New Decimal() {Nothing, 20, 20, 20}),
        New EntityTableRow("C", "D", New Decimal() {10, 20, Nothing, 40}),
        New EntityTableRow("C", "D", New Decimal() {50, 60})
    }

此时,我认为IList会为稀疏的数据填充提供更好的支持,但原始问题中没有定义。这些例子从一个单调开始。

有几种方法可以执行分组,我将它分成三个简单的步骤,我将很容易遵循。记住,这都是懒惰的评估。

首先,将数据展平为月份级别,并将月份编号合并到序列中。

 Dim allCosts = testData.SelectMany( _
     Function(r) r.MonthCosts.Select( _
         Function(c, i) New With {r.Descriptionr, r.Activity, .Month = i, .Cost = c}))

请注意使用索引Select扩展名来推断订单中的月份。

接下来,按活动,描述和月份

对成本进行分组和汇总
Dim groupedCosts = allCosts.GroupBy( _
    Function(r) New With {r.Activity, r.Description, r.Month},
    Function(k, s) New With
        {
            k.Description,
            k.Activity,
            k.Month,
            .TotalCost = s.Sum(Function(r) r.Cost)
        })

这实际上为您提供了所需的信息,如果仍然需要,您可以按描述和活动重新组合月份,

Dim groupedDescriptionActivities = groupedCosts.GroupBy( _
    Function(r) New With {r.Description, r.Activity}, _
    Function(k, s) New With
        {
            k.Description,
            k.Activity,
            .MonthCosts = s.Select(Function(r) New With {r.Month, r.TotalCost})
        })