2009-12-02 13 views
33

Desidero visualizzare la cronologia di un cliente in un DataGridView e voglio avere una colonna che visualizza il totale parziale per il loro saldo. Il modo in cui l'ho fatto è stato ottenere i dati, eseguire il looping dei dati e aggiungere righe allo DataGridView uno alla volta e calcolare il totale parziale in quel momento. Noioso. Preferirei usare LINQ to SQL, o LINQ se non possibile con LINQ to SQL, per calcolare i totali correnti così posso solo impostare DataGridView.DataSource sui miei dati.LINQ a SQL e un totale parziale sui risultati ordinati

Questo è un esempio semplicissimo di quello per cui sto girando. Dì che ho la seguente classe.

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

Vorrei una L2S o LINQ, dichiarazione che potrebbe generare risultati che assomigliano a questo:

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 

Si noti che non ci può essere più elementi con la stessa data (12-02-2009). I risultati dovrebbero essere ordinati per data prima del calcolo dei totali correnti. Sto indovinando questo significa che ho bisogno di due dichiarazioni, una per ottenere i dati e ordinarli e un secondo per eseguire il calcolo totale parziale.

Speravo che il Aggregate facesse il trucco, ma non funziona come speravo. O forse non riuscivo a capirlo.

Questo question sembrava cercare la stessa cosa che volevo, ma non vedo come la risposta accettata/unica risolva il mio problema.

Qualche idea su come estrarre questo?

Modifica Pettinatura le risposte da Alex e DOK, questo è ciò che ho finito con:

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

Grazie per il nuovo strumento! : RunningTotal = runningTotal + = item.Amount –

+0

Questa soluzione non impone l'esecuzione forzata sul client? (cioè deve tirare giù l'intero set di risultati per ottenere la risposta corretta?) - sembra che qualcosa del genere sarebbe molto più performante se fosse eseguita sul server SQL ... – BrainSlugs83

+1

Uso di una variabile esterna alla query è molto pericoloso! Poiché la variabile 'results' è di tipo' IEnumerable', la sua ** esecuzione sarà differita ** fino a tardi. Se cambi il valore di 'runningTotal' prima di quello, la tua query risultante non sarà più corretta. Per sicurezza, è necessario enumerarlo immediatamente (per elencare o array). Qui non vedo nulla di sbagliato nell'usare un semplice ciclo 'foreach'. – Alexey

risposta

35

Usando chiusure e metodo anonimo:

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

Vorrei poterti contraddistinguere entrambi come risposta! La tua risposta è stata facile da capire e corrisponde al mio esempio. Mi piace come DOK incrementa il 'currentTotal' in linea, durante l'assegnazione, però. – Ecyrb

+0

+1 SOF dovrebbe avere opzioni per contrassegnare più risposte. –

+4

Ho provato questo con Linq alle Entità (EF) e ottenere un "Errore di lambda con un corpo di istruzioni non può essere convertito in un albero di espressione" errore di compilazione. Questo è particolare per EF rispetto a L2O? –

25

ne dite di questo: (credito passa a 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

Grazie! Mi piace come assegni 'running_total' inline. È piuttosto lucido. – Ecyrb

+3

'running_total = (running_total = running_total + x)' => Mente bruciata. Lo ricorderò sicuramente per la prossima volta :) –

+1

cool! Funziona anche con oggetti di tipo forte, non solo con tipi anonimi – hagensoft

1

aggregato può essere utilizzato per ottenere un totale parziale così:

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

Nel caso in cui questo non è stato ancora risposto, ho una soluzione che ho utilizzato nei miei progetti. Questo è molto simile a un gruppo partizionato di Oracle. La chiave è di avere la clausola where nel totale parziale che corrisponde alla lista orig, quindi raggrupparla per data.

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

Interessante. Sembra che ciò possa scaricare correttamente i dati nel database. – 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)); 
    } 
} 
Problemi correlati