0

I have the following Linq query. transactionData is an IEnumerable.

var totalTransactions = 0;
viewModel.GroupedTransactions = transactionData
    .GroupBy(x => new { DocumentId = x.DocumentId ?? "Un Documented" })
    .Select(x => new GroupedTransaction
    {
        DocumentId = x.Key.DocumentId,
        Transactions = x.Select(y => new Transaction
        {
            Amount = y.CommitAmount,
            ActivityType = y.ActivityType,
            Number = totalTransactions++
        })
    })
    .OrderBy(x => x.DocumentId);

where I'm trying to set the Number on the Transaction record to be an incremented number. This doesn't work, leaving gaps in the numbers.

I also tried the following after the query.

foreach (var item in viewModel.GroupedTransactions.SelectMany(x => x.Transactions))
{
     item.Number = totalTransactions;
     totalTransactions++;
}

This didn't even update the Number value. What am I doing wrong, or is there a simpler way, with a neat linq extension method?

Schotime
  • 15,707
  • 10
  • 46
  • 75

2 Answers2

1

The problem is that you are closing over the variable totalTransactions, you have to create a local copy to use. Check Closing over the loop variable considered harmful for a more detailed explanation.

Something like this should work:

var totalTransactions = 0;
viewModel.GroupedTransactions = transactionData
    .GroupBy(x => new { DocumentId = x.DocumentId ?? "Un Documented" })
    .Select(x => 
    {
      new GroupedTransaction()
      {
        DocumentId = x.Key.DocumentId,
        Transactions = x.Select(y => 
        {
          var currentTransactionId = totalTransactions;
          totalTransactions++;

          return new Transaction
          {
            Amount = y.CommitAmount,
            ActivityType = y.ActivityType,
            Number = currentTransactionId 
          }
        })
      }
    })
    .OrderBy(x => x.DocumentId);

For your second approach with the foreach loop - you are actually creating a new enumeration with SelectMany() that you subsequently just throw away:

foreach (var item in viewModel.GroupedTransactions.SelectMany(x => x.Transactions))
{
     item.Number = totalTransactions;
     totalTransactions++;
}

Instead you have to force eager evaluation of your collection by using ToList() to create a collection you can safely modify.

var transactions = viewModel.GroupedTransactions
                            .SelectMany(x => x.Transactions)
                            .ToList();
foreach (var item in transactions)
{
     item.Number = totalTransactions;
     totalTransactions++;
}
BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
  • Still get the same result. Leaves gaps in the numbers depending on how many are in the group. This is really weird. – Schotime Mar 16 '11 at 01:58
  • what numbers are you seeing? I confirmed with some simplified example code that this should work – BrokenGlass Mar 16 '11 at 02:15
1

Another way to think about it is that you have two sequences:

  1. Transactions
  2. "auto incremented" index

And you want to get one sequence, transactions with ids. When we want to combine two sequences, we can use the Zip operator:

viewModel.GroupedTransactions = transactionData     
    .GroupBy(x => new { DocumentId = x.DocumentId ?? "Un Documented" })
    .Zip(Enumerable.Range(0, int.MaxValue), (x, index) => new GroupedTransaction     
    {         
        DocumentId = x.Key.DocumentId,         
        Transactions = x.Select(y => new Transaction         
        {             
            Amount = y.CommitAmount,             
            ActivityType = y.ActivityType,             
            Number = index         
        })     
    })     
    .OrderBy(x => x.DocumentId); 

Is this what you had in mind?

Zip combines two sequences until it reaches the end of one of the sequences. Thats why it is ok tu Enumberable.Range to get a much larger range of numbers than we actually need.

Chris Pitman
  • 12,990
  • 3
  • 41
  • 56
  • Sounds ok Chris, but Zip is in dotnet 4.0 I believe, of which I cannot support just yet. – Schotime Mar 16 '11 at 02:13
  • @Schotime Thats unfortunate, but it is [easy to implement in 3.0](http://stackoverflow.com/questions/2427015/how-to-do-pythons-zip-in-c/2427087#2427087) and is fairly useful to have around. – Chris Pitman Mar 16 '11 at 02:26