tags:

views:

65

answers:

1

I recently needed to do a running total on a report. Where for each group, I order the rows and then calculate the running total based on the previous rows within the group. Aha! I thought, a perfect use case for PLINQ!

However, when I wrote up the code, I got some strange behavior. The values I was modifying showed as being modified when stepping through the debugger, but when they were accessed they were always zero.

Sample Code:

class Item
{
 public int PortfolioID;
 public int TAAccountID;
 public DateTime TradeDate;
 public decimal Shares;
 public decimal RunningTotal;
}

List<Item> itemList = new List<Item>
{
 new Item
 {
  PortfolioID = 1,
  TAAccountID = 1,
  TradeDate = new DateTime(2010, 5, 1),
  Shares = 5.335m,
 },
 new Item
 {
  PortfolioID = 1,
  TAAccountID = 1,
  TradeDate = new DateTime(2010, 5, 2),
  Shares = -2.335m,
 },
 new Item
 {
  PortfolioID = 2,
  TAAccountID = 1,
  TradeDate = new DateTime(2010, 5, 1),
  Shares = 7.335m,
 },
 new Item
 {
  PortfolioID = 2,
  TAAccountID = 1,
  TradeDate = new DateTime(2010, 5, 2),
  Shares = -3.335m,
 },

};

var found = (from i in itemList
   where i.TAAccountID == 1
   select new Item
   {
    TAAccountID = i.TAAccountID,
    PortfolioID = i.PortfolioID,
    Shares = i.Shares,
    TradeDate = i.TradeDate,
    RunningTotal = 0
   });

found.AsParallel().ForAll(x =>
{
 var prevItems =  found.Where(i => i.PortfolioID == x.PortfolioID
  && i.TAAccountID == x.TAAccountID 
  && i.TradeDate <= x.TradeDate);
 x.RunningTotal = prevItems.Sum(s => s.Shares);
});

foreach (Item i in found)
{
 Console.WriteLine("Running total: {0}", i.RunningTotal);
}

Console.ReadLine();

If I change the select for found to be .ToArray(), then it works fine and I get calculated reuslts.

Any ideas what I am doing wrong?

+4  A: 

When your PLINQ query executes, the "found" IEnumerable<T> has not be executed completely. Since LINQ to Objects, by default, uses deferred execution, each element of "Found" will not be created until the PLINQ query reaches that position.

Since the ForAll method executes using found internally, it's getting a non-executed, or only partially enumerated, sequence. By adding .ToArray() (or ToList - basically, anything that forces your LINQ to Objects query to execute) prior to your .ForAll call, you're forcing the LINQ to Objects query to execute, which allows the PLINQ query to execute properly.

Reed Copsey