using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Quadarax.Foundation.Core.Mapper { /// /// Mapping builder. /// /// The source type. /// The destination type. public class LoomBuilder where TFrom : class where TTo : class, new() { private ParameterExpression sourceObj; private ParameterExpression destObj; private List rules; private Loom loom; /// /// Initializes a new instance of this class. /// public LoomBuilder(Loom loom) { this.loom = loom; sourceObj = Expression.Variable(typeof(TFrom), "source"); destObj = Expression.Variable(typeof(TTo), "dest"); rules = new List(); } internal class ParameterReplacer : ExpressionVisitor { private ParameterExpression oldParam; private ParameterExpression newParam; /// /// Initializes a new instance of this class. /// public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam) { this.oldParam = oldParam; this.newParam = newParam; } public static Expression Replace(Expression expr, ParameterExpression oldParam, ParameterExpression newParam) { return new ParameterReplacer(oldParam, newParam).Visit(expr); } protected override Expression VisitParameter(ParameterExpression node) { return node.Name != null && node.Name.Equals(oldParam.Name) ? newParam : node; } } public LoomBuilder Custom(Expression> destSet, Expression> sourceGet) { if (sourceGet == null) throw new ArgumentNullException("sourceGet"); if (destSet == null) throw new ArgumentNullException("destSet"); var rule = Expression.Assign( ParameterReplacer.Replace(destSet.Body, destSet.Parameters.Single(), destObj), ParameterReplacer.Replace(sourceGet.Body, sourceGet.Parameters.Single(), sourceObj)); rules.Add(rule); return this; } public LoomBuilder PublicProperties() { var sourceProps = typeof(TFrom).GetProperties(BindingFlags.Public | BindingFlags.Instance); var destProps = typeof(TTo).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var sourceProp in sourceProps.Where(p => p.CanRead && p.GetGetMethod() != null)) { var destProp = destProps.FirstOrDefault(p => p.Name == sourceProp.Name && p.CanWrite && p.GetSetMethod() != null); if (destProp != null) { if (destProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType)) { // convert the source property's value to the destination type: required to assign into a Nullable<> destination property, and won't hurt in other cases rules.Add(Expression.Assign(Expression.Property(destObj, destProp), Expression.Convert(Expression.Property(sourceObj, sourceProp), destProp.PropertyType))); } else { // source may be Nullable Type? sourceNullabeType = Nullable.GetUnderlyingType(sourceProp.PropertyType); if (sourceNullabeType != null && destProp.PropertyType.IsAssignableFrom(sourceNullabeType)) { rules.Add(Expression.IfThen( Expression.Property(Expression.Property(sourceObj, sourceProp), "HasValue"), Expression.Assign(Expression.Property(destObj, destProp), Expression.Property(Expression.Property(sourceObj, sourceProp), "Value")))); } } } } return this; } public Func Compile() { var parameters = new[] {sourceObj, destObj}; var body = Expression.Block(rules); var map = Expression.Lambda>(body, parameters).Compile(); loom.Define(map); return loom.Map; } } }