QueryBuilderService.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. namespace Vit.Linq.QueryBuilder
  7. {
  8. public class QueryBuilderService
  9. {
  10. public static QueryBuilderService Instance = new QueryBuilderService();
  11. /// <summary>
  12. /// operatorName -> operatorType(in class FilterRuleOperator)
  13. /// </summary>
  14. public Dictionary<string, string> operatorMap = new Dictionary<string, string>();
  15. public bool operatorIsIgnoreCase = true;
  16. public bool ignoreError = false;
  17. public QueryBuilderService AddOperatorMap(string operatorName, string operatorType)
  18. {
  19. if (operatorIsIgnoreCase) operatorName = operatorName?.ToLower();
  20. operatorMap[operatorName] = operatorType;
  21. return this;
  22. }
  23. public QueryBuilderService AddOperatorMap(IEnumerable<(string operatorName, string operatorType)> maps)
  24. {
  25. foreach (var map in maps)
  26. AddOperatorMap(map.operatorName, map.operatorType);
  27. return this;
  28. }
  29. public Func<T, bool> ToPredicate<T>(IFilterRule rule)
  30. {
  31. return ToExpression<T>(rule)?.Compile();
  32. }
  33. public string ToExpressionString<T>(IFilterRule rule)
  34. {
  35. return ToExpression<T>(rule)?.ToString();
  36. }
  37. public Expression<Func<T, bool>> ToExpression<T>(IFilterRule rule)
  38. {
  39. var exp = ToLambdaExpression(rule, typeof(T));
  40. return (Expression<Func<T, bool>>)exp;
  41. }
  42. public LambdaExpression ToLambdaExpression(IFilterRule rule, Type targetType)
  43. {
  44. ParameterExpression parameter = Expression.Parameter(targetType);
  45. var expression = ConvertToExpression(rule, parameter);
  46. if (expression == null)
  47. {
  48. return null;
  49. }
  50. return Expression.Lambda(expression, parameter);
  51. }
  52. public ECondition GetCondition(IFilterRule filter)
  53. {
  54. return filter.condition?.ToLower() == "or" ? ECondition.or : ECondition.and;
  55. }
  56. public string GetOperator(IFilterRule filter)
  57. {
  58. var operate = filter.@operator ?? "";
  59. if (operatorIsIgnoreCase) operate = operate.ToLower();
  60. if (operatorMap.TryGetValue(operate, out var op2)) return operatorIsIgnoreCase ? op2?.ToLower() : op2;
  61. return operate;
  62. }
  63. Expression ConvertToExpression(IFilterRule rule, ParameterExpression parameter)
  64. {
  65. if (rule == null) return null;
  66. // #1 nested filter rules
  67. if (rule.rules?.Any() == true)
  68. {
  69. return ConvertToExpression(rule.rules, parameter, GetCondition(rule));
  70. }
  71. // #2 simple rule
  72. if (string.IsNullOrWhiteSpace(rule.field))
  73. {
  74. return null;
  75. }
  76. MemberExpression memberExp = LinqHelp.GetFieldMemberExpression(parameter, rule.field);
  77. #region Get Expression
  78. Type fieldType = memberExp.Type;
  79. var Operator = GetOperator(rule);
  80. var cmpType = operatorIsIgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
  81. {
  82. #region ##1 null
  83. if (FilterRuleOperator.IsNull.Equals(Operator, cmpType))
  84. {
  85. return IsNull();
  86. }
  87. if (FilterRuleOperator.IsNotNull.Equals(Operator, cmpType))
  88. {
  89. return Expression.Not(IsNull());
  90. }
  91. #endregion
  92. #region ##2 number
  93. if (FilterRuleOperator.Equal.Equals(Operator, cmpType))
  94. {
  95. return Expression.Equal(memberExp, ConvertValueExp());
  96. }
  97. if (FilterRuleOperator.NotEqual.Equals(Operator, cmpType))
  98. {
  99. return Expression.NotEqual(memberExp, ConvertValueExp());
  100. }
  101. if (FilterRuleOperator.GreaterThan.Equals(Operator, cmpType))
  102. {
  103. return Expression.GreaterThan(memberExp, ConvertValueExp());
  104. }
  105. if (FilterRuleOperator.GreaterThanOrEqual.Equals(Operator, cmpType))
  106. {
  107. return Expression.GreaterThanOrEqual(memberExp, ConvertValueExp());
  108. }
  109. if (FilterRuleOperator.LessThan.Equals(Operator, cmpType))
  110. {
  111. return Expression.LessThan(memberExp, ConvertValueExp());
  112. }
  113. if (FilterRuleOperator.LessThanOrEqual.Equals(Operator, cmpType))
  114. {
  115. return Expression.LessThanOrEqual(memberExp, ConvertValueExp());
  116. }
  117. #endregion
  118. #region ##3 array
  119. if (FilterRuleOperator.In.Equals(Operator, cmpType))
  120. {
  121. return In();
  122. }
  123. if (FilterRuleOperator.NotIn.Equals(Operator, cmpType))
  124. {
  125. return Expression.Not(In());
  126. }
  127. #endregion
  128. #region ##4 string
  129. if (FilterRuleOperator.Contains.Equals(Operator, cmpType))
  130. {
  131. var nullCheck = Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp);
  132. var contains = Expression.Call(memberExp, "Contains", null, ConvertValueExp());
  133. return Expression.AndAlso(Expression.Not(nullCheck), contains);
  134. }
  135. if (FilterRuleOperator.NotContains.Equals(Operator, cmpType))
  136. {
  137. var nullCheck = Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp);
  138. var contains = Expression.Call(memberExp, "Contains", null, ConvertValueExp());
  139. return Expression.OrElse(nullCheck, Expression.Not(contains));
  140. }
  141. if (FilterRuleOperator.StartsWith.Equals(Operator, cmpType))
  142. {
  143. var nullCheck = Expression.Not(Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp));
  144. var startsWith = Expression.Call(memberExp, "StartsWith", null, ConvertValueExp());
  145. return Expression.AndAlso(nullCheck, startsWith);
  146. }
  147. if (FilterRuleOperator.EndsWith.Equals(Operator, cmpType))
  148. {
  149. var nullCheck = Expression.Not(Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp));
  150. var endsWith = Expression.Call(memberExp, "EndsWith", null, ConvertValueExp());
  151. return Expression.AndAlso(nullCheck, endsWith);
  152. }
  153. if (FilterRuleOperator.IsNullOrEmpty.Equals(Operator, cmpType))
  154. {
  155. return Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp);
  156. }
  157. if (FilterRuleOperator.IsNotNullOrEmpty.Equals(Operator, cmpType))
  158. {
  159. return Expression.Not(Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp));
  160. }
  161. #endregion
  162. }
  163. #endregion
  164. if (!ignoreError) throw new Exception("unrecognized operator : " + rule.@operator);
  165. return null;
  166. #region Method ConvertValueExp
  167. UnaryExpression ConvertValueExp()
  168. {
  169. object value = rule.value;
  170. if (value != null)
  171. {
  172. Type valueType = Nullable.GetUnderlyingType(fieldType) ?? fieldType;
  173. value = Convert.ChangeType(value, valueType);
  174. }
  175. Expression<Func<object>> valueLamba = () => value;
  176. return Expression.Convert(valueLamba.Body, fieldType);
  177. }
  178. Expression IsNull()
  179. {
  180. var isNullable = !fieldType.IsValueType || Nullable.GetUnderlyingType(fieldType) != null;
  181. if (isNullable)
  182. {
  183. var nullValue = Expression.Constant(null, fieldType);
  184. return Expression.Equal(memberExp, nullValue);
  185. }
  186. return Expression.Constant(false, typeof(bool));
  187. }
  188. Expression In()
  189. {
  190. Expression arrayExp = null;
  191. #region build arrayExp
  192. {
  193. Type valueType = typeof(IEnumerable<>).MakeGenericType(fieldType);
  194. object value = null;
  195. if (rule.value != null)
  196. {
  197. //value = Vit.Core.Module.Serialization.Json.Deserialize(Vit.Core.Module.Serialization.Json.Serialize(rule.value), valueType);
  198. if (rule.value is IEnumerable arr)
  199. {
  200. value = ConvertToList(arr, fieldType);
  201. }
  202. }
  203. Expression<Func<object>> valueLamba = () => value;
  204. arrayExp = Expression.Convert(valueLamba.Body, valueType);
  205. }
  206. #endregion
  207. var inCheck = Expression.Call(typeof(System.Linq.Enumerable), "Contains", new[] { fieldType }, arrayExp, memberExp);
  208. return inCheck;
  209. }
  210. #endregion
  211. }
  212. Expression ConvertToExpression(IEnumerable<IFilterRule> rules, ParameterExpression parameter, ECondition condition = ECondition.and)
  213. {
  214. if (rules?.Any() != true)
  215. {
  216. return null;
  217. }
  218. Expression expression = null;
  219. foreach (var rule in rules)
  220. {
  221. var curExp = ConvertToExpression(rule, parameter);
  222. if (curExp != null)
  223. expression = Append(expression, curExp);
  224. }
  225. return expression;
  226. #region Method Append
  227. Expression Append(Expression exp1, Expression exp2)
  228. {
  229. if (exp1 == null)
  230. {
  231. return exp2;
  232. }
  233. if (exp2 == null)
  234. {
  235. return exp1;
  236. }
  237. return condition == ECondition.or ? Expression.OrElse(exp1, exp2) : Expression.AndAlso(exp1, exp2);
  238. }
  239. #endregion
  240. }
  241. #region ConvertToList
  242. internal object ConvertToList(IEnumerable values, Type fieldType)
  243. {
  244. var methodInfo = typeof(QueryBuilderService).GetMethod("ConvertToListByType", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).MakeGenericMethod(fieldType);
  245. return methodInfo.Invoke(this, new object[] { values });
  246. }
  247. internal List<T> ConvertToListByType<T>(IEnumerable values)
  248. {
  249. Type valueType = typeof(T);
  250. var list = new List<T>();
  251. foreach (var item in values)
  252. {
  253. var value = (T)Convert.ChangeType(item, valueType);
  254. list.Add(value);
  255. }
  256. return list;
  257. }
  258. #endregion
  259. }
  260. }