private MethodInfo GetSortMethod( SortingDirection sortDirection, Type propertyType, ref bool preOrdered, bool caseSensitive) { string methodName = sortDirection == SortingDirection.Asc ? preOrdered ? nameof(Enumerable.ThenBy) : nameof(Enumerable.OrderBy) : preOrdered ? nameof(Enumerable.ThenByDescending) : nameof(Enumerable.OrderByDescending); preOrdered = true; return(LINQUtils.BuildLINQExtensionMethod( methodName, numberOfParameters: !_usesCaseInsensitiveSource && caseSensitive && propertyType == typeof(string) ? CASE_SENSITIVE_ORDER_FUNCTION_PARAM_COUNT : CASE_INSENSITIVE_ORDER_FUNCTION_PARAM_COUNT, genericElementTypes: new[] { _setElementType, propertyType })); }
/// <summary> /// Applies the given DynamicQueryOptions to the generic IEnumerable instace. /// </summary> /// <param name="currentSet">Existing IEnumerable instance.</param> /// <param name="dynamicQueryOptions">Query options to apply.</param> /// <returns>DynamicQueryOptions applied IEnumerable instance,</returns> public static IQueryable ApplyFilters(this IQueryable currentSet, DynamicQueryOptions dynamicQueryOptions) { try { if (dynamicQueryOptions == null || currentSet == null) { return(currentSet); } Expression exp = null; // Create the query parameter ParameterExpression param = Expression.Parameter(currentSet.ElementType, currentSet.ElementType.Name.ToLower()); if (dynamicQueryOptions.Filters != null && dynamicQueryOptions.Filters.Count > 0) { // Copy the array since we need to mutate it, we should avoid mutating the real list. List <Filter> dqbFilters = dynamicQueryOptions.Filters.ToList(); // Since the expression is null at this point, we should create it with our first filter. exp = BuildFilterExpression(param, dqbFilters.FirstOrDefault(), dynamicQueryOptions.UsesCaseInsensitiveSource); dqbFilters.RemoveAt(0); // Remove the first since it was added already. // Append the rest foreach (Filter item in dqbFilters) { exp = Expression.AndAlso(exp, BuildFilterExpression(param, item, dynamicQueryOptions.UsesCaseInsensitiveSource)); } } if (dynamicQueryOptions.SortOptions != null && dynamicQueryOptions.SortOptions.Count > 0) { List <OrderOptionDetails> orderLambdas = new List <OrderOptionDetails>(); foreach (SortOption so in dynamicQueryOptions.SortOptions) { Expression paramExpr = ExtractMember(param, so.PropertyName); orderLambdas.Add(new OrderOptionDetails { Direction = so.SortingDirection, Expression = Expression.Lambda(paramExpr, param), ParameterExpression = paramExpr, CaseSensitive = so.CaseSensitive }); } var orderVisitor = new OrderFunctionVisitor( currentSet.Expression, orderLambdas, currentSet.ElementType, dynamicQueryOptions.UsesCaseInsensitiveSource, dynamicQueryOptions.IgnorePredefinedOrders); currentSet = currentSet.Provider.CreateQuery(orderVisitor.ApplyOrders()); } if (exp != null) { MethodCallExpression whereFilter = Expression.Call( LINQUtils.BuildLINQExtensionMethod( nameof(Enumerable.Where), genericElementTypes: new[] { currentSet.ElementType }, enumerableType: typeof(Queryable)), currentSet.Expression, Expression.Quote(Expression.Lambda(exp, param))); currentSet = currentSet.Provider.CreateQuery(whereFilter); } if (dynamicQueryOptions.PaginationOption != null) { if (dynamicQueryOptions.PaginationOption.AssignDataSetCount) { dynamicQueryOptions.PaginationOption.DataSetCount = (int)_countFunction.Invoke(null, new[] { currentSet }); } if (dynamicQueryOptions.PaginationOption.Offset > 0) { MethodCallExpression skip = Expression.Call( _skipFunction, currentSet.Expression, Expression.Constant(dynamicQueryOptions.PaginationOption.Offset)); currentSet = currentSet.Provider.CreateQuery(skip); } if (dynamicQueryOptions.PaginationOption.Count > 0) { MethodCallExpression take = Expression.Call( _takeFunction, currentSet.Expression, Expression.Constant(dynamicQueryOptions.PaginationOption.Count)); currentSet = currentSet.Provider.CreateQuery(take); } } return(currentSet); } catch (Exception ex) { throw new DynamicQueryException("DynamicQueryBuilder has encountered an unhandled exception", string.Empty, ex); } }
/// <summary> /// Builds a runtime generic dynamic query with the given filters. /// </summary> /// <param name="param">Created parameter instance or current expression body.</param> /// <param name="filter">Filter instance to build.</param> /// <param name="usesCaseInsensitiveSource">Flag to detect if the query is going to run on a SQL database.</param> /// <returns>Built query expression.</returns> internal static Expression BuildFilterExpression(ParameterExpression param, Filter filter, bool usesCaseInsensitiveSource = false) { Expression parentMember = ExtractMember(param, filter.PropertyName); if (parentMember.Type == typeof(string) && filter.CaseSensitive && !usesCaseInsensitiveSource) { parentMember = Expression.Call(parentMember, _toLowerInvariantMethod); } string stringFilterValue = filter.Value?.ToString(); // We are handling In operations seperately which are basically a list of OR=EQUALS operation. We recursively handle this operation. if (filter.Operator == FilterOperation.In) { if (filter.Value == null) { throw new DynamicQueryException("You can't pass type null to In. Pass null as a string instead."); } // Split all data into a list List <string> splittedValues = stringFilterValue.Split(PARAMETER_OPTION_DELIMITER).ToList(); var equalsFilter = new Filter { Operator = FilterOperation.Equals, PropertyName = filter.PropertyName, Value = splittedValues.First().ToLowerInvariant(), CaseSensitive = filter.CaseSensitive }; // Create the expression with the first value. Expression builtInExpression = BuildFilterExpression(param, equalsFilter, usesCaseInsensitiveSource); splittedValues.RemoveAt(0); // Remove the first value // Create query for every splitted value and append them. foreach (var item in splittedValues) { equalsFilter.Value = item; builtInExpression = Expression.Or(builtInExpression, BuildFilterExpression(param, equalsFilter, usesCaseInsensitiveSource)); } return(builtInExpression); } // We should convert the data into its own type before we do any query building. object convertedValue = null; if (filter.Operator < FilterOperation.Any) { convertedValue = stringFilterValue != "null" ? TypeDescriptor.GetConverter(parentMember.Type).ConvertFromInvariantString( usesCaseInsensitiveSource ? stringFilterValue : filter.CaseSensitive ? stringFilterValue?.ToLowerInvariant() : stringFilterValue) : null; } ConstantExpression constant = Expression.Constant(convertedValue); switch (filter.Operator) { case FilterOperation.Equals: return(Expression.Equal(parentMember, constant)); case FilterOperation.NotEqual: return(Expression.NotEqual(parentMember, constant)); case FilterOperation.Contains: return(Expression.Call(parentMember, _stringContainsMethod, constant)); case FilterOperation.GreaterThan: return(Expression.GreaterThan(parentMember, constant)); case FilterOperation.GreaterThanOrEqual: return(Expression.GreaterThanOrEqual(parentMember, constant)); case FilterOperation.LessThan: return(Expression.LessThan(parentMember, constant)); case FilterOperation.LessThanOrEqual: return(Expression.LessThanOrEqual(parentMember, constant)); case FilterOperation.StartsWith: return(Expression.Call(parentMember, _stringStartsWithMethod, constant)); case FilterOperation.EndsWith: return(Expression.Call(parentMember, _stringEndsWithMethod, constant)); case FilterOperation.Any: case FilterOperation.All: ParameterExpression memberParam = Expression.Parameter( parentMember.Type.GenericTypeArguments[0], parentMember.Type.GenericTypeArguments[0].Name); MethodInfo requestedFunction = LINQUtils.BuildLINQExtensionMethod( filter.Operator.ToString(), genericElementTypes: new[] { memberParam.Type }, enumerableType: typeof(Enumerable)); Expression builtMemberExpression = BuildFilterExpression(memberParam, (filter.Value as DynamicQueryOptions).Filters.First(), usesCaseInsensitiveSource); return(Expression.Call( requestedFunction, Expression.PropertyOrField(param, filter.PropertyName), Expression.Lambda(builtMemberExpression, memberParam))); default: return(null); } }
/// <summary> /// Applies the given DynamicQueryOptions to the generic IEnumerable instace. /// </summary> /// <param name="currentSet">Existing IEnumerable instance.</param> /// <param name="dynamicQueryOptions">Query options to apply.</param> /// <returns>DynamicQueryOptions applied IEnumerable instance,</returns> public static IQueryable ApplyFilters(this IQueryable currentSet, DynamicQueryOptions dynamicQueryOptions) { try { if (dynamicQueryOptions == null || currentSet == null) { return(currentSet); } Expression exp = null; // Create the query parameter (x =>) ParameterExpression param = Expression.Parameter(currentSet.ElementType, currentSet.ElementType.Name.ToLower()); // Check if we have any filters if (dynamicQueryOptions.Filters != null && dynamicQueryOptions.Filters.Count > 0) { // Lets build the first expression and then iterate the rest and append them to this one exp = BuildFilterExpression(param, dynamicQueryOptions.Filters.First(), dynamicQueryOptions.UsesCaseInsensitiveSource); // We start to iterate with the second element here because we have just built the first expression up above for (int i = 1; i < dynamicQueryOptions.Filters.Count; ++i) { // Build the current expression Expression builtExpression = BuildFilterExpression(param, dynamicQueryOptions.Filters[i], dynamicQueryOptions.UsesCaseInsensitiveSource); // Get the previous filter to retrieve the logical operator between the current and the next filter Filter previousFilter = dynamicQueryOptions.Filters.ElementAtOrDefault(i - 1); exp = LINQUtils.BuildLINQLogicalOperatorExpression(previousFilter, exp, builtExpression); } } if (dynamicQueryOptions.SortOptions != null && dynamicQueryOptions.SortOptions.Count > 0) { var orderLambdas = new List <OrderOptionDetails>(); foreach (SortOption so in dynamicQueryOptions.SortOptions) { Expression paramExpr = ExtractMember(param, so.PropertyName, false); orderLambdas.Add(new OrderOptionDetails { Direction = so.SortingDirection, Expression = Expression.Lambda(paramExpr, param), ParameterExpression = paramExpr, CaseSensitive = so.CaseSensitive }); } var orderVisitor = new OrderFunctionVisitor( currentSet.Expression, orderLambdas, currentSet.ElementType, dynamicQueryOptions.UsesCaseInsensitiveSource, dynamicQueryOptions.IgnorePredefinedOrders); currentSet = currentSet.Provider.CreateQuery(orderVisitor.ApplyOrders()); } if (exp != null) { MethodCallExpression whereFilter = Expression.Call( LINQUtils.BuildLINQExtensionMethod( nameof(Enumerable.Where), genericElementTypes: new[] { currentSet.ElementType }, enumerableType: typeof(Queryable)), currentSet.Expression, Expression.Quote(Expression.Lambda(exp, param))); currentSet = currentSet.Provider.CreateQuery(whereFilter); } if (dynamicQueryOptions.PaginationOption != null) { if (dynamicQueryOptions.PaginationOption.AssignDataSetCount) { dynamicQueryOptions.PaginationOption.DataSetCount = (int)ExtensionMethods.CountFunction.Invoke(null, new[] { currentSet }); } if (dynamicQueryOptions.PaginationOption.Offset > 0) { MethodCallExpression skip = Expression.Call( ExtensionMethods.SkipFunction, currentSet.Expression, Expression.Constant(dynamicQueryOptions.PaginationOption.Offset)); currentSet = currentSet.Provider.CreateQuery(skip); } if (dynamicQueryOptions.PaginationOption.Count > 0) { MethodCallExpression take = Expression.Call( ExtensionMethods.TakeFunction, currentSet.Expression, Expression.Constant(dynamicQueryOptions.PaginationOption.Count)); currentSet = currentSet.Provider.CreateQuery(take); } } return(currentSet); } catch (Exception ex) { throw new DynamicQueryException("DynamicQueryBuilder has encountered an unhandled exception", string.Empty, ex); } }