I've searched the other "linq pivot" questions and I can't quite seem to find an exact match for mine. I need to do this with all anonymous types. I'm trying to track a checking transaction with data like the following
Check# - Step - Amount
100 - Book - 100
100 - Bank - 100
100 - Account - 100
101 - Book - 75
101 - Bank - 75
101 - Account - NULL
The result I'm looking for, again, as an anonymous type is:
Check # Book - Bank - Account
100 - 100 - 100- 100
101 - 75 - 75 - NULL
I really can't tell if I need to do a grouping first or not (by check#). I need it to be anonymous because I will NOT know the names of the steps as shown here. Sometimes there will 3 steps, other times there will be many more.
I've done something similar. Anonymous types wouldn't work since I had to do the columns dynamically and anonymous types still must be known at compile-time . The ExpandoObject
, however, allows us to define properties at run-time .
I've done a quick console app as proof:
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
List<Record> input = new List<Record>();
input.Add(new Record { CheckNumber = 100, Step = "Book", Amount = 100 });
input.Add(new Record { CheckNumber = 100, Step = "Bank", Amount = 100 });
input.Add(new Record { CheckNumber = 100, Step = "Account", Amount = 100 });
input.Add(new Record { CheckNumber = 101, Step = "Book", Amount = 75 });
input.Add(new Record { CheckNumber = 101, Step = "Bank", Amount = 75 });
List<ExpandoObject> results = GetPivotRows(input);
//test
for (int i = 0; i < results.Count; i++)
{
dynamic record = results[i];
Console.WriteLine("{0} - {1} - {2} - {3}", record.CheckNumber, record.Book, record.Bank, record.Account);
}
}
public static List<ExpandoObject> GetPivotRows(List<Record> input)
{
List<string> steps = input.Select(e => e.Step).Distinct().ToList();
Dictionary<int, ExpandoObject> outputMap = new Dictionary<int,ExpandoObject>();
for (int i = 0; i < input.Count; i++)
{
dynamic row;
if(outputMap.ContainsKey(input[i].CheckNumber))
{
row = outputMap[input[i].CheckNumber];
}
else
{
row = new ExpandoObject();
row.CheckNumber = input[i].CheckNumber;
outputMap.Add(input[i].CheckNumber, row);
// Here we're initializing all the possible "Step" columns
for (int j = 0; j < steps.Count; j++)
{
(row as IDictionary<string, object>)[steps[j]] = new Nullable<int>();
}
}
(row as IDictionary<string, object>)[input[i].Step] = input[i].Amount;
}
return outputMap.Values.OrderBy(e => ((dynamic)e).CheckNumber).ToList();
}
}
public class Record
{
public int CheckNumber { get; set; }
public string Step { get; set; }
public decimal Amount { get; set; }
}
Output:
100 - 100 - 100- 100
101 - 75 - 75 -
You can use reflection to check the actual properties created in the process.
EDIT: Demystifying this a little bit - if I change that "test" loop in the main to:
for (int i = 0; i < results.Count; i++)
{
Console.WriteLine(string.Join(" - ", results[i]));
}
I get:
[CheckNumber, 100] - [Book, 100] - [Bank, 100] - [Account, 100]
[CheckNumber, 101] - [Book, 75] - [Bank, 75] - [Account, ]
ExpandoObject
implements IDictionary<string, object>
behind the scenes to store whatever it needs to yet at the same time implements IDynamicMetaObjectProvider
and works with dynamic binding.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.