39

I want to display a customer's accounting history in a DataGridView and I want to have a column that displays the running total for their balance. The old way I did this was by getting the data, looping through the data, and adding rows to the DataGridView one-by-one and calculating the running total at that time. Lame. I would much rather use LINQ to SQL, or LINQ if not possible with LINQ to SQL, to figure out the running totals so I can just set DataGridView.DataSource to my data.

This is a super-simplified example of what I'm shooting for. Say I have the following class.

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

I would like a L2S, or LINQ, statement that could generate results that look like this:

   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

Notice that there can be multiple items with the same date (12-02-2009). The results should be sorted by date before the running totals are calculated. I'm guessing this means I'll need two statements, one to get the data and sort it and a second to perform the running total calculation.

I was hoping Aggregate would do the trick, but it doesn't work like I was hoping. Or maybe I just couldn't figure it out.

This question seemed to be going after the same thing I wanted, but I don't see how the accepted/only answer solves my problem.

Any ideas on how to pull this off?

Edit Combing the answers from Alex and DOK, this is what I ended up with:

decimal runningTotal = 0;
var results = FetchDataFromDatabase()
    .OrderBy(item => item.Date)
    .Select(item => new Item
    {
        Amount = item.Amount,
        Date = item.Date,
        RunningTotal = runningTotal += item.Amount
    });
Community
  • 1
  • 1
Ecyrb
  • 1,950
  • 2
  • 25
  • 43
  • Thanks for the new tool! : RunningTotal = runningTotal += item.Amount – Rick Love Mar 17 '15 at 19:39
  • Won't this solution force execution to be on the client? (i.e. it has to pull down the whole result set to get the correct answer?) -- it seems like something like this would be much more performant if it were done on the SQL server... – BrainSlugs83 Jun 25 '15 at 21:14
  • 2
    Using a variable external to the query is very dangerous! Because `results` variable is of `IEnumerable` type, its **execution will be deferred** till later. If you change the value of `runningTotal` before that, your resulting query won't be correct anymore. To be safe you need to enumerate it immediately (to list or array). I don't see anything wrong here with using a simple `foreach` loop. – Alexey Jun 30 '15 at 05:13
  • EXACTLY the problem I was trying to solve. Thanks for asking this six years ago! – Casey Crookston Jan 11 '17 at 16:47

6 Answers6

40

Using closures and anonymous method:

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
}
Alex Bagnolini
  • 20,446
  • 3
  • 39
  • 40
  • 1
    I wish I could mark you both as the answer! Your answer was easy to understand and matches my example. I do like how DOK increments the `currentTotal` inline, during the assignment, though. – Ecyrb Dec 02 '09 at 19:16
  • 6
    I've tried this with Linq to Entities (EF) and get a "A lambda expression with a statement body cannot be converted to an expression tree" compile error. Is that particular to EF as opposed to L2O? – Neil Barnwell Feb 18 '11 at 14:40
  • 1
    Note that this ends up being wrong if you use the query result repeatedly. [This question is a result of this.](http://stackoverflow.com/questions/25037055/linq-running-total-1st-value-added-to-itself/25037928#25037928) You can solve this by adding a `.ToList()` to the end of the query. – ahruss Jul 30 '14 at 14:22
  • 1
    This solution will force execution to be on the client (i.e. it has to pull down the whole result set to get the correct answer) -- it seems like something like this would be much more performant if it were done on the SQL server... – BrainSlugs83 Jun 25 '15 at 21:15
  • @NeilBarnwell it's telling you that the only way this works is if you pull the whole dataset down to the client first (note he's storing the dataset in a list and not in an IQueryable or IEnumerable). There has to be a better way. – BrainSlugs83 Jun 25 '15 at 21:16
27

How about this: (credit goes to 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();
        }
    }
}
DOK
  • 31,405
  • 7
  • 58
  • 91
  • Thanks! I like how you assign `running_total` inline. That's pretty slick. – Ecyrb Dec 02 '09 at 19:17
  • 5
    `running_total = (running_total = running_total + x)` => Mind blown. I will surely remember this for the next time :) – Alex Bagnolini Dec 02 '09 at 21:52
  • 1
    cool! It works with strongly type objects as well, not just anonymous types – hagensoft Jul 15 '13 at 17:53
  • 2
    Note that this ends up being wrong if you use the query result repeatedly. [This question is a result of this.](http://stackoverflow.com/questions/25037055/linq-running-total-1st-value-added-to-itself/25037928#25037928) You can solve this by adding a `.ToList()` to the end of the query. – ahruss Jul 30 '14 at 14:22
  • 1
    This is another client-side only solution (that requires enumerating the whole dataset on the client -- this will suck for large datasets). :( – BrainSlugs83 Jun 25 '15 at 21:17
7

In case this hasn't been answered yet, I have a solution that I have been using in my projects. This is pretty similar to an Oracle partitioned group. The key is to have the where clause in the running total match the orig list, then group it by 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();
DF5
  • 13
  • 2
DF5
  • 71
  • 1
  • 1
2

Aggregate can be used to obtain a running total as well:

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;
});
Aaron Anodide
  • 16,154
  • 14
  • 59
  • 117
  • But how does this apply the running total to the original collection? The OP asks for a way to append this to each item. – Chris Peacock Dec 18 '20 at 17:46
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));
    }
}
daydreamer
  • 33
  • 6
0

Most of the other answers to this, which properly set the running totals within the objects, rely on a side-effect variable, which is not in the spirit of functional coding and the likes of .Aggregate(). This solution eliminates the side-effect variable.

(NB - This solution will run on the client as with other answers, and so may not be optimal for what you require.)

var results = FetchDataFromDatabase()
    .OrderBy(item => item.Date)
    .Aggregate(new List<Item>(), (list, i) =>
        {
            var item = new Item
            {
                Amount = i.Amount,
                Date = i.Date,
                RunningTotal = i.Amount + (list.LastOrDefault()?.RunningTotal ?? 0)
            };

            return list.Append(item).ToList();
            // Or, possibly more efficient:
            // list.Add(item);
            // return list;
        });
Chris Peacock
  • 3,011
  • 1
  • 21
  • 20