Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C#

Lambda Converter Pattern

Rate me:
Please Sign up or sign in to vote.
4.20/5 (4 votes)
13 Mar 2009CPOL3 min read 31.5K   143   20   2
The article attempts to explain a neat coding pattern for converting an IEnumerable to a DataTable.

Introduction

.NET 3.5 has many new cool features. One of them being lambda expressions, which can cleanup a lot of code, and really push higher-order functions to a new level.

Background

Lambda Expression in 3.5

Basically, lambda expressions provide a way to anonymously implement delegates using Type Inferencing.

C#
ublic void performOnAllMembers(Action<Foo> action) {
  foreach( Foo foo in datastructure ) {
    action(foo);
  }
}

// Old syntax
performOnAllMembers(delegate(Foo foo) {
  foo.bar();
});

// New Lambda Syntax
performOnAllMembers( foo => foo.bar() );

Lambda expressions are very common in functional programming languages such as Scheme, ML, F# etc., and is a fundamental programming concept. I am actually a bit surprised it took 7 years for Microsoft to adopt it.

The article will try to apply lambda expressions to implement a converter from an underlying data source (IEnumerable<T>) and apply a list of transformations based on a List of Mappings that we call (Transformer<F,T>). In this example, the final data source is a DataTable.

The scenario

One way to convert a IEnumerable data source such as a Business Object Collection is to manually create the data table, add all the necessary columns and data types, then look through the data source and manually provide a mapping of the data to column rows. So for example, you might have something like this...

C#
... 
// get employees from some where
List<Employee> emps = getEmployees();

DataTable dt = new DataTable("Employee");
dt.Columns.Add(New DataColumn("FirstName", typeof(string)));
dt.Columns.Add(New DataColumn("Address Info", typeof(string)));
dt.Columns.Add(New DataColumn("Salary", typeof(double)));
dt.Columns.Add(New DataColumn("Performance", typeof(double)));
dt.Columns.Add(New DataColumn("Cool", typeof(bool)));

foreach(Employee e in )
{
    DataRow dr = dt.NewRow(); 
    dr["FirstName"] = e.Contact.FirstName;
    dr["Address Info"] = e.Address;
    dr["Performance"] = getPerformance(e.ID);

    if(e.FirstName.Equals("Yang"))
        dr["Cool"] = true;
    else
        dr["Cool"] = false;

    dt.Rows.Add(dr);
}

return dt; // do bindings or whatever
...

This approach will get the job done; however, for every data conversion you might have from a complex business object with lots of columns, you might pull some hair out writing this kind of code over and over again.

Another approach is to create a single converter<F,T> that converts a type F such as Employee into a type T such as a DataRow. However, you will still be writing a lot of hardcoded column and result mappings.

The solution

You guessed it! Here is where lambda expressions are used. Similar to a PropertyPath in a WPF binding property, we can create a lambda expression that describes the transformation of a object of type F to type T.

For example:

C#
dr["FirstName"] = e.Contact.FirstName; 

is applied on an object e of Employee, and returns a string which is what the column is expecting (f:Employee->String). Similarly, the other column mappings can all be described as a functional transformation from an Employee.

C#
// declare an delegate signature
delegate string EmployeeToString(Employee emp);
// define an delegate that implements 
var FirstNameTransformer = e => { return e.Contact.FirstName; } 

//More annonymously...

Func<Employee,string> 
  LastNameTransformer = (e => { return e.Contact.LastName; });

Similarly, each column mapping can be represented by a transformation. This means that for each column, we need a transformation function Func<T,F>. In the case of a property, we already have such a function, so we do not need to create a new one.

C#
...
// the following code will create an delegate from
// an property specicly the PropertyName property, 
// of return type T of a F type object and use its getmethod
// to create a wrapper delegate pointer.
private Func<F,T> GetPropertyDelegate<F,T> (string PropertyName)
{
   return (Func<F, T>)Delegate.CreateDelegate(typeof(Func<F, T>), 
                    typeof(F).GetProperty(PropertyName, typeof(T)).GetGetMethod());
}

// one might get the Address property get delegate of an Employee object like so:
   return GetPropertyDelegate<Employee,string> ("Address");
...

Putting it together

We can use an interface to store a Mapping Transformation which contains the source data type F as a generic type and then a transform function that will basically transform that type to a desired type.

C#
/// <summary>
///  Contains a transformer that takes one value of F type and converts it to another value
/// </summary>
/// <typeparam name="F">The Type to convert from</typeparam>
public interface ITransformer<F>
{
    Type DataType { get; }
    string Caption { get; }

    /// this property can be derived into a subclass
    DataColumn AsColumn { get; }

    /// <summary>
    /// get the actual object using the lambda function
    /// </summary>
    /// <param name="source">the object to apply the transformation</param>
    /// <returns>return the result of the transformation function</returns>
    object transform(F value);
}

Now, let's look at an implementation of this:

C#
/// <summary>
/// Transforms a value of F type to T type using a transformation delegate.
/// </summary>
/// <typeparam name="F">The type of the source object</typeparam>
/// <typeparam name="T">The type to transform to</typeparam>
public class Transformer<F, T> : ITransformer<F>
{
    private readonly string _caption;
    private readonly Func<F, T> _tranfer;
    private DataColumn _column;

    #region properties

    /// <summary>
    /// data type of transformed value
    /// </summary>
    public Type DataType
    {
        get
        {
            return typeof(T);
        }
    }

    /// <summary>
    /// what this transformer describes
    /// </summary>
    public string Caption
    {
        get
        {
            return this._caption;
        }
    }

    /// <summary>
    /// convert to a data column
    /// </summary>
    public DataColumn AsColumn
    {
        get
        {
            if (this._column == null)
                this._column = new DataColumn(this.Caption, this.DataType);
            return this._column;
        }
    }
    #endregion

    #region constructors
    /// <summary>
    /// create a transformer with name and using a propertyname as the function
    /// </summary>
    /// <param name="Caption">The name of this transformation</param>
    /// <param name="PropertyName">The property name
    /// of the source object to use as the delegate</param>
    public Transformer(string Caption, string PropertyName)
        : this(Caption,
            (Func<F, T>)Delegate.CreateDelegate(typeof(Func<F, T>),
            typeof(F).GetProperty(PropertyName, typeof(T)).GetGetMethod()))
    {
    }

    /// <summary>
    /// create a transformer with a column name, the caption, and the covertor function
    /// </summary>
    /// <param name="Caption"></param>
    /// <param name="transfer"></param>
    public Transformer(string Caption, Func<F, T> transfer)
    {
        this._caption = Caption;
        this._tranfer = transfer;
    }
    #endregion

    /// <summary>
    /// get the actual object using the lambda function
    /// </summary>
    /// <param name="source">the object to apply the transformation</param>
    /// <returns>return the result of the transformation function</returns>
    public object transform(F source)
    {
        return this._tranfer.Invoke(source);
    }
    
}

/// <summary>
/// Used to convert a IEnumerable<T> into an DataTable
/// </summary>
/// <typeparam name="T"></typeparam>
public class DataTableMapper<T>
{

    /// <summary>
    /// Convert a List of objects into a DataTable using a mapping
    /// </summary>
    /// <typeparam name="T">The type of object to be converted</typeparam>
    /// <param name="list">The IEnumerable
    /// data source to be used to convert</param>
    /// <param name="Mappings">The mapping
    /// Transformers on the source objects</param>
    /// <returns>A data table that contains the same number of rows
    /// as the data source and containing the mapped items</returns>
    public static DataTable ConvertToDataTable(IEnumerable<T> list,
        ITransformer<T>[] Mappings)
    {
        // pre:
        if (Mappings == null || list == null) return null;

        DataTable dt = new DataTable();
        dt.BeginLoadData();
        // create the columns found in the chris grid
        Type tType = typeof(T);

        // create data columns
        foreach (ITransformer<T> m in Mappings)
            dt.Columns.Add(m.AsColumn);

        // convert to datarows
        foreach (T beo in list)
        {
            DataRow dr = dt.NewRow();
            foreach (ITransformer<T> m in Mappings)
                // invoke the delegate in the transformer on the item in the list
                dr[m.Caption] = m.transform(beo);
            dt.Rows.Add(dr);
        }

        dt.EndLoadData();
        return dt;
    }
}

How to use

To use this, we simply declare an ITransformer<T> mapping array which is used to build our data table.

Each ITransformer maps to a new DataColumn in the table.

C#
...
// get employees from some where
List<Employee> emps = getEmployees();

var mappings = new ITransformer<Employee>[] {
    new Transformer<Employee, String> ("First Name", e => { return e.Contact.FirstName; }),
    // uses a property delegate
    new Transformer<Employee, String> ("Address Info", "Address"),
    new Transformer<Employee, double> ("Performance", e => { getPerformance(e.ID); }),
    new Transformer<Employee, bool> ("Cool", e => { return e.FirstName.Equals("Yang"); }),

// we then create the table by calling the mapping helper
DataTable dt = DataTableMapper<Employee>.ConvertToDataTable(emps, mappings);
return dt;
...

I hope you guys enjoyed this article. Please submit your feedback and comments.

Points of interest

This is just a start of using lambda to extract higher-order functions. We can potentially incorporate some LINQ Expression into the lambda expression for even more powerful joins and queries.

The only performance impact of this code is creating the lambda function; it does use Reflection to retrieve the property method in one constructor type. However, this is a tiny impact.

History

  • March 13, 2009 - First draft created!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Canada Canada
Engineer, maker, food lover

Comments and Discussions

 
GeneralCool technique! Pin
cpousset18-Mar-09 9:54
cpousset18-Mar-09 9:54 
GeneralRe: Cool technique! Pin
Yang Yu23-Mar-09 5:26
Yang Yu23-Mar-09 5:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.