简体   繁体   中英

Pivot with LINQ and anonymous type?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM