Thursday, June 18, 2009

Strongly typed mapping

Often there is a need to map some properties on of class to properties of another class or columns in DB. There might be few ways to do that. The most popular ways are via code or via xml files. While working on my sharepoint-lists-to-objects mapper I had to create some way to map class properties to sharepoint fields and decided to go with code implementation. So I'm going to talk how to do that via code file in strongly typed manner.

Lets say I have a class
public class MyClass
{
public int MyProperty{get;set;}
}

and I want to map property in strogly-typed manner. So instead of typing
MapField("MyProperty", "ColumnName");

I want to have
MapField(x=>x.MyProperty, "ColumnName");

This gives a nice opportunity to refactor, as when renaming MyProperty to something else you will rename it in the mapping as well (though R# can do that for you as it can search literals with property name when renaming a property).

So we need to determine what's the name of property we're passing and also might be interested in it's type.
MapField method should have this signature to work
public void MapField(Expression<Func<MyClass, object>> expression, string columnName)
{
//get field/property name and type from expression
}


Now we have to parse that linq expression. To get required info I've wrote a class with following interface
public interface ILinqExpressionReader<T>
{
string GetFieldOrPropertyNameFromBodyOfExpression(Expression<Func<T, object>> expression);
Type GetFieldTypeFromBodyOfExpression(Expression<Func<T, object>> expression);
}


and here is the implementation:
public class LinqExpressionReader<T> : ILinqExpressionReader<T>
{
public string GetFieldOrPropertyNameFromBodyOfExpression(Expression<Func<T, object>> expression)
{
var memberInfo = GetMemberInfoFrom(expression);
return memberInfo.Name;
}

private static MemberInfo GetMemberInfoFrom(Expression<Func<T, object>> expression)
{
MemberInfo member;
if (expression.Body.GetType() == typeof(UnaryExpression))
{
var unaryExpression = (UnaryExpression)expression.Body;
if (IsMemberExpression(unaryExpression.Operand))
{
member = ((MemberExpression)unaryExpression.Operand).Member;
}
else { throw new ArgumentOutOfRangeException("expression", "Should use the following syntax: x => x.PropertyName (or x => x.FieldName)"); }
}
else if (IsMemberExpression(expression.Body))
{
member = ((MemberExpression)expression.Body).Member;
}
else { throw new ArgumentOutOfRangeException("expression", "Should use the following syntax: x => x.PropertyName"); }
return member;
}
private static bool IsMemberExpression(Expression expression)
{
return expression.GetType() == typeof(MemberExpression);
}

public Type GetFieldTypeFromBodyOfExpression(Expression<Func<T, object>> expression)
{
var memberInfo = GetMemberInfoFrom(expression);
switch (memberInfo.MemberType)
{
case MemberTypes.Property:
return ((PropertyInfo)memberInfo).PropertyType;
case MemberTypes.Field:
return ((FieldInfo)memberInfo).FieldType;
default:
throw new Exception("Cannot get types for anything but Properties and Fields");
}
}
}


Implementation uses reflection to get info that is required. Also works fine with properties or fields of Enum type.

No comments:

Post a Comment