2013-03-19 86 views
0

我们需要以下逻辑形式的数据存储到一个类: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 
+0

如果您的成本价值是使用'双'的财务是一个坏主意。 “小数”更适合。 – Jodrell 2013-03-19 17:26:49

+0

“MonthCosts”字典中的Keys列表是否在来自同一组的项目之间有所不同? – MarcinJuraszek 2013-03-19 17:26:54

回答

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

+0

你是一个Linq魔术师。谢谢!你的逻辑起作用! – 2013-03-20 01:54:02

+0

使用'IDctionary'或任何其他键控或索引的集合确实允许稀疏数据。虽然最初没有在问题中指定,但这可能很有用。 +1的Linq。 – Jodrell 2013-03-20 12:13:52

-3

这听起来我喜欢你的初始数据类型应采取的形式

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

我会告诉你如何从IEnumerable(Of Decimal)和组的顺序选择。这应该说明IEnumerable可以表示有序数据,取决于实现者。首先,我将扩展​​类定义。有重要的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}) 
    } 

在这一点上,我认为accapet将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}) 
     }) 
+1

IEnumerable只是一个集合,您将失去发生特定成本的那个月的上下文。 – Rich 2013-03-19 17:33:33

+0

@这些数据将按顺序列举,否?你失去的是非规范化的模式。 – Jodrell 2013-03-19 17:34:37

+0

@Jodrell是的,但每个项目可以从不同的月份开始,并包含不同数量的项目。 – MarcinJuraszek 2013-03-19 17:35:35