LoomBuilder.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using System.Reflection;
  6. namespace Quadarax.Foundation.Core.Mapper
  7. {
  8. /// <summary>
  9. /// Mapping builder.
  10. /// </summary>
  11. /// <typeparam name="TFrom">The source type.</typeparam>
  12. /// <typeparam name="TTo">The destination type.</typeparam>
  13. public class LoomBuilder<TFrom, TTo>
  14. where TFrom : class
  15. where TTo : class, new()
  16. {
  17. private ParameterExpression sourceObj;
  18. private ParameterExpression destObj;
  19. private List<Expression> rules;
  20. private Loom loom;
  21. /// <summary>
  22. /// Initializes a new instance of this class.
  23. /// </summary>
  24. public LoomBuilder(Loom loom)
  25. {
  26. this.loom = loom;
  27. sourceObj = Expression.Variable(typeof(TFrom), "source");
  28. destObj = Expression.Variable(typeof(TTo), "dest");
  29. rules = new List<Expression>();
  30. }
  31. internal class ParameterReplacer : ExpressionVisitor
  32. {
  33. private ParameterExpression oldParam;
  34. private ParameterExpression newParam;
  35. /// <summary>
  36. /// Initializes a new instance of this class.
  37. /// </summary>
  38. public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam)
  39. {
  40. this.oldParam = oldParam;
  41. this.newParam = newParam;
  42. }
  43. public static Expression Replace(Expression expr, ParameterExpression oldParam, ParameterExpression newParam)
  44. {
  45. return new ParameterReplacer(oldParam, newParam).Visit(expr);
  46. }
  47. protected override Expression VisitParameter(ParameterExpression node)
  48. {
  49. return node.Name != null && node.Name.Equals(oldParam.Name) ? newParam : node;
  50. }
  51. }
  52. public LoomBuilder<TFrom, TTo> Custom<T>(Expression<Func<TTo, T>> destSet, Expression<Func<TFrom, T>> sourceGet)
  53. {
  54. if (sourceGet == null) throw new ArgumentNullException("sourceGet");
  55. if (destSet == null) throw new ArgumentNullException("destSet");
  56. var rule = Expression.Assign(
  57. ParameterReplacer.Replace(destSet.Body, destSet.Parameters.Single(), destObj),
  58. ParameterReplacer.Replace(sourceGet.Body, sourceGet.Parameters.Single(), sourceObj));
  59. rules.Add(rule);
  60. return this;
  61. }
  62. public LoomBuilder<TFrom, TTo> PublicProperties()
  63. {
  64. var sourceProps = typeof(TFrom).GetProperties(BindingFlags.Public | BindingFlags.Instance);
  65. var destProps = typeof(TTo).GetProperties(BindingFlags.Public | BindingFlags.Instance);
  66. foreach (var sourceProp in sourceProps.Where(p => p.CanRead && p.GetGetMethod() != null))
  67. {
  68. var destProp = destProps.FirstOrDefault(p => p.Name == sourceProp.Name && p.CanWrite && p.GetSetMethod() != null);
  69. if (destProp != null)
  70. {
  71. if (destProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
  72. {
  73. // 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
  74. rules.Add(Expression.Assign(Expression.Property(destObj, destProp), Expression.Convert(Expression.Property(sourceObj, sourceProp), destProp.PropertyType)));
  75. }
  76. else
  77. {
  78. // source may be Nullable<T>
  79. Type? sourceNullabeType = Nullable.GetUnderlyingType(sourceProp.PropertyType);
  80. if (sourceNullabeType != null && destProp.PropertyType.IsAssignableFrom(sourceNullabeType))
  81. {
  82. rules.Add(Expression.IfThen(
  83. Expression.Property(Expression.Property(sourceObj, sourceProp), "HasValue"),
  84. Expression.Assign(Expression.Property(destObj, destProp), Expression.Property(Expression.Property(sourceObj, sourceProp), "Value"))));
  85. }
  86. }
  87. }
  88. }
  89. return this;
  90. }
  91. public Func<TFrom, TTo?> Compile()
  92. {
  93. var parameters = new[] {sourceObj, destObj};
  94. var body = Expression.Block(rules);
  95. var map = Expression.Lambda<Action<TFrom, TTo>>(body, parameters).Compile();
  96. loom.Define(map);
  97. return loom.Map<TFrom, TTo>;
  98. }
  99. }
  100. }