2013-03-19 4 views
0

Nous devons stocker les données du formulaire ci-dessous logique dans une classe:requête Linq à groupe et somme sur l'objet dictionnaire

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

J'ai donc un objet de classe, comme ci-dessous:

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

La propriété M ... contiendrait des coûts mensuels en fonction du nombre de mois écoulés dans les données source (source de données Excel). Logiquement, la classe ci-dessus contiendrait des données similaires à la forme logique ci-dessus

Maintenant, je dois regrouper les lignes en fonction des mêmes colonnes et obtenir les coûts mensuels résumés.

Pour cela, je suis en train d'utiliser le ci-dessous requête 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)} 

Cela semble fonctionner très bien, maintenant comment puis-je changer la requête Linq ci-dessus, pour la classe modifiée ci-dessous, où j'ai besoin pour stocker les coûts mensuels dans le dictionnaire et toujours obtenir des lignes groupées, avec sommation sur différentes colonnes de mois?

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

Si vos valeurs de coût sont financières en utilisant «Double» est probablement une mauvaise idée. 'Decimal' est mieux adapté. – Jodrell

+0

La liste des clés dans le dictionnaire 'MonthCosts' diffère-t-elle entre les éléments du même groupe? – MarcinJuraszek

Répondre

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) 
       } 

données de test simple:

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}}} 
} 

Et le résultat: enter image description here

+0

vous êtes un magicien Linq. Merci! votre logique fonctionne! –

+0

L'utilisation de 'IDctionary' ou de toute autre collection indexée ou indexée permet d'avoir une population clairsemée de données. Bien que non spécifié initialement dans la question, cela pourrait être utile. +1 pour le Linq. – Jodrell

-3

Il me semble que le type de données initiales devraient prendre la forme

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

Je vais vous montrer comment choisir parmi un IEnumerable(Of Decimal) et le groupe de l'ordre. Ceci devrait illustrer qu'un IEnumerable peut représenter des données ordonnées, dépendant de l'implémenteur. D'abord, je vais développer la définition de classe EntityTableRow. Il y a benefits when using immutable classes significatif, et vous devriez convenir que l'exposition directe de vos variables membres sans propriétés est considérée comme une mauvaise pratique.

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 

Vous pouvez voir que la classe n'expose que IEnumerable(Of Decimal) mais cette séquence ne représentent des données ordonnées. Maintenant, je vais montrer comment vous pouvez utiliser linq pour grouper les données de IEnumerable(Of EntityTableRow) en utilisant cette définition.

D'abord je vais créer quelques données de test.

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}) 
    } 

À ce stade, j'accapet que IList offrirait un meilleur soutien à faible densité de population des données, mais qui ne définit pas la question initiale. Les exemples commencent de façon monotone.

Il y a plusieurs façons d'effectuer le regroupement, je le diviserai en trois étapes simples que j'espère faciles à suivre. Rappelez-vous que tout cela est évalué paresseux.

Tout d'abord, aplatissez les données au niveau du mois en intégrant le numéro du mois dans la séquence.

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

Notez l'utilisation de l'indice Select extension à déduire le mois de l'ordre.

Ensuite, le groupe et la somme des coûts par activité, la description et le mois,

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) 
     }) 

Cela vous donne en fait les informations nécessaires, si elle est toujours souhaitable que vous pouvez regrouper les mois par Description et activité,

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

Un IEnumerable est juste une collection, vous perdez le contexte de quel mois un coût particulier est encouru. – Rich

+0

@Rich les données seraient énumérées dans l'ordre, non? Ce que vous perdez, c'est le schéma dénormalisé. – Jodrell

+0

@Jodrell Oui, mais chaque article peut commencer à partir d'un mois différent et contenir un nombre différent d'articles. – MarcinJuraszek