Loom.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. namespace Quadarax.Foundation.Core.Mapper
  5. {
  6. /// <summary>
  7. /// Lightweight Object-to-Object Mapper.
  8. /// </summary>
  9. public class Loom
  10. {
  11. /// <summary>
  12. /// Defines a new mapping function using a configuration-driven builder.
  13. /// </summary>
  14. /// <typeparam name="TFrom">The source type.</typeparam>
  15. /// <typeparam name="TTo">The destination type.</typeparam>
  16. /// <returns></returns>
  17. public LoomBuilder<TFrom, TTo> Define<TFrom, TTo>()
  18. where TFrom : class
  19. where TTo : class, new()
  20. {
  21. return new LoomBuilder<TFrom, TTo>(this);
  22. }
  23. /// <summary>
  24. /// Defines a new mapping function.
  25. /// </summary>
  26. /// <typeparam name="TFrom">The source type.</typeparam>
  27. /// <typeparam name="TTo">The destination type.</typeparam>
  28. /// <param name="map">The mapping function.</param>
  29. public void Define<TFrom, TTo>(Action<TFrom, TTo> map)
  30. {
  31. if (!_maps.TryGetValue(typeof(TFrom), out Dictionary<Type, Delegate>? inner))
  32. {
  33. inner = new Dictionary<Type, Delegate>();
  34. _maps[typeof(TFrom)] = inner;
  35. }
  36. inner[typeof(TTo)] = map;
  37. }
  38. /// <summary>
  39. /// Finds a mapping function.
  40. /// </summary>
  41. /// <typeparam name="TFrom">The source type.</typeparam>
  42. /// <typeparam name="TTo">The destination type.</typeparam>
  43. /// <returns>The mapping function for the given combination of types or a null reference when not defined.</returns>
  44. public Action<TFrom, TTo>? Find<TFrom, TTo>()
  45. {
  46. if (!_maps.TryGetValue(typeof(TFrom), out Dictionary<Type, Delegate>? inner)) return null;
  47. if (!inner.TryGetValue(typeof(TTo), out var map)) return null;
  48. return (Action<TFrom, TTo>) map;
  49. }
  50. /// <summary>
  51. /// Updates the destination object from the source using a mapping function.
  52. /// </summary>
  53. /// <typeparam name="TFrom">The source type.</typeparam>
  54. /// <typeparam name="TTo">The destination type.</typeparam>
  55. /// <param name="from">The source object.</param>
  56. /// <param name="to">The destination object.</param>
  57. public void Update<TFrom, TTo>(TFrom from, TTo to)
  58. where TFrom : class
  59. where TTo : class
  60. {
  61. if (from == null) throw new ArgumentNullException(nameof(from));
  62. if (to == null) throw new ArgumentNullException(nameof(to));
  63. Find<TFrom, TTo>()?.Invoke(from, to);
  64. }
  65. /// <summary>
  66. /// Creates a destination object from the source using a mapping function.
  67. /// </summary>
  68. /// <typeparam name="TFrom">The source type.</typeparam>
  69. /// <typeparam name="TTo">The destination type.</typeparam>
  70. /// <param name="from">The source object.</param>
  71. /// <returns>The destination object or a null reference in case <paramref name="from"/> is a null reference, too.</returns>
  72. public TTo? Map<TFrom, TTo>(TFrom? from)
  73. where TFrom : class
  74. where TTo : class, new()
  75. {
  76. if (from == null) return null;
  77. var to = new TTo();
  78. Find<TFrom, TTo>()?.Invoke(from, to);
  79. return to;
  80. }
  81. /// <summary>
  82. /// Creates a list of destination objects from source objects using a mapping function.
  83. /// </summary>
  84. /// <typeparam name="TFrom">The source type.</typeparam>
  85. /// <typeparam name="TTo">The destination type.</typeparam>
  86. /// <param name="from">The source objects.</param>
  87. /// <returns>The destination objects.</returns>
  88. public List<TTo>? Map<TFrom, TTo>(IEnumerable<TFrom>? from)
  89. where TFrom : class
  90. where TTo : class, new()
  91. {
  92. if (from == null) return null;
  93. var map = Find<TFrom, TTo>();
  94. return new List<TTo>(from.Select(f => {
  95. var to = new TTo();
  96. map?.Invoke(f, to);
  97. return to;
  98. }));
  99. }
  100. /// <summary>
  101. /// Creates a list of destination objects from source objects using a mapping function.
  102. /// </summary>
  103. /// <typeparam name="TFrom">The source type.</typeparam>
  104. /// <typeparam name="TTo">The destination type.</typeparam>
  105. /// <param name="from">The source objects.</param>
  106. /// <returns>The destination objects.</returns>
  107. public List<TTo>? MapList<TFrom, TTo>(IEnumerable<TFrom>? from)
  108. where TFrom : class
  109. where TTo : class, new()
  110. {
  111. if (from == null) return null;
  112. var map = Find<TFrom, TTo>();
  113. return from.Select(f => New(map, f)).ToList();
  114. }
  115. /// <summary>
  116. /// Creates a list of destination objects from source objects using a mapping function.
  117. /// </summary>
  118. /// <typeparam name="TFrom">The source type.</typeparam>
  119. /// <typeparam name="TTo">The destination type.</typeparam>
  120. /// <param name="from">The source objects.</param>
  121. /// <returns>The destination objects.</returns>
  122. public List<TTo>? MapList<TFrom, TTo>(IQueryable<TFrom>? from)
  123. where TFrom : class
  124. where TTo : class, new()
  125. {
  126. if (from == null) return null;
  127. var map = Find<TFrom, TTo>();
  128. return from.Select(f => New(map, f)).ToList();
  129. }
  130. #region ------ Internals ------------------------------------------------------------------
  131. private TTo New<TFrom, TTo>(Action<TFrom, TTo>? map, TFrom f)
  132. where TFrom : class
  133. where TTo : class, new()
  134. {
  135. var to = new TTo();
  136. map?.Invoke(f, to);
  137. return to;
  138. }
  139. /// <summary>
  140. /// Repository of all known mapping functions.
  141. /// </summary>
  142. private readonly Dictionary<Type, Dictionary<Type, Delegate>> _maps = new();
  143. #endregion
  144. }
  145. }