Expression Trees: Part 4 – Simple mapping inversion


This is part of a series of blog post about expression trees in .NET 4.0.  The past blog entries are:

In this article, I’ll cover simple mapping inversion using Expression Trees (ET).  By simple I mean that we’ll map two objects properties / fields directly:  there won’t be any complex properties.

Let’s say we have the following two classes:

public class Individual
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public decimal Salary { get; set; }
}

public class PersonTable
{
    public string FIRST_NAME { get; set; }
    public string LAST_NAME { get; set; }
    public DateTime DATE_OF_BIRTH { get; set; }
    public decimal SALARY { get; set; }
}

We can easily map one class to the other:

Expression<Func<PersonTable, Individual>> map = p => new Individual
{
    FirstName = p.FIRST_NAME,
    LastName = p.LAST_NAME,
    DateOfBirth = p.DATE_OF_BIRTH,
    Salary = p.SALARY
};

This expression maps a PersonTable object to an Individual object.  What we would like to do is revert this mapping in order to get a mapping from Individual object to an PersonTable object.

The first thing we would like to do is to extract the different field / property maps:

private static IEnumerable<Tuple<MemberInfo, MemberInfo>>
    ExtractMemberMaps<S, D>(Expression<Func<S, D>> map)
{    //    Validate the map isn’t a null reference
    if (map == null)
    {
        throw new ArgumentNullException("map");
    }

    var initExp = map.Body as MemberInitExpression;

    //    Validate the body of the expression constist in a
    //    member initialization with a default (no arguments) constructor
    if (initExp == null || (initExp.NewExpression.Arguments.Count != 0))
    {
        throw new ApplicationException("Not a member init expression with empty constructor");
    }

    var mappingPairs =
        from binding in initExp.Bindings.OfType<MemberAssignment>()
        let memberAssignment = binding.Expression as MemberExpression
        //    Make sure we have an assignment
        where memberAssignment != null
            //    Make sure we assign to a property or a field
        && (memberAssignment.Member.MemberType == MemberTypes.Property
        || memberAssignment.Member.MemberType == MemberTypes.Field)
        select new Tuple<MemberInfo, MemberInfo>(memberAssignment.Member, binding.Member);

    //    Materialize the enumeration so that it doesn’t get recomputed many times
    return mappingPairs.ToList();
}

We can then check for each property in the source object where it maps to:

Expression<Func<PersonTable, Individual>> map = p => new Individual
{
    FirstName = p.FIRST_NAME,
    LastName = p.LAST_NAME,
    DateOfBirth = p.DATE_OF_BIRTH,
    Salary = p.SALARY
};

var sourceToDestinationMaps = ExtractMemberMaps(map);

Console.WriteLine(MapMember<PersonTable, string>(sourceToDestinationMaps, p => p.FIRST_NAME));
Console.WriteLine(MapMember<PersonTable, string>(sourceToDestinationMaps, p => p.LAST_NAME));
Console.WriteLine(MapMember<PersonTable, DateTime>(sourceToDestinationMaps, p => p.DATE_OF_BIRTH));
Console.WriteLine(MapMember<PersonTable, decimal>(sourceToDestinationMaps, p => p.SALARY));

This outputs:

  • System.String FirstName
  • System.String LastName
  • System.DateTime DateOfBirth
  • System.Decimal Salary

So we do have individual member element maps.  We now have to revert those maps and validate there are no duplicates:

private static IEnumerable<Tuple<MemberInfo, MemberInfo>>
    InvertMaps(IEnumerable<Tuple<MemberInfo, MemberInfo>> maps)
{    //    Inverts
    var invertedMaps =
        (from map in maps
         select new Tuple<MemberInfo, MemberInfo>(map.Item2, map.Item1)).ToList();

    //    Validate uniqueness of mapping
    var count = (from map in invertedMaps
                 select map.Item1).Distinct().Count();

    if (invertedMaps.Count != count)
    {
        throw new ApplicationException(string.Format(
            "There are {0} duplicate of destinations",
            invertedMaps.Count – count));
    }

    return invertedMaps;
}

The only way we could fail the validation is if we had some destination members mapping to the same source member, for instance:

Expression<Func<PersonTable, Individual>> map = p => new Individual
{
    FirstName = p.FIRST_NAME,
    LastName = p.FIRST_NAME
};

Of course that type of mapping would be invalid for inversion since we wouldn’t be able to determine how to map from p.FIRST_NAME.

We can now revert an entire mapping by simply constructing the inverse map:

private static Expression<Func<D, S>> InvertMapping<S, D>(Expression<Func<S, D>> map)
{    //    Fetch the maps of each field / property
    var sourceToDestMaps = ExtractMemberMaps(map);
    //    Invert the maps
    var destToSourceMaps = InvertMaps(sourceToDestMaps);
    //    Creates a variable for the destination type
    var destVar = Expression.Variable(typeof(D), "d");
    //    New the source type, i.e. new S()
    var newSource = Expression.New(typeof(S));
    //    List the bindings i.e. p1 = m.P1
    var bindings = from m in destToSourceMaps
                   let destinationMember = m.Item1
                   let sourceMember = m.Item2
                   let assignation = Expression.MakeMemberAccess(destVar, destinationMember)
                   select Expression.Bind(sourceMember, assignation);
    //    Init expression, i.e. new S{p1=m.P1,p2=m.P2}
    var initSource = Expression.MemberInit(newSource, bindings);
    //    Creates the lambda expression, i.e. m => new S{p1=m.P1,p2=m.P2}
    var initLambda = Expression.Lambda<Func<D, S>>(initSource, destVar);

    return initLambda;
}

Putting it all together:

Expression<Func<PersonTable, Individual>> map = p => new Individual
{
    FirstName = p.FIRST_NAME,
    LastName = p.LAST_NAME,
    DateOfBirth = p.DATE_OF_BIRTH,
    Salary = p.SALARY
};
var invertMap = InvertMapping(map);
var mapDel = map.Compile();
var invertMapDel = invertMap.Compile();
var person = new PersonTable
{
    FIRST_NAME = "John",
    LAST_NAME = "Doe",
    DATE_OF_BIRTH = DateTime.Now.Subtract(TimeSpan.FromDays(100000)),
    SALARY = 25000
};
//    Map from PersonTable to Individual
var mapped = mapDel(person);
//    Map from Individual back to PersonTable
var unmapped = invertMapDel(mapped);

Console.WriteLine(invertMap);

Here we successfully mapped the object from PersonTable to Individual back to PersonTable representation.

The outputted invert map is:

d => new PersonTable()

{
  FIRST_NAME = d.FirstName,
  LAST_NAME = d.LastName,
  DATE_OF_BIRTH = d.DateOfBirth,
  SALARY = d.Salary
}

We therefore have something pretty powerful here:  we enter a mapping expression and get the functioning reverse mapping.  This gets quite some of the declarative strength of expression trees.

4 thoughts on “Expression Trees: Part 4 – Simple mapping inversion

  1. Pingback: Expression Trees: Part 4 – Simple mapping inversion | All kinds of ….solutions

    1. Vincent-Philippe Lauzon Post author

      For sure. As long as your code can ‘predict’ what the ID property name will be (e.g. Class-Name + ‘ID’), then you could craft an expression tree to do conversions and compile it.

      Reply
  2. Pingback: Expression Trees Blog Series « Vincent-Philippe Lauzon's blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s