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.
Very interesting code. Any idea if you could use Expression Trees to solve challenges like the one I posted here? http://www.codeproject.com/Questions/266200/System-Func-Converter-Challenge
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.