From 51d0e5d4c6186009d859cd3a3713d0a6477f5865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20Can?= Date: Fri, 31 Jul 2020 03:08:21 +0300 Subject: [PATCH 1/3] new filter type added and iplemented expression feature to collection's methods --- README.md | 42 +++++ .../Expressions/WhereExpression.cs | 150 ++++++++++++++++-- src/QueryDesignerCore/WhereFilter.cs | 6 + src/QueryDesignerCore/WhereFilterType.cs | 33 +++- 4 files changed, 220 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 016ff54..28ca024 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ Let's extend existing user entity and add another: ```csharp public class User { + public bool Status { get; set; } public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } @@ -148,6 +149,7 @@ public class User public class Car { + public bool Status { get; set; } public int CarId { get; set; } public string Model { get; set; } public int MaxSpeed { get; set; } @@ -166,6 +168,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 +222,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..88ea9bb 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); @@ -433,4 +552,15 @@ private static PropertyInfo GetDeclaringProperty(Expression e, string name) return p; } } + + public class Parent + { + public Child[] Children { get; set; } + } + + public class Child + { + public int ID { get; set; } + public Guid SomeID { get; set; } + } } 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..16c8491 100644 --- a/src/QueryDesignerCore/WhereFilterType.cs +++ b/src/QueryDesignerCore/WhereFilterType.cs @@ -108,6 +108,37 @@ public enum WhereFilterType /// /// The field is not null or empty /// - IsNotNullOrEmpty + IsNotNullOrEmpty, + + + /// + /// Collection count. + /// + CountEquals, + + + /// + /// Collection count greater less the value.. + /// + CountLessThan, + + + /// + /// Collection count greater than the value. + /// + CountGreaterThan, + + /// + /// Collection count is less than or equal to value. + /// + CountLessThanOrEqual, + + /// + /// Collection count is greater than or equal to the value. + /// + CountGreaterThanOrEqual, + + + } } \ No newline at end of file From 8dbd97d9fd76bb052ece7872e6ac73527ccde35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20Can?= Date: Fri, 31 Jul 2020 03:11:00 +0300 Subject: [PATCH 2/3] un necessary models removed --- README.md | 2 -- src/QueryDesignerCore/Expressions/WhereExpression.cs | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/README.md b/README.md index 28ca024..83976f7 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,6 @@ Let's extend existing user entity and add another: ```csharp public class User { - public bool Status { get; set; } public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } @@ -149,7 +148,6 @@ public class User public class Car { - public bool Status { get; set; } public int CarId { get; set; } public string Model { get; set; } public int MaxSpeed { get; set; } diff --git a/src/QueryDesignerCore/Expressions/WhereExpression.cs b/src/QueryDesignerCore/Expressions/WhereExpression.cs index 88ea9bb..b2e9e1c 100644 --- a/src/QueryDesignerCore/Expressions/WhereExpression.cs +++ b/src/QueryDesignerCore/Expressions/WhereExpression.cs @@ -552,15 +552,4 @@ private static PropertyInfo GetDeclaringProperty(Expression e, string name) return p; } } - - public class Parent - { - public Child[] Children { get; set; } - } - - public class Child - { - public int ID { get; set; } - public Guid SomeID { get; set; } - } } From a4392851d6574e59089758c4163f377fb4286cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20Can?= Date: Fri, 31 Jul 2020 03:12:17 +0300 Subject: [PATCH 3/3] typo fixed --- src/QueryDesignerCore/WhereFilterType.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/QueryDesignerCore/WhereFilterType.cs b/src/QueryDesignerCore/WhereFilterType.cs index 16c8491..3126b05 100644 --- a/src/QueryDesignerCore/WhereFilterType.cs +++ b/src/QueryDesignerCore/WhereFilterType.cs @@ -112,26 +112,27 @@ public enum WhereFilterType /// - /// Collection count. + /// Collection count equal the value. /// CountEquals, /// - /// Collection count greater less the value.. + /// 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 less than or equal to value. - /// - CountLessThanOrEqual, + /// /// Collection count is greater than or equal to the value.