Reputation: 8966
I have a complex set of banking transaction objects which are being distilled into an array of (if needed, nullable) decimals. On the front end, I require a certain number of "transaction sums" in order to fully populate a data table. However, in some cases, there are not enough transactions to fill up the array size, which breaks the UI.
Here is part of the C# ViewModel code that constructs the array:
var minDate = this.Periods[0].FirstDayOfMonth(); // 11/1/2016
var maxDate = this.Periods[this.NumberOfPeriods - 1].LastDayOfMonth(); // 11/20/2017
this.NetCashReceipts = this.NetCashReceiptsCredits
.Where(bt => bt.Date.Between(minDate, maxDate, true))
.GroupBy(bt => bt.Date.FirstDayOfMonth())
.ToDictionary(group => group.Key, group => group.Sum(bt => bt.Amount))
.OrderBy(kvp => kvp.Key)
.Select(kvp => kvp.Value)
.ToArray();
Before the Select
statement, the ordered dictionary data looks like this:
{System.Collections.Generic.KeyValuePair<System.DateTime, decimal>[11]}
[0]: {[1/1/2017 12:00:00 AM, 27172.13]}
[1]: {[2/1/2017 12:00:00 AM, 5080.18]}
[2]: {[3/1/2017 12:00:00 AM, 63934.53]}
[3]: {[4/1/2017 12:00:00 AM, 30006.93]}
[4]: {[5/1/2017 12:00:00 AM, 64821.43]}
[5]: {[6/1/2017 12:00:00 AM, 19161.06]}
[6]: {[7/1/2017 12:00:00 AM, 130019.11]}
[7]: {[8/1/2017 12:00:00 AM, 132176.08]}
[8]: {[9/1/2017 12:00:00 AM, 72991.46]}
[9]: {[10/1/2017 12:00:00 AM, 14440.88]}
[10]: {[11/1/2017 12:00:00 AM, 18057.63]}
Notice that the earliest transaction is 1/1/2017, which is two months after 11/1/2016. All future code that touches this data will fail by skewing my tables, not to mention IndexOutOfRangeExceptions
.
I was thinking of just checking the first date and last date of the Dictionary in a while
loop and adding items to the start or end of the array (or a new array if needed) until my array is of the correct size. Is this the only way to proceed? Or is there some LINQ
magic that can help handle this? Or something more efficient?
The expected result from working code would include null
in places where there are no transactions, like this:
{System.Collections.Generic.KeyValuePair<System.DateTime, decimal>[11]}
[0]: {[11/1/2016 12:00:00 AM, null]}
[1]: {[12/1/2016 12:00:00 AM, null]}
[2]: {[1/1/2017 12:00:00 AM, 27172.13]}
[3]: {[2/1/2017 12:00:00 AM, 5080.18]}
[4]: {[3/1/2017 12:00:00 AM, 63934.53]}
[5]: {[4/1/2017 12:00:00 AM, 30006.93]}
[6]: {[5/1/2017 12:00:00 AM, 64821.43]}
[7]: {[6/1/2017 12:00:00 AM, 19161.06]}
[8]: {[7/1/2017 12:00:00 AM, 130019.11]}
[9]: {[8/1/2017 12:00:00 AM, 132176.08]}
[10]: {[9/1/2017 12:00:00 AM, 72991.46]}
[11]: {[10/1/2017 12:00:00 AM, 14440.88]}
[12]: {[11/1/2017 12:00:00 AM, 18057.63]}
*The null
values would then allow me to populate my front end data table with N/a
instead of 0
.
Upvotes: 1
Views: 74
Reputation: 32780
There is no straightforward way to do this with Linq, but Linq isn't magic, someone wrote all those extension methods. If you can't find one that fits your needs then simply implement one that does!
In this case what you need is a custom Take
that pads the output. Padding the end is always easier, so you can do the following:
public static IEnumerable<TSource> TakeAndPadEnd<TSource>(
this IEnumerable<TSource>source,
int count,
Func<TSource, TSource> paddedValueSelector)
{
var yielded = 0;
var previous = default(TSource);
using (var e = source.GetEnumerator())
{
while (e.MoveNext())
{
yield return e.Current;
previous = e.Current;
yielded += 1;
if (yielded == count)
yield break;
}
}
while (yielded < count)
{
var newValue = paddedValueSelector(previous);
yield return newValue;
previous = newValue;
yielded += 1;
}
}
And you'd use it:
this.NetCashReceipts = TakeAndPadd(
12,
this.NetCashReceiptsCredits
.Where(bt => bt.Date.Between(minDate, maxDate, true))
.GroupBy(bt => bt.Date.FirstDayOfMonth())
.ToDictionary(
group => group.Key,
group => group.Sum(bt => bt.Amount))
.OrderBy(kvp => kvp.Key)
.Select(kvp => kvp.Value),
v => default(decimal?)).ToArray();
UPDATE: Removed the tuple solution because its redundant, the solution shown above is enough; TSource
can already be anything.
Upvotes: 2
Reputation: 23
You should just do:
List<decimal?> LNetCashReceipts = NetCashReceipts.ToList<decimal?>();
and then work on that.
Upvotes: 0