Reputation: 15997
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?
Upvotes: 0
Views: 3096
Reputation: 160942
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++;
}
Upvotes: 1
Reputation: 13104
Another way to think about it is that you have two sequences:
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.
Upvotes: 1