2009-12-02 5 views
33

Je souhaite afficher l'historique comptable d'un client dans un DataGridView et je souhaite avoir une colonne affichant le total cumulé de leur solde. L'ancienne façon de procéder consistait à obtenir les données, à faire défiler les données et à ajouter des lignes au fichier DataGridView et à calculer le total cumulé à ce moment-là. Boiteux. Je préférerais utiliser LINQ to SQL, ou LINQ si ce n'est pas possible avec LINQ to SQL, pour calculer les totaux cumulés, donc je peux simplement mettre DataGridView.DataSource à mes données.LINQ to SQL et un total cumulé sur les résultats commandés

Ceci est un exemple super simplifié de ce que je photographie. Dites que j'ai la classe suivante.

class Item 
{ 
    public DateTime Date { get; set; } 
    public decimal Amount { get; set; } 
    public decimal RunningTotal { get; set; } 
} 

Je voudrais un L2S ou LINQ, déclaration qui pourrait générer des résultats qui ressemblent à ceci:

Date  Amount RunningTotal 
12-01-2009  5   5 
12-02-2009  -5   0 
12-02-2009  10   10 
12-03-2009  5   15 
12-04-2009 -15   0 

avis qu'il peut y avoir plusieurs éléments avec la même date (12-02-2009). Les résultats doivent être triés par date avant le calcul des totaux cumulés. Je suppose que cela signifie que j'ai besoin de deux instructions, l'une pour obtenir les données et le trier et une seconde pour effectuer le calcul du total cumulé.

J'espérais que Aggregate ferait l'affaire, mais cela ne fonctionne pas comme j'espérais. Ou peut-être que je ne pouvais tout simplement pas comprendre.

Ce question semblait aller après la même chose que je voulais, mais je ne vois pas comment la réponse acceptée/seule résout mon problème.

Des idées pour réussir?

Modifier peigner les réponses de Alex et DOK, voilà ce que j'ai fini avec:

decimal runningTotal = 0; 
var results = FetchDataFromDatabase() 
    .OrderBy(item => item.Date) 
    .Select(item => new Item 
    { 
     Amount = item.Amount, 
     Date = item.Date, 
     RunningTotal = runningTotal += item.Amount 
    }); 
+0

Merci pour le nouvel outil! : RunningTotal = runningTotal + = item.Amount –

+0

Cette solution ne forcera-t-elle pas l'exécution à être sur le client? (c'est-à-dire qu'il doit dérouler tout le jeu de résultats pour obtenir la bonne réponse?) - il semble que quelque chose comme cela serait beaucoup plus performant s'il était fait sur le serveur SQL ... – BrainSlugs83

+1

Utilisation d'une variable externe à la requête est très dangereux! Parce que la variable 'results' est de type' IEnumerable', son exécution ** sera différée ** jusqu'à plus tard. Si vous changez la valeur de 'runningTotal' avant cela, votre requête résultante ne sera plus correcte. Pour être sûr, vous devez l'énumérer immédiatement (à la liste ou tableau). Je ne vois rien de mal ici à l'aide d'une simple boucle foreach. – Alexey

Répondre

35

utilisant des fermetures et méthode anonyme:

List<Item> myList = FetchDataFromDatabase(); 

decimal currentTotal = 0; 
var query = myList 
       .OrderBy(i => i.Date) 
       .Select(i => 
          { 
          currentTotal += i.Amount; 
          return new { 
              Date = i.Date, 
              Amount = i.Amount, 
              RunningTotal = currentTotal 
             }; 
          } 
        ); 
foreach (var item in query) 
{ 
    //do with item 
} 
+1

Je voudrais pouvoir vous marquer tous les deux comme la réponse! Votre réponse était facile à comprendre et correspond à mon exemple. J'aime bien comment DOK incrémente le «currentTotal» en ligne, pendant l'affectation, cependant. – Ecyrb

+0

+1 SOF devrait avoir des options pour marquer plusieurs réponses. –

+4

J'ai essayé ceci avec Linq to Entities (EF) et obtenir une erreur de compilation "Une expression lambda avec un corps d'instruction ne peut pas être convertie en arbre d'expression". Est-ce particulier à EF par opposition à L2O? –

25

Que diriez-vous ceci: (crédit passe à this source)

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

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     delegate string CreateGroupingDelegate(int i); 

     static void Main(string[] args) 
     { 
      List<int> list = new List<int>() { 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 69, 2007}; 
      int running_total = 0; 

      var result_set = 
       from x in list 
       select new 
       { 
        num = x, 
        running_total = (running_total = running_total + x) 
       }; 

      foreach (var v in result_set) 
      { 
       Console.WriteLine("list element: {0}, total so far: {1}", 
        v.num, 
        v.running_total); 
      } 

      Console.ReadLine(); 
     } 
    } 
} 
+0

Merci! J'aime comment assigner 'running_total' inline. C'est plutôt chic. – Ecyrb

+3

'running_total = (running_total = running_total + x)' => Esprit grillé. Je m'en souviendrai sûrement pour la prochaine fois :) –

+1

cool! Il fonctionne également avec des objets de type fortement, et pas seulement des types anonymes. – hagensoft

1

agrégat peut être utilisé pour obtenir un total en cours d'exécution ainsi:

var src = new [] { 1, 4, 3, 2 }; 
var running = src.Aggregate(new List<int>(), (a, i) => { 
    a.Add(a.Count == 0 ? i : a.Last() + i); 
    return a; 
}); 
5

Dans le cas où cela n'a pas été encore répondu, j'ai une solution que je sers dans mes projets. C'est assez similaire à un groupe partitionné Oracle. La clé est que la clause where du total cumulé corresponde à la liste orig, puis la regroupe par date.

var itemList = GetItemsFromDBYadaYadaYada(); 

var withRuningTotals = from i in itemList  
         select i.Date, i.Amount,  
           Runningtotal = itemList.Where(x=> x.Date == i.Date). 
                 GroupBy(x=> x.Date). 
                 Select(DateGroup=> DateGroup.Sum(x=> x.Amount)).Single(); 
+0

Intéressant: On dirait que cela déchargerait correctement les données dans la base de données. – BrainSlugs83

0
using System; 
using System.Linq; 
using System.Collections.Generic; 

public class Program 
{ 
    public static void Main() 
    { 
     var list = new List<int>{1, 5, 4, 6, 8, 11, 3, 12}; 

     int running_total = 0; 

     list.ForEach(x=> Console.WriteLine(running_total = x+running_total)); 
    } 
}