I recently had to pivot data that I had in a list of POCO objects. I had to do this for a couple of different object types and it just felt wrong to have to recode something so generic over and over again. So... I figured I'd write a little helper class that utilizes reflection and allows me to pivot on any list of POCO objects, and specify what I want to pivot on (X and Y columns) and what I should be summing. This is all done using System.Collections.Generic and System.Reflection so it may not be the ideal solution from a performance perspective for huge datasets. On the other hand, for most datasets and modern hardware it isn't a problem at all, and it is really flexible and very highly reuseable for cranking out reports quickly.
Code Snippet
- public class PivotResult
- {
-
- public String[] ColumnNames;
- public String[] RowNames;
- public float[,] ValuesTable;
- public int Count;
- }
- public class ReflectionPivot
- {
- public PivotResult Pivot(List<Object> ObjectList, String ColumnProperty, String RowProperty, String ValueProperty)
- {
- if (ObjectList == null)
- return null;
- if (ObjectList.Count == 0)
- return null;
- Object first = ObjectList[0];
- Type type = first.GetType();
- PropertyInfo[] properties = type.GetProperties();
- PropertyInfo colPropInfo = null;
- PropertyInfo rowPropInfo = null;
- PropertyInfo valPropInfo = null;
- foreach (PropertyInfo pInfo in properties)
- {
- if (pInfo.Name.Equals(ColumnProperty))
- colPropInfo = pInfo;
- if (pInfo.Name.Equals(RowProperty))
- rowPropInfo = pInfo;
- if (pInfo.Name.Equals(ValueProperty))
- valPropInfo = pInfo;
- }
- if (colPropInfo == null)
- {
- throw new ArgumentException("Column Property invalid.");
- }
- if (rowPropInfo == null)
- {
- throw new ArgumentException("Row Property invalid.");
- }
- if (valPropInfo == null)
- {
- if (!(ValueProperty.Equals("1")))
- {
- throw new ArgumentException("Value Property invalid.");
- }
- }
- List<String> lstColNames = new List<string>();
- List<String> lstRowNames = new List<string>();
- foreach (Object obj in ObjectList)
- {
- string strCol = colPropInfo.GetValue(obj, null).ToString();
- string strRow = rowPropInfo.GetValue(obj, null).ToString();
- if (!(lstColNames.Contains(strCol)))
- {
- lstColNames.Add(strCol);
- }
- if (!(lstRowNames.Contains(strRow)))
- {
- lstRowNames.Add(strRow);
- }
- }
- PivotResult pr = new PivotResult();
- pr.Count = 0;
- pr.ValuesTable = new float[lstRowNames.Count, lstColNames.Count];
- pr.RowNames = lstRowNames.ToArray();
- pr.ColumnNames = lstColNames.ToArray();
- foreach (Object obj in ObjectList)
- {
- float fVal = 0;
- if (ValueProperty.Equals("1"))
- {
- fVal = 1;
- }
- else
- {
- fVal = (float)valPropInfo.GetValue(obj, null);
- }
- pr.Count++;
- string strCol = colPropInfo.GetValue(obj, null).ToString();
- string strRow = rowPropInfo.GetValue(obj, null).ToString();
- int intColName = lstColNames.FindIndex(x => x == strCol);
- int intRowName = lstRowNames.FindIndex(x => x == strRow);
- pr.ValuesTable[intRowName, intColName] += fVal;
- }
- return pr;
- }
- }
And you would use it something like this:
ReflectionPivot reflectionPivot = new ReflectionPivot();
PivotResult pivotResult = reflectionPivot.Pivot(intermediate.ToList<Object>(), "Team", "Grade", "1");
PivotResult pivotResult2 = reflectionPivot.Pivot(lstSales, "Division", "State", "Sales");
And then you can simply pass the PivotResult instance to a MVC view and use it to display a graph or to just show in a table list like this:
Code Snippet
- <%@ Page Title="Sales Report" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<PivotResult>" %>
- <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
-
- <script src="<%: Url.Content("~/Scripts/visualize.jQuery.js") %>" type="text/javascript"></script>
- <script type="text/javascript">
- //<![CDATA[
- $(function () {
- $(".jqvisualizesimple").visualize();
- });
- });
- //]]>
- </script>
-
- <h2>Sales Report</h2>
-
- <table class="jqvisualizesimple">
- <thead>
- <td> </td>
- <% foreach(var strCol in Model.ColumnNames) { %>
- <th scope="col"><%: strCol %></th>
- <% } %>
- </thead>
- <tbody>
- <% for (int i = 0; i < Model.ValuesTable.GetLength(0); i++)
- {
- var strRow = Model.RowNames[i]; %>
- <tr><th scope="row"><%: strRow %></th>
- <%
- for (int j = 0; j < Model.ValuesTable.GetLength(1); j++)
- { %>
- <td><%:Model.ValuesTable[i, j]%></td>
- <% }
- }%>
- </tr>
- </tbody>
- </table>
- </asp:Content>
I was really hoping to do this using pure LINQ, but it turned out to be the much more difficult route so I ended up backing off and using reflection instead. If someone has seen something like this done in LINQ, please do send me a message or a comment and point towards that solution as I would very much enjoy seeing it and learning from it.