diff --git a/README.md b/README.md index 016ff54..83976f7 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,41 @@ Okay, now select users only who have sport cars capable of speeds up to 300 km / } } ``` +If we want to complicate the situation a little more, we can select users only who have more than 4 sport cars capable of speeds up to 300 km / hour with model Mercedes. + + +```csharp +var filter = new FilterContainer + { + Where = new TreeFilter + { + Field = "Cars", + FilterType = WhereFilterType.CountGreaterThan, + Value = 4, + OperandsOfCollections = new TreeFilter + { + OperatorType = TreeFilterType.And, + Operands = new List + { + new TreeFilter + { + Field = "MaxSpeed", + FilterType = WhereFilterType.GreaterThan, + Value = 300 + }, + new TreeFilter + { + Field = "Model", + FilterType = WhereFilterType.Equals, + Value = "Mercedes" + } + } + + } + }, + }; +``` + Field supports the appeal to the properties of the members of the entity with unlimited nesting. Similarly works sorting. ## Filtering methods @@ -185,6 +220,11 @@ Currently FilterType allows you to filter by the following ways: 2. Applied to the listed items without using Value: * Any * NotAny + * CountEquals + * CountLessThan + * CountGreaterThan + * CountLessThanOrEqual + * CountGreaterThanOrEqual ## Entity members Available types for single member entities, **which can be filtered**: diff --git a/src/QueryDesignerCore/Expressions/WhereExpression.cs b/src/QueryDesignerCore/Expressions/WhereExpression.cs index ad41aa1..b2e9e1c 100644 --- a/src/QueryDesignerCore/Expressions/WhereExpression.cs +++ b/src/QueryDesignerCore/Expressions/WhereExpression.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -15,6 +16,12 @@ internal static class WhereExpression /// private static readonly Type StringType = typeof(string); + + /// + /// Integer type. + /// + private static readonly Type IntType = typeof(int); + /// /// Expression type. /// @@ -71,11 +78,45 @@ internal static class WhereExpression method => method.Name == "Any" && method.IsStatic && method.GetParameters().Length == 2); + /// + /// Info about "Any" method with one parameter for collection. + /// + private static readonly MethodInfo CollectionCount = QueryableType.GetRuntimeMethods().Single( + method => method.Name == "Count" && + method.GetParameters().Length == 1); + + /// + /// Info about "Any" method with one parameter for collection. + /// + private static readonly MethodInfo CollectionCount2 = QueryableType.GetRuntimeMethods().Single( + method => method.Name == "Count" && + method.GetParameters().Length == 2); + /// /// Info about method of constructing expressions. /// private static readonly MethodInfo ExpressionMethod = typeof(WhereExpression).GetRuntimeMethods().FirstOrDefault(m => m.Name == "GetExpression"); + /// + /// Info about method of constructing expressions. + /// + private static readonly MethodInfo ExpressionTreeMethod = typeof(WhereExpression).GetRuntimeMethods().FirstOrDefault(m => m.Name == "GetTreeExpression"); + + + /// + /// Info about avaliable methods for filter types. + /// + private static readonly Dictionary> FilterTypeAvaliableMethods = new Dictionary>() + { + {WhereFilterType.Any, new List(){CollectionAny,CollectionAny2 } }, + {WhereFilterType.NotAny, new List(){CollectionAny,CollectionAny2 } }, + {WhereFilterType.CountEquals, new List(){ CollectionCount, CollectionCount2 } }, + {WhereFilterType.CountGreaterThan, new List(){ CollectionCount, CollectionCount2 } }, + {WhereFilterType.CountGreaterThanOrEqual, new List(){ CollectionCount, CollectionCount2 } }, + {WhereFilterType.CountLessThan, new List(){ CollectionCount, CollectionCount2 } }, + {WhereFilterType.CountLessThanOrEqual, new List(){ CollectionCount, CollectionCount2 } }, + }; + /// /// Available types for conversion. /// @@ -126,6 +167,8 @@ internal static class WhereExpression public static Expression> GetExpression(this WhereFilter filter, string suffix = "") { var e = Expression.Parameter(typeof(T), "e" + suffix); + + var exs = GetExpressionForField(e, filter, suffix + "0"); return Expression.Lambda>(exs, e); } @@ -202,15 +245,18 @@ private static Expression GetExpressionForField(Expression e, WhereFilter filter Field = t, FilterType = filter.FilterType, Value = filter.Value - }, - suffix + }, suffix }; var expr = (Expression)generic.Invoke(null, pars); - return Expression.Call( + + var call = Expression.Call( CollectionAny2.MakeGenericMethod( prop.Type.GenericTypeArguments.First()), prop, expr); + + return call; + } prop = Expression.Property(prop, GetDeclaringProperty(prop, t)); } @@ -218,6 +264,7 @@ private static Expression GetExpressionForField(Expression e, WhereFilter filter return exp; } + /// /// Construct bool-expression between different expression and a filter. /// @@ -282,16 +329,13 @@ private static Expression GenerateExpressionOneField(Expression prop, WhereFilte case WhereFilterType.Any: if (IsEnumerable(prop)) prop = AsQueryable(prop); - var ca = CollectionAny.MakeGenericMethod( - prop.Type.GenericTypeArguments.First()); - return Expression.Call(ca, prop); + + return CreateMethodCall(prop, filter); case WhereFilterType.NotAny: if (IsEnumerable(prop)) prop = AsQueryable(prop); - var cna = CollectionAny.MakeGenericMethod( - prop.Type.GenericTypeArguments.First()); - return Expression.Not(Expression.Call(cna, prop)); + return Expression.Not(CreateMethodCall(prop, filter)); case WhereFilterType.IsNull: return Expression.Equal(prop, ToStaticParameterExpressionOfType(null, prop.Type)); @@ -326,10 +370,85 @@ private static Expression GenerateExpressionOneField(Expression prop, WhereFilte Expression.Equal(prop, ToStaticParameterExpressionOfType(null, prop.Type)), Expression.Equal(prop, ToStaticParameterExpressionOfType(string.Empty, prop.Type)))); + + case WhereFilterType.CountEquals: + if (IsEnumerable(prop)) + prop = AsQueryable(prop); + + return Expression.Equal(CreateMethodCall(prop, filter), Expression.Constant(filter.Value, IntType)); + + case WhereFilterType.CountGreaterThan: + if (IsEnumerable(prop)) + prop = AsQueryable(prop); + + return Expression.GreaterThan(CreateMethodCall(prop, filter), Expression.Constant(filter.Value, IntType)); + + case WhereFilterType.CountGreaterThanOrEqual: + if (IsEnumerable(prop)) + prop = AsQueryable(prop); + return Expression.GreaterThanOrEqual(CreateMethodCall(prop, filter), Expression.Constant(filter.Value, IntType)); + + case WhereFilterType.CountLessThan: + if (IsEnumerable(prop)) + prop = AsQueryable(prop); + return Expression.LessThan(CreateMethodCall(prop, filter), Expression.Constant(filter.Value, IntType)); + + case WhereFilterType.CountLessThanOrEqual: + if (IsEnumerable(prop)) + prop = AsQueryable(prop); + return Expression.LessThanOrEqual(CreateMethodCall(prop, filter), Expression.Constant(filter.Value, IntType)); + default: return prop; } } + /// + /// Create method call with different expression + /// + /// Different expression. + /// Filter for query. + /// Method call expression. + private static MethodCallExpression CreateMethodCall(Expression prop, WhereFilter filter) + { + var methods = FilterTypeAvaliableMethods[filter.FilterType]; + + + var mf = methods[0].MakeGenericMethod( + prop.Type.GenericTypeArguments.First()); + + var mf2 = methods[1].MakeGenericMethod( + prop.Type.GenericTypeArguments.First()); + + + return filter.OperandsOfCollections == null ? + Expression.Call(mf, prop) : + Expression.Call(mf2, prop, CreateCollectionMethodExpression(filter.OperandsOfCollections, prop.Type.GenericTypeArguments.First())); + + } + + + /// + /// Create expression for collection methods + /// + /// Filter value. + /// Conversion to type. + /// Suffix vor variable. + /// Converted value. + private static Expression CreateCollectionMethodExpression(TreeFilter filter, Type type, string suffix = "") + { + + var generic = ExpressionTreeMethod.MakeGenericMethod(type); + + object[] pars = { + filter + ,suffix+"0" + }; + var expr = (Expression)generic.Invoke(null, pars); + + return expr; + + } + /// /// Value type filter field conversion. @@ -383,7 +502,7 @@ private static object TryCastFieldValueType(object value, Type type) private static Expression ToStaticParameterExpressionOfType(object obj, Type type) => Expression.Convert( Expression.Property( - Expression.Constant(new { obj }), + Expression.Constant(new { obj }), "obj"), type); diff --git a/src/QueryDesignerCore/WhereFilter.cs b/src/QueryDesignerCore/WhereFilter.cs index 40c1d01..91bb082 100644 --- a/src/QueryDesignerCore/WhereFilter.cs +++ b/src/QueryDesignerCore/WhereFilter.cs @@ -27,5 +27,11 @@ public WhereFilter() /// Value for filtering. /// public object Value { get; set; } + + /// + /// Operands of collection boolean expressions. + /// + public TreeFilter OperandsOfCollections { get; set; } + } } \ No newline at end of file diff --git a/src/QueryDesignerCore/WhereFilterType.cs b/src/QueryDesignerCore/WhereFilterType.cs index 3e237dc..3126b05 100644 --- a/src/QueryDesignerCore/WhereFilterType.cs +++ b/src/QueryDesignerCore/WhereFilterType.cs @@ -108,6 +108,38 @@ public enum WhereFilterType /// /// The field is not null or empty /// - IsNotNullOrEmpty + IsNotNullOrEmpty, + + + /// + /// Collection count equal the value. + /// + CountEquals, + + + /// + /// Collection count less than the value. + /// + CountLessThan, + + /// + /// Collection count is less than or equal to value. + /// + CountLessThanOrEqual, + + /// + /// Collection count greater than the value. + /// + CountGreaterThan, + + + + /// + /// Collection count is greater than or equal to the value. + /// + CountGreaterThanOrEqual, + + + } } \ No newline at end of file