Pages

Tuesday, February 9, 2010

Some fun with Lambdas, MappingObject


Mapping Object:


This probably isn't the most original piece of code that can be done with Lambda expressions but I think it has some useful applications.

I love Lambda Expressions and anonymous methods and the things that one do with them, to accomplish his goals, to make code more dynamic, clean, and more flexible.

Sometimes when working with a data centric application that's plugged to ORM (or not) that you may deal with large objects where some or most properties are assigned from other objects according to some bl rules. Manually setting such rules in code is plain boring and requires a lot of work when you are just doing a brain dead job, and if it can't be done automatically via reflection, then this kind of job is meh. fortunately there are lambda expressions that make the whole thing a little more bearable.

Our goal here is to create a simple class that will behave like a object mapper, we still need to tell what maps to what but by using lambdas we will end up in more clean and flexible and shorter piece of code (and of course we will learn how to use lambdas ;) ).

So let's see the class diagram.
This class could have existed without the interfaces but I wanted to do a fluent kind of a class and have a base interface that other mapping interfaces could extend for different behavior, like for e.g have a collection of mapping states in a MappingObject that could re map itself based on the maps that were entered.

As you can see from the diagram we have two MappingObjects the first one just accepts a T source class and then applies the mapping from other classes. The second one accepts two classes T and N so it just maps one know class to another.

So let's look at the code:
    /// <summary>
/// Defines the base mapping contract.
/// </summary>
public interface IMap
{
void Map();
}

/// <summary>
/// Defines mapping operations when base object
/// is considered to be mapped with many others.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IMap<T> : IMap
{
IMap<T> AddMapping<N>(Action<T, N> map, N obj)
where N : class;
IMap<T> AddMapping<K, N>
(Predicate<K> condition, Action<T, N> map, N obj, K cond)
where K : class
where N : class;
}

/// <summary>
/// Defines mapping operations of two objects.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="N"></typeparam>
public interface IMap<T,N> : IMap
{
IMap<T,N> AddMapping(Action<T, N> map);
IMap<T, N> AddMapping<K>
(Predicate<K> condition, Action<T, N> map, K cond)
where K : class;
}

/// <summary>
/// The Mapping object,
/// that defines how object fields are assigned.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class MappingObject<T> : IMap<T> where T : class
{
List<KeyValuePair<Delegate, object>> mapping;
private T mapObj1;
private Delegate map;

/// <summary>
/// Initializes a new instance of the 
/// <see cref="MappingObject&lt;T, N&gt;"/> class.
/// </summary>
/// <param name="obj1">The obj1.</param>
public MappingObject(T obj1)
{
this.mapObj1 = obj1;
this.mapping = new List<KeyValuePair<Delegate, object>>();
}

/// <summary>
/// Adds the mapping.
/// </summary>
/// <typeparam name="N"></typeparam>
/// <param name="map">The map.</param>
/// <param name="obj">The obj.</param>
public IMap<T> AddMapping<N>(Action<T, N> map, N obj)
where N : class
{
this.mapping.Add(new KeyValuePair<Delegate, object>
(Delegate.CreateDelegate(map.GetType(), map.Method), obj));
return this;
}

/// <summary>
/// Adds the mapping if the condition is fulfilled.
/// </summary>
/// <typeparam name="K"></typeparam>
/// <param name="condition">The condition.</param>
/// <param name="map">The map.</param>
public IMap<T> AddMapping<K, N>
(Predicate<K> condition, Action<T, N> map, N obj, K cond)
where K : class
where N : class
{
if (condition(cond))
{
this.mapping.Add(new KeyValuePair<Delegate, object>
(Delegate.CreateDelegate(map.GetType(), map.Method), obj));
}
return this;
}

/// <summary>
/// Starts Mapping operation.
/// </summary>
public void Map()
{
foreach (var target in mapping)
{
this.map = Delegate.
CreateDelegate(target.Key.GetType(), target.Key.Method);
this.map.DynamicInvoke(mapObj1, target.Value);
}

}
}

/// <summary>
/// The Mapping object, 
/// that defines the field mappings between two objects.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="N"></typeparam>
public sealed class MappingObject<T, N> : IMap<T, N>
where T : class
where N : class
{
private T mapObj1;
private N mapObj2;
private Action<T, N> map;

/// <summary>
/// Initializes a new instance of the 
/// <see cref="MappingObject&lt;T, N&gt;"/> class.
/// </summary>
/// <param name="obj1">The obj1.</param>
/// <param name="obj2">The obj2.</param>
public MappingObject(T obj1, N obj2)
{
this.mapObj1 = obj1;
this.mapObj2 = obj2;
}

/// <summary>
/// Adds the mapping.
/// </summary>
/// <param name="map">The map.</param>
public IMap<T, N> AddMapping(Action<T, N> map)
{
this.map += map;
return this;
}

/// <summary>
/// Adds the mapping if the condition is fulfilled.
/// </summary>
/// <typeparam name="K"></typeparam>
/// <param name="condition">The condition.</param>
/// <param name="map">The map.</param>
public IMap<T, N> AddMapping<K>
(Predicate<K> condition, Action<T, N> map, K cond)
where K : class
{
if (condition(cond))
{
this.map += map;
}
return this;
}

/// <summary>
/// Starts Mapping operation.
/// </summary>
public void Map()
{
this.map(mapObj1, mapObj2);
}
}
The code is simple someone that never did any lambdas or generic may find it hard to understand at first but it should be very clear.

With this code insted of saying largeObjA.PropA = largeObjB.PropB; or
if(largeObjB.PropB) { largeObjA. PropA = something } x times.

we could write:
class Program
{
static void Main(string[] args)
{
A a = new A();
B b = new B();
C c = new C();

c.z = "z";

a.i = 100;
a.s = "a";

b.x = "bx";
b.y = "1";

MappingObject<A> mo = new MappingObject<A>(a);
mo.AddMapping<C>((x, y) => x.s2 = y.z, c);
mo.AddMapping<C, B>(k => k.z == "z", (x, y) => x.s2 = y.x, b, c);
mo.Map();
}
}
classes A, B and C contain just some fields.

Now this is purely a cosmetic issue between normal assignment based on bl rules and lambdas but i prefer lambdas, and if we implement the mapping storage (via Hashtable for e.g) then things get interesting. We can store expressions and compile them later, or just delegates and store then and then load when needed, Maybe Ill expand this classes in another post and add the mapping storing features and some others as well.


No comments:

 
ranktrackr.net