using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace Vit.Linq.QueryBuilder { public class QueryBuilderService { public static QueryBuilderService Instance = new QueryBuilderService(); /// /// operatorName -> operatorType(in class FilterRuleOperator) /// public Dictionary operatorMap = new Dictionary(); public bool operatorIsIgnoreCase = true; public bool ignoreError = false; public QueryBuilderService AddOperatorMap(string operatorName, string operatorType) { if (operatorIsIgnoreCase) operatorName = operatorName?.ToLower(); operatorMap[operatorName] = operatorType; return this; } public QueryBuilderService AddOperatorMap(IEnumerable<(string operatorName, string operatorType)> maps) { foreach (var map in maps) AddOperatorMap(map.operatorName, map.operatorType); return this; } public Func ToPredicate(IFilterRule rule) { return ToExpression(rule)?.Compile(); } public string ToExpressionString(IFilterRule rule) { return ToExpression(rule)?.ToString(); } public Expression> ToExpression(IFilterRule rule) { var exp = ToLambdaExpression(rule, typeof(T)); return (Expression>)exp; } public LambdaExpression ToLambdaExpression(IFilterRule rule, Type targetType) { ParameterExpression parameter = Expression.Parameter(targetType); var expression = ConvertToExpression(rule, parameter); if (expression == null) { return null; } return Expression.Lambda(expression, parameter); } public ECondition GetCondition(IFilterRule filter) { return filter.condition?.ToLower() == "or" ? ECondition.or : ECondition.and; } public string GetOperator(IFilterRule filter) { var operate = filter.@operator ?? ""; if (operatorIsIgnoreCase) operate = operate.ToLower(); if (operatorMap.TryGetValue(operate, out var op2)) return operatorIsIgnoreCase ? op2?.ToLower() : op2; return operate; } Expression ConvertToExpression(IFilterRule rule, ParameterExpression parameter) { if (rule == null) return null; // #1 nested filter rules if (rule.rules?.Any() == true) { return ConvertToExpression(rule.rules, parameter, GetCondition(rule)); } // #2 simple rule if (string.IsNullOrWhiteSpace(rule.field)) { return null; } MemberExpression memberExp = LinqHelp.GetFieldMemberExpression(parameter, rule.field); #region Get Expression Type fieldType = memberExp.Type; var Operator = GetOperator(rule); var cmpType = operatorIsIgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; { #region ##1 null if (FilterRuleOperator.IsNull.Equals(Operator, cmpType)) { return IsNull(); } if (FilterRuleOperator.IsNotNull.Equals(Operator, cmpType)) { return Expression.Not(IsNull()); } #endregion #region ##2 number if (FilterRuleOperator.Equal.Equals(Operator, cmpType)) { return Expression.Equal(memberExp, ConvertValueExp()); } if (FilterRuleOperator.NotEqual.Equals(Operator, cmpType)) { return Expression.NotEqual(memberExp, ConvertValueExp()); } if (FilterRuleOperator.GreaterThan.Equals(Operator, cmpType)) { return Expression.GreaterThan(memberExp, ConvertValueExp()); } if (FilterRuleOperator.GreaterThanOrEqual.Equals(Operator, cmpType)) { return Expression.GreaterThanOrEqual(memberExp, ConvertValueExp()); } if (FilterRuleOperator.LessThan.Equals(Operator, cmpType)) { return Expression.LessThan(memberExp, ConvertValueExp()); } if (FilterRuleOperator.LessThanOrEqual.Equals(Operator, cmpType)) { return Expression.LessThanOrEqual(memberExp, ConvertValueExp()); } #endregion #region ##3 array if (FilterRuleOperator.In.Equals(Operator, cmpType)) { return In(); } if (FilterRuleOperator.NotIn.Equals(Operator, cmpType)) { return Expression.Not(In()); } #endregion #region ##4 string if (FilterRuleOperator.Contains.Equals(Operator, cmpType)) { var nullCheck = Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp); var contains = Expression.Call(memberExp, "Contains", null, ConvertValueExp()); return Expression.AndAlso(Expression.Not(nullCheck), contains); } if (FilterRuleOperator.NotContains.Equals(Operator, cmpType)) { var nullCheck = Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp); var contains = Expression.Call(memberExp, "Contains", null, ConvertValueExp()); return Expression.OrElse(nullCheck, Expression.Not(contains)); } if (FilterRuleOperator.StartsWith.Equals(Operator, cmpType)) { var nullCheck = Expression.Not(Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp)); var startsWith = Expression.Call(memberExp, "StartsWith", null, ConvertValueExp()); return Expression.AndAlso(nullCheck, startsWith); } if (FilterRuleOperator.EndsWith.Equals(Operator, cmpType)) { var nullCheck = Expression.Not(Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp)); var endsWith = Expression.Call(memberExp, "EndsWith", null, ConvertValueExp()); return Expression.AndAlso(nullCheck, endsWith); } if (FilterRuleOperator.IsNullOrEmpty.Equals(Operator, cmpType)) { return Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp); } if (FilterRuleOperator.IsNotNullOrEmpty.Equals(Operator, cmpType)) { return Expression.Not(Expression.Call(typeof(string), "IsNullOrEmpty", null, memberExp)); } #endregion } #endregion if (!ignoreError) throw new Exception("unrecognized operator : " + rule.@operator); return null; #region Method ConvertValueExp UnaryExpression ConvertValueExp() { object value = rule.value; if (value != null) { Type valueType = Nullable.GetUnderlyingType(fieldType) ?? fieldType; value = Convert.ChangeType(value, valueType); } Expression> valueLamba = () => value; return Expression.Convert(valueLamba.Body, fieldType); } Expression IsNull() { var isNullable = !fieldType.IsValueType || Nullable.GetUnderlyingType(fieldType) != null; if (isNullable) { var nullValue = Expression.Constant(null, fieldType); return Expression.Equal(memberExp, nullValue); } return Expression.Constant(false, typeof(bool)); } Expression In() { Expression arrayExp = null; #region build arrayExp { Type valueType = typeof(IEnumerable<>).MakeGenericType(fieldType); object value = null; if (rule.value != null) { //value = Vit.Core.Module.Serialization.Json.Deserialize(Vit.Core.Module.Serialization.Json.Serialize(rule.value), valueType); if (rule.value is IEnumerable arr) { value = ConvertToList(arr, fieldType); } } Expression> valueLamba = () => value; arrayExp = Expression.Convert(valueLamba.Body, valueType); } #endregion var inCheck = Expression.Call(typeof(System.Linq.Enumerable), "Contains", new[] { fieldType }, arrayExp, memberExp); return inCheck; } #endregion } Expression ConvertToExpression(IEnumerable rules, ParameterExpression parameter, ECondition condition = ECondition.and) { if (rules?.Any() != true) { return null; } Expression expression = null; foreach (var rule in rules) { var curExp = ConvertToExpression(rule, parameter); if (curExp != null) expression = Append(expression, curExp); } return expression; #region Method Append Expression Append(Expression exp1, Expression exp2) { if (exp1 == null) { return exp2; } if (exp2 == null) { return exp1; } return condition == ECondition.or ? Expression.OrElse(exp1, exp2) : Expression.AndAlso(exp1, exp2); } #endregion } #region ConvertToList internal object ConvertToList(IEnumerable values, Type fieldType) { var methodInfo = typeof(QueryBuilderService).GetMethod("ConvertToListByType", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).MakeGenericMethod(fieldType); return methodInfo.Invoke(this, new object[] { values }); } internal List ConvertToListByType(IEnumerable values) { Type valueType = typeof(T); var list = new List(); foreach (var item in values) { var value = (T)Convert.ChangeType(item, valueType); list.Add(value); } return list; } #endregion } }