DynamicQueryable.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. // ReSharper disable MemberCanBePrivate.Global
  7. namespace FormulaEnginePoC.DynamicExpressionParse
  8. {
  9. /// <summary>
  10. /// Microsoft provided class. It allows dynamic string based querying.
  11. /// Very handy when, at compile time, you don't know the type of queries that will be generated.
  12. /// </summary>
  13. internal static class DynamicQueryable
  14. {
  15. #region IQueryable Extensions
  16. public static IQueryable<T> Where<T>(this IQueryable<T> source, string predicate, params object[] values)
  17. {
  18. return (IQueryable<T>)Where((IQueryable)source, predicate, values);
  19. }
  20. public static IQueryable Where(this IQueryable source, string predicate, params object[] values)
  21. {
  22. if (source == null) throw new ArgumentNullException(nameof(source));
  23. if (predicate == null) throw new ArgumentNullException(nameof(predicate));
  24. var lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values);
  25. return source.Provider.CreateQuery(
  26. Expression.Call(
  27. typeof(Queryable), "Where",
  28. new[] { source.ElementType },
  29. source.Expression, Expression.Quote(lambda)));
  30. }
  31. public static IQueryable Select(this IQueryable source, string selector, params object[] values)
  32. {
  33. if (source == null) throw new ArgumentNullException(nameof(source));
  34. if (selector == null) throw new ArgumentNullException(nameof(selector));
  35. var lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
  36. return source.Provider.CreateQuery(
  37. Expression.Call(
  38. typeof(Queryable), "Select",
  39. new[] { source.ElementType, lambda.Body.Type },
  40. source.Expression, Expression.Quote(lambda)));
  41. }
  42. public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values)
  43. {
  44. return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
  45. }
  46. public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values)
  47. {
  48. if (source == null) throw new ArgumentNullException(nameof(source));
  49. if (ordering == null) throw new ArgumentNullException(nameof(ordering));
  50. var parameters = new[] {
  51. Expression.Parameter(source.ElementType, "") };
  52. var parser = new ExpressionParser(parameters, ordering, values);
  53. var orderings = parser.ParseOrdering();
  54. var queryExpr = source.Expression;
  55. var methodAsc = "OrderBy";
  56. var methodDesc = "OrderByDescending";
  57. foreach (var o in orderings)
  58. {
  59. queryExpr = Expression.Call(
  60. typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
  61. new[] { source.ElementType, o.Selector.Type },
  62. queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
  63. methodAsc = "ThenBy";
  64. methodDesc = "ThenByDescending";
  65. }
  66. return source.Provider.CreateQuery(queryExpr);
  67. }
  68. public static IQueryable Take(this IQueryable source, int count)
  69. {
  70. if (source == null) throw new ArgumentNullException(nameof(source));
  71. return source.Provider.CreateQuery(
  72. Expression.Call(
  73. typeof(Queryable), "Take",
  74. new[] { source.ElementType },
  75. source.Expression, Expression.Constant(count)));
  76. }
  77. public static IQueryable Skip(this IQueryable source, int count)
  78. {
  79. if (source == null) throw new ArgumentNullException(nameof(source));
  80. return source.Provider.CreateQuery(
  81. Expression.Call(
  82. typeof(Queryable), "Skip",
  83. new[] { source.ElementType },
  84. source.Expression, Expression.Constant(count)));
  85. }
  86. public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values)
  87. {
  88. if (source == null) throw new ArgumentNullException(nameof(source));
  89. if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
  90. if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector));
  91. var keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values);
  92. var elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values);
  93. return source.Provider.CreateQuery(
  94. Expression.Call(
  95. typeof(Queryable), "GroupBy",
  96. new[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
  97. source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda)));
  98. }
  99. public static bool Any(this IQueryable source)
  100. {
  101. if (source == null) throw new ArgumentNullException(nameof(source));
  102. return (bool)source.Provider.Execute(
  103. Expression.Call(
  104. typeof(Queryable), "Any",
  105. new[] { source.ElementType }, source.Expression));
  106. }
  107. public static int Count(this IQueryable source)
  108. {
  109. if (source == null) throw new ArgumentNullException(nameof(source));
  110. return (int)source.Provider.Execute(
  111. Expression.Call(
  112. typeof(Queryable), "Count",
  113. new[] { source.ElementType }, source.Expression));
  114. }
  115. public static IQueryable Distinct(this IQueryable source)
  116. {
  117. if (source == null) throw new ArgumentNullException(nameof(source));
  118. return source.Provider.CreateQuery(
  119. Expression.Call(
  120. typeof(Queryable), "Distinct",
  121. new[] { source.ElementType },
  122. source.Expression));
  123. }
  124. /// <summary>
  125. /// Dynamically runs an aggregate function on the IQueryable.
  126. /// </summary>
  127. /// <param name="source">The IQueryable data source.</param>
  128. /// <param name="function">The name of the function to run. Can be Sum, Average, Min, Max.</param>
  129. /// <param name="member">The name of the property to aggregate over.</param>
  130. /// <returns>The value of the aggregate function run over the specified property.</returns>
  131. public static object Aggregate(this IQueryable source, string function, string member)
  132. {
  133. if (source == null) throw new ArgumentNullException(nameof(source));
  134. if (member == null) throw new ArgumentNullException(nameof(member));
  135. // Properties
  136. var property = source.ElementType.GetProperty(member) ?? throw new ArgumentException($"Property [{member}] not found", nameof(member));
  137. var parameter = Expression.Parameter(source.ElementType, "s");
  138. Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter);
  139. // We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
  140. // which is expressed as ( (TSource s) => s.Price );
  141. //var methods = typeof(Queryable).GetMethods().Where(x => x.Name == function);
  142. // Method
  143. var aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
  144. m => m.Name == function
  145. && m.ReturnType == property.PropertyType // should match the type of the property
  146. && m.IsGenericMethod);
  147. // Sum, Average
  148. if (aggregateMethod != null)
  149. {
  150. return source.Provider.Execute(
  151. Expression.Call(
  152. null,
  153. aggregateMethod.MakeGenericMethod(source.ElementType),
  154. new[] { source.Expression, Expression.Quote(selector) }));
  155. }
  156. // Min, Max
  157. else
  158. {
  159. aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
  160. m => m.Name == function
  161. && m.GetGenericArguments().Length == 2
  162. && m.IsGenericMethod);
  163. if (null == aggregateMethod) throw new ArgumentException();
  164. return source.Provider.Execute(
  165. Expression.Call(
  166. null,
  167. aggregateMethod.MakeGenericMethod(source.ElementType, property.PropertyType),
  168. new[] { source.Expression, Expression.Quote(selector) }));
  169. }
  170. }
  171. #endregion IQueryable Extensions
  172. #region IEnumerable Extensions
  173. public static IEnumerable<T> Where<T>(this IEnumerable<T> source, string predicate, params object[] values)
  174. {
  175. if (source == null) throw new ArgumentNullException(nameof(source));
  176. return source.AsQueryable().Where(predicate, values);
  177. }
  178. public static IEnumerable Where(this IEnumerable source, string predicate, params object[] values)
  179. {
  180. if (source == null) throw new ArgumentNullException(nameof(source));
  181. return source.AsQueryable().Where(predicate, values);
  182. }
  183. public static IEnumerable Select(this IEnumerable source, string selector, params object[] values)
  184. {
  185. if (source == null) throw new ArgumentNullException(nameof(source));
  186. return source.AsQueryable().Select(selector, values);
  187. }
  188. public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> source, string ordering, params object[] values)
  189. {
  190. if (source == null) throw new ArgumentNullException(nameof(source));
  191. return source.AsQueryable().OrderBy(ordering, values);
  192. }
  193. public static IEnumerable OrderBy(this IEnumerable source, string ordering, params object[] values)
  194. {
  195. if (source == null) throw new ArgumentNullException(nameof(source));
  196. return source.AsQueryable().OrderBy(ordering, values);
  197. }
  198. public static IEnumerable Take(this IEnumerable source, int count)
  199. {
  200. if (source == null) throw new ArgumentNullException(nameof(source));
  201. return source.AsQueryable().Take(count);
  202. }
  203. public static IEnumerable Skip(this IEnumerable source, int count)
  204. {
  205. if (source == null) throw new ArgumentNullException(nameof(source));
  206. return source.AsQueryable().Skip(count);
  207. }
  208. public static IEnumerable GroupBy(this IEnumerable source, string keySelector, string elementSelector, params object[] values)
  209. {
  210. if (source == null) throw new ArgumentNullException(nameof(source));
  211. return source.AsQueryable().GroupBy(keySelector, elementSelector, values);
  212. }
  213. public static bool Any(this IEnumerable source)
  214. {
  215. if (source == null) throw new ArgumentNullException(nameof(source));
  216. return source.AsQueryable().Any();
  217. }
  218. public static int Count(this IEnumerable source)
  219. {
  220. if (source == null) throw new ArgumentNullException(nameof(source));
  221. return source.AsQueryable().Count();
  222. }
  223. public static IEnumerable Distinct(this IEnumerable source)
  224. {
  225. if (source == null) throw new ArgumentNullException(nameof(source));
  226. return source.AsQueryable().Distinct();
  227. }
  228. #endregion IEnumerable Extensions
  229. }
  230. }