0

In my controller I am returning a db query result (list of objects) defined by my model. I'm displaying this in the view. For example:

prop1 prop2 prop3 prop4 prop5
AAA 1123 400 35% $600
BBB 3444 23 45% $235
CCC 5000 55 15% $555
DDD 2500 264 70% $243

I would like to pivot this result to look like below:

prop1 AAA BBB CCC DDD
prop2 1123 3444 5000 2500
prop3 400 23 55 264
prop4 35% 45% 15% 70%
prop5 $600 $235 $555 $243

What's the best way to achieve this? I had initially pivoted in the SQL query and had a model representing the bottom result , however it became difficult to manage as I had frequent changes and requests to add new fields calculated from other columns etc.

4
  • Take a look at this post for possible solution: stackoverflow.com/questions/167304/… Commented Sep 27, 2022 at 1:04
  • If you ready to pay there are controls which can do that. For example DevExpress ASP.NET MVC Pivot Grid. You just simply bind to plain IQueryable (without PIVOT) and grid takes care which queries to execute. Commented Sep 27, 2022 at 6:09
  • 1
    @Adam - Based on you example it looks like your attempting to do a matrix rotation common in data science tasks - without any grouping. Let me know if this is what you're looking for or if you want the first column in the pivot to be the header row. Commented Sep 27, 2022 at 19:26
  • Thank you everyone for your responses! @AdamCohen - I was looking to rotate the data (rows to columns essentially, no grouping). Or yes, the first column as headers. I was actually able to work with your pivot extension solution below to get the data displayed properly. Appreciate your input very much Commented Sep 27, 2022 at 19:41

1 Answer 1

1

See below for an example implementation extension method below that uses reflection to pivot an IEnumerable<T>.

Example Usage Pivot()

// Test Type
public record MyObj(string Prop1, int Prop2, int Prop3);

// test data
var list = new List<MyObj> {
        new MyObj("AAA", 1123, 400),
        new MyObj("BBB", 2123, 500),
        new MyObj("CCC", 3123, 600) 
    };

Console.Write(list.Pivot());

Output

Example Usage Pivot() w/ ColumnSelector

Example 1

Console.Write(list.Pivot( pivotOn: (f)=>f.Prop1));

enter image description here

Example 2

Console.Write(list.Pivot( pivotOn: (f)=>f.Prop2));

enter image description here

Implementation

public static class PivotExtension
{
    public static IEnumerable<ExpandoObject> Pivot<T>(this IEnumerable<T> objs)
    {
        var objArray = objs.ToArray();
        var properties = typeof(T).GetProperties();
        foreach (var property in properties)
        {
            var obj = new ExpandoObject();
            var objDic = (IDictionary<string, object>)obj;
            objDic.Add("Name", property.Name);
            for (int i = 1; i < objs.Count(); i++)
                objDic.Add($"Row_{i.ToString()}", property.GetValue(objArray[i]));
                
            yield return obj;
        }
    }
    public static IEnumerable<ExpandoObject> Pivot<T, TValue>(this IEnumerable<T> objs, Expression<Func<T, TValue>> pivotOn)
    {
        var objArray = objs.ToList();
        var properties = typeof(T).GetProperties();
        var pivotProperty = pivotOn.GetProperty();
        
        //header
        var cnt = 1;
        var pivotHeader = new Dictionary<int, string>{{0, pivotProperty.Name}};
        objArray.ForEach(r=> pivotHeader.Add( cnt++, pivotProperty.GetValue(r).ToString()));
        
        //rotate
        foreach (var property in properties.Where(r=> !pivotHeader.ContainsValue(r.Name)))
        {
            var obj = new ExpandoObject();
            var objDic = (IDictionary<string, object>)obj;
            objDic.Add(pivotHeader[0], property.Name);
            for (int i = 0; i < objs.Count(); i++)
                objDic.Add($"{pivotHeader[i+1]}", property.GetValue(objArray[i]));

            yield return obj;
        }
    }
    
    public static PropertyInfo GetProperty<T, TValue>(this Expression<Func<T, TValue>> expression)
    {
        return GetProperty(expression.Body);
    }
    
    private static PropertyInfo GetProperty(Expression body)
    {
        MemberExpression memberExpression;
        switch (body.NodeType)
        {
            case ExpressionType.Convert:
                memberExpression = (MemberExpression)((UnaryExpression)body).Operand;
                break;
            case ExpressionType.MemberAccess:
                memberExpression = (MemberExpression)body;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        return (PropertyInfo)memberExpression.Member;
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! This got me very close to a final solution.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.