Transpose or just rows as columns

Monday, February 16, 2009 by Stefan Dobrev | Comments 12

A common scenario when displaying tabular data is to flip the axis so your rows becomes columns and vice versa. This can be easily achieved when you know the shape of your data. For example imagine we have a Customer class with 3 properties:  Name, Age and JobCode. Let’s say that we have 5 customers in our data repository. If we want to select just the names we can do something like this:

IList<Customer> customers = Customer.GetAll();

var names =

    new

        {

            Customer1 = customers[0].Name,

            Customer2 = customers[1].Name,

            Customer3 = customers[2].Name,

            Customer4 = customers[3].Name,

            Customer5 = customers[4].Name

        };

Imagine that another Customer is added and suddenly this code have to changed. Obviously this won’t scale even in a short period of time. So what is the solution?

Introducing Transpose<T> extension method

Transpose is an operation used in linear algebra to transpose (of course) a matrix. In our case Transpose is an extension method with the following signature:

public static IEnumerable Transpose<T>(this IEnumerable<T> source)

But what did Transpose do under the covers? Here are the recipe ingredients:

  • 1 tablespoon of DynamicClass.
  • 2 tablespoons of Reflection.
  • 1 cup of Expression Trees.
  • 1 slice of LINQ.
  • 2 slices of functional programming.

Here is the code itself:

public static class EnumerableExtension

{

    private static readonly MethodInfo GetValueMethod =

        (from m in typeof(PropertyInfo).GetMethods()

        where m.Name == "GetValue" && !m.IsAbstract

        select m).First();

 

    private static readonly ConstantExpression NullObjectArrayExpression =

        Expression.Constant(null, typeof(object[]));

 

    public static IEnumerable Transpose<T>(this IEnumerable<T> source)

    {

        if (source == null)

        {

            throw new ArgumentNullException("source");

        }

 

        return TransposeCore(source);

    }

 

    private static Delegate CreateSelectorFunc<T>(IEnumerable<T> source)

    {

        T[] list = source.ToArray();

        DynamicProperty[] dynamicProperties =

            list.Select(i => new DynamicProperty(i.ToString(), typeof(object))).ToArray();

 

        Type transposedType = ClassFactory.Instance.GetDynamicClass(dynamicProperties);

 

        ParameterExpression propParam = Expression.Parameter(typeof(PropertyInfo), "prop");

 

        var bindings = new MemberBinding[list.Length];

        for (int i = 0; i < list.Length; i++)

        {

            MethodCallExpression getter =

                Expression.Call(

                    propParam,

                    GetValueMethod,

                    Expression.Constant(list[i]),

                    NullObjectArrayExpression

                    );

 

            bindings[i] = Expression.Bind(transposedType.GetProperty(dynamicProperties[i].Name), getter);

        }

 

        LambdaExpression selector =

            Expression.Lambda(

                Expression.MemberInit(

                    Expression.New(transposedType),

                    bindings),

                propParam);

 

        return selector.Compile();

    }

 

    private static IEnumerable TransposeCore<T>(IEnumerable<T> source)

    {

        List<PropertyInfo> properties = typeof(T).GetProperties().ToList();

        Delegate selector = CreateSelectorFunc(source);

 

        foreach (PropertyInfo property in properties)

        {

            yield return selector.DynamicInvoke(property);

        }

    }

}

Note that some iterator best guidelines are also in place. And the final result is:

TransposedGridView

You can download the code from here

.

Have fun.

12 Comments

  • Jeffro 18 Jun
    This is awesome!  You rock.  Thanks for the post.
  • Vadim 24 Jul
    Hi, Stefan

    I did something lile that for time intervals, but deal in a different way with dynamic LINQ, and the problem was -RadGridView can bind to the data, but can not be groupable, sortable or filterable. I got an error - something like it can find the properties.

    Now I took you sample, made both RadGridView gorupable and filterable and set IsGroupable, IsSortable, IsFilterable to true for every column of GridViewTransposed grid.
    This time I got no error, but the problem remains - the columns can not be sorted or filtered (no UI elements appear), and they can not be used for grouping by droping them to grouping bar.
  • Kasim Husaini 10 Feb
    Excellent piece of code

    You saved our time!!!!
  • sam 21 Feb
    great.
  • Cliff 08 Apr
    This is a very good solution (although I am still trying to get my head around the code!).

    There is one issue related to the repeat of the "Customer 1  Customer 2...."  as the 3rd line of the newdatagrid. This is obviously a duplication of the new datagrid column headers. Is there an way to remove this duplication and just leave the "Customer 1.." etc in the column headers only?

    thanks in advance,

    Cliff
  • JAck 28 Apr
    Cliff,

    It picks up all public properties and does a row for each.  If you take out the properties you don't want they won't have a row.  Just keep the ToString( ) to get the column header.

    jack
  • Jack 07 May
    Stefan - few questions:

    1) Any way to make the grid sort/filter/group by?  If I pre-build the grid and generate real columns in lieu of Auto-Generated columns would that make a difference?

    2) Ditto for editing?  I was hoping to maybe intercept the endEdit and re-assign the values to the pre-transposed data.

    3) Anythoughts on refactoring this /w .Net 4?  Couldn't one do this with a true dynamic class and bypass much of the complexity?

    Otherwise this is a pretty slick and advanced demo.  I was able to implement a huge transpose to grid and provide my users with a number of exporting options.  The next step would be to provide the runtime sort/filter/group etc.

    Thanks

    jack
  • Sindhu 10 May
    Hi Stefan,

    When i pass th eobservable collection instance, it is not transposing.

    Kindly let me know how to transpose the collection.

  • Stefan Dobrev 18 May
    Hi Sindhu,

    Can post more info your case? It is definitely working with observable collection on my side.
  • Dave 22 Oct
    How can we put headers on the rows?
  • Lance 08 Mar
    Hi Stefan,

    I tried your example but I get a blank grid with 2 columns (one with the name of my object as a header, and the other with a blank header).  My grid had no data in it.

    My xaml RadGridView is set up as in your example code and here's my C# code:
          void ClientManagerForecastClient_GetClientManagerHistoricalForecastsCompleted(object sender, ClientManagerForecastServiceReference.GetClientManagerHistoricalForecastsCompletedEventArgs e)  
          {  
             try 
             {  
                if (e.Error == null && e.Result != null)  
                {  
    List<ClientManagerForecastServiceReference.CMForecastsHistory> clientManagerForecastsHistory = new List<ClientManagerForecastServiceReference.CMForecastsHistory>();  
                   clientManagerForecastsHistory = e.Result.ToList<ClientManagerForecastServiceReference.CMForecastsHistory>();  
     
                   if (clientManagerForecastsHistory != null && clientManagerForecastsHistory.Count > 0)  
                   {  
                       IEnumerable transposed = clientManagerForecastsHistory.Transpose();  
     
                       grdClientManagerForecastsHistory.DataContext = clientManagerForecastsHistory;// new ProxyEnumerable(transposed);  
                      grdClientManagerForecastsHistory.ItemsSource = grdClientManagerForecastsHistory.DataContext;  
     
                      IsDirty = false;  
                   }  
                }  
             }  
             catch 
             {  
             }  
             finally 
             {  
                Utils.SetIsBusy(false);  
                grdClientManagerForecastsHistory.IsBusy = false;  
             }  
          } 

    My data comes from the database via WCF as below:
    Client Executive     WeekCommencingDateForecastMade     ClientManagerForecast
    Amy Young           07/03/2011                                                 775
    Sonia Majkic         07/03/2011                                                 1850
    Sonia Majkic         21/02/2011                                                 800
    Amy Young           28/02/2011                                                 900
    Sonia Majkic         28/02/2011                                                 700

    I want it to appear in the grid as below:
    Client Executive     21/02/2011     28/02/2011     07/03/2011
    Amy Young             NULL             900                 775
    Sonia Majkic           800                 700                 1850

    As you can see I want to pivot the data and the number of columns and their headers will depend on the data.

    Is this possible?

    Please help.
  • Stefan Dobrev 08 Mar
    Hi Lance,

    Looking at your sample data I'm not sure whether you can achieve your requirements using the technique illustrated in this blog post. Have you check out our lightweight data table implementation for Silverlight introduced in this blog post? I think it is more appropriate for your scenario.

Add comment

  1. Formatting options
       
     
     
     
     
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)