From 4c45a47045ffdd3ca7d8231c6596dc5358764d98 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Sat, 7 Oct 2023 00:16:37 +0300 Subject: [PATCH 1/7] Convert to property access instead constants --- .../Abstractions/IFilterableType.cs | 4 +- .../Attributes/ArraySearchFilterAttribute.cs | 21 ++++--- .../Attributes/CollectionFilterAttribute.cs | 15 +++-- .../Attributes/CompareToAttribute.cs | 59 +++++++++++------- .../FilteringOptionsBaseAttribute.cs | 2 +- .../Attributes/IgnoreFilterAttribute.cs | 4 +- .../Attributes/OperatorComparisonAttribute.cs | 29 ++++----- .../StringFilterOptionsAttribute.cs | 24 ++++--- .../ToLowerContainsComparisonAttribute.cs | 6 +- src/AutoFilterer/ExpressionBuildContext.cs | 38 ++++++++++++ src/AutoFilterer/Types/FilterBase.cs | 12 +++- src/AutoFilterer/Types/OperatorFilter.cs | 36 +++++++---- src/AutoFilterer/Types/Range.cs | 12 +++- src/AutoFilterer/Types/StringFilter.cs | 62 ++++++++++++++----- .../Types/FilterBaseTests.cs | 24 +++++++ 15 files changed, 249 insertions(+), 99 deletions(-) create mode 100644 src/AutoFilterer/ExpressionBuildContext.cs diff --git a/src/AutoFilterer/Abstractions/IFilterableType.cs b/src/AutoFilterer/Abstractions/IFilterableType.cs index 10c809e..3e14ad0 100644 --- a/src/AutoFilterer/Abstractions/IFilterableType.cs +++ b/src/AutoFilterer/Abstractions/IFilterableType.cs @@ -5,7 +5,7 @@ namespace AutoFilterer.Abstractions; /// -/// Any property type which is able to over source property. +/// Any property type which is able to over source property. /// /// /// You can create new Complex Types via implementing this interface. It'll be automatically called if defined in an object which is implements . @@ -14,5 +14,5 @@ namespace AutoFilterer.Abstractions; /// public interface IFilterableType { - Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value); + Expression BuildExpression(ExpressionBuildContext context); } diff --git a/src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs b/src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs index 5a1be1d..8f8588f 100644 --- a/src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs +++ b/src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs @@ -7,12 +7,15 @@ namespace AutoFilterer.Attributes; public class ArraySearchFilterAttribute : FilteringOptionsBaseAttribute { - public override Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public override Expression BuildExpression(ExpressionBuildContext context) { - if (value is ICollection list && list.Count == 0) - return Expression.Constant(true); - var type = targetProperty.PropertyType; - var prop = Expression.Property(expressionBody, targetProperty.Name); + if (context.FilterProperty is ICollection list && list.Count == 0) + { + return Expression.Constant(true); // TODO: Make it better. Maybe return null? When null, it should be ignored and combined with another expressions. + } + + var type = context.TargetProperty.PropertyType; + var prop = Expression.Property(context.ExpressionBody, context.TargetProperty.Name); var containsMethod = typeof(Enumerable).GetMethods().FirstOrDefault(x => x.Name == nameof(Enumerable.Contains)).MakeGenericMethod(type); @@ -20,10 +23,14 @@ public override Expression BuildExpression(Expression expressionBody, PropertyIn method: containsMethod, arguments: new Expression[] { - Expression.Constant(value), - Expression.Property(expressionBody, targetProperty.Name) + Expression.Property(Expression.Constant(context.FilterObject), context.FilterProperty), + Expression.Property(context.ExpressionBody, context.TargetProperty) }); + + + // x => filter.Status.Contains(x.Status) + return containsExpression; } } diff --git a/src/AutoFilterer/Attributes/CollectionFilterAttribute.cs b/src/AutoFilterer/Attributes/CollectionFilterAttribute.cs index 3b33b72..80dbcf2 100644 --- a/src/AutoFilterer/Attributes/CollectionFilterAttribute.cs +++ b/src/AutoFilterer/Attributes/CollectionFilterAttribute.cs @@ -22,14 +22,18 @@ public CollectionFilterAttribute(CollectionFilterType filterOption) public CollectionFilterType FilterOption { get; set; } - public override Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public override Expression BuildExpression(ExpressionBuildContext context) { - if (value is IFilter filter) + var expressionBody = context.ExpressionBody; + + if (context.FilterObjectPropertyValue is IFilter filter) { - var type = targetProperty.PropertyType.GetGenericArguments().FirstOrDefault(); - var parameter = Expression.Parameter(type, "a"); + var type = context.TargetProperty.PropertyType.GetGenericArguments().FirstOrDefault(); + + var parameter = Expression.Parameter(type, "a"); // TODO: Change parameter name according to nested execution level. + var innerLambda = Expression.Lambda(filter.BuildExpression(type, body: parameter), parameter); - var prop = Expression.Property(expressionBody, targetProperty.Name); + var prop = Expression.Property(context.ExpressionBody, context.TargetProperty.Name); var methodInfo = typeof(Enumerable).GetMethods().LastOrDefault(x => x.Name == FilterOption.ToString()); var method = methodInfo.MakeGenericMethod(type); @@ -39,6 +43,7 @@ public override Expression BuildExpression(Expression expressionBody, PropertyIn arguments: new Expression[] { prop, innerLambda } ); } + return expressionBody; } } diff --git a/src/AutoFilterer/Attributes/CompareToAttribute.cs b/src/AutoFilterer/Attributes/CompareToAttribute.cs index eebbe57..71ff4f2 100644 --- a/src/AutoFilterer/Attributes/CompareToAttribute.cs +++ b/src/AutoFilterer/Attributes/CompareToAttribute.cs @@ -7,6 +7,7 @@ using System.Linq.Expressions; using System.Reflection; using System; +using AutoFilterer.Types; namespace AutoFilterer.Attributes; @@ -50,68 +51,84 @@ public Type FilterableType } } - public override Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public override Expression BuildExpression(ExpressionBuildContext context) { + // TODO: Decide to use context.ExpressionBody itself of use a local variable 'expressionBody'. + var expressionBody = context.ExpressionBody; + for (int i = 0; i < PropertyNames.Length; i++) { var targetPropertyName = PropertyNames[i]; - var _targetProperty = targetProperty.DeclaringType.GetProperty(targetPropertyName); + var _targetProperty = context.TargetProperty.DeclaringType.GetProperty(targetPropertyName); + + var newContext = new ExpressionBuildContext( + expressionBody, + _targetProperty, + context.FilterProperty, + context.FilterPropertyExpression, + context.FilterObject, + context.FilterObjectPropertyValue); + if (FilterableType != null) { - expressionBody = ((IFilterableType)Activator.CreateInstance(FilterableType)).BuildExpression(expressionBody, _targetProperty, filterProperty, value); + expressionBody = ((IFilterableType)Activator.CreateInstance(FilterableType)).BuildExpression(newContext); } else { - expressionBody = BuildExpressionForProperty(expressionBody, _targetProperty, filterProperty, value); + expressionBody = BuildExpressionForProperty(newContext); } } return expressionBody; } - public virtual Expression BuildExpressionForProperty(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public virtual Expression BuildExpressionForProperty(ExpressionBuildContext context) { if (FilterableType != null) { - return ((IFilterableType)Activator.CreateInstance(FilterableType)).BuildExpression(expressionBody, targetProperty, filterProperty, value); + return ((IFilterableType)Activator.CreateInstance(FilterableType)).BuildExpression(context); } - var attribute = filterProperty.GetCustomAttributes().FirstOrDefault(x => !(x is CompareToAttribute)); + var attribute = context.FilterProperty.GetCustomAttributes().FirstOrDefault(x => !(x is CompareToAttribute)); if (attribute != null) { - return attribute.BuildExpression(expressionBody, targetProperty, filterProperty, value); + return attribute.BuildExpression(context); } - return BuildDefaultExpression(expressionBody, targetProperty, filterProperty, value); + return BuildDefaultExpression(context); } - public virtual Expression BuildDefaultExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + //public virtual Expression BuildDefaultExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, MemberExpression filterPropertyExpression) + public virtual Expression BuildDefaultExpression(ExpressionBuildContext context) { - if (value is IFilter filter) + if (context.FilterObjectPropertyValue is IFilter filter) { - if (typeof(ICollection).IsAssignableFrom(targetProperty.PropertyType) || (targetProperty.PropertyType.IsConstructedGenericType && typeof(IEnumerable).IsAssignableFrom(targetProperty.PropertyType))) + if (typeof(ICollection).IsAssignableFrom(context.TargetProperty.PropertyType) || (context.TargetProperty.PropertyType.IsConstructedGenericType && typeof(IEnumerable).IsAssignableFrom(context.TargetProperty.PropertyType))) { - return Singleton.Instance.BuildExpression(expressionBody, targetProperty, filterProperty, value); + return Singleton.Instance.BuildExpression(context); } else { - var parameter = Expression.Property(expressionBody, targetProperty.Name); - return filter.BuildExpression(targetProperty.PropertyType, parameter); + var parameter = Expression.Property(context.ExpressionBody, context.TargetProperty.Name); + + return filter.BuildExpression(context.TargetProperty.PropertyType, parameter); } } - if (value is IFilterableType filterableProperty) + + + if (context.FilterObjectPropertyValue is IFilterableType filterableProperty) { - return filterableProperty.BuildExpression(expressionBody, targetProperty, filterProperty, value); + return filterableProperty.BuildExpression(context); } - else if (filterProperty.PropertyType.IsArray && !typeof(ICollection).IsAssignableFrom(targetProperty.PropertyType)) + else if (context.FilterProperty.PropertyType.IsArray && !typeof(ICollection).IsAssignableFrom(context.TargetProperty.PropertyType)) { - return Singleton.Instance.BuildExpression(expressionBody, targetProperty, filterProperty, value); + return Singleton.Instance.BuildExpression(context); } else { - return OperatorComparisonAttribute.Equal.BuildExpression(expressionBody, targetProperty, filterProperty, value); + return OperatorComparisonAttribute.Equal.BuildExpression(context); } } -} +} \ No newline at end of file diff --git a/src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs b/src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs index 0e8ee9f..26816d9 100644 --- a/src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs +++ b/src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs @@ -8,5 +8,5 @@ namespace AutoFilterer.Attributes; [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public abstract class FilteringOptionsBaseAttribute : Attribute, IFilterableType { - public abstract Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value); + public abstract Expression BuildExpression(ExpressionBuildContext context); } diff --git a/src/AutoFilterer/Attributes/IgnoreFilterAttribute.cs b/src/AutoFilterer/Attributes/IgnoreFilterAttribute.cs index d6b383c..04ab374 100644 --- a/src/AutoFilterer/Attributes/IgnoreFilterAttribute.cs +++ b/src/AutoFilterer/Attributes/IgnoreFilterAttribute.cs @@ -7,8 +7,8 @@ namespace AutoFilterer.Attributes; [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] public class IgnoreFilterAttribute : FilteringOptionsBaseAttribute { - public override Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public override Expression BuildExpression(ExpressionBuildContext context) { - return expressionBody; + return context.ExpressionBody; } } diff --git a/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs b/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs index aa36403..59e97c0 100644 --- a/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs +++ b/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs @@ -18,34 +18,35 @@ public OperatorComparisonAttribute(OperatorType operatorType) public OperatorType OperatorType { get; } - public override Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, - PropertyInfo filterProperty, object value) + public override Expression BuildExpression(ExpressionBuildContext context) { - var prop = Expression.Property(expressionBody, targetProperty.Name); - var param = Expression.Constant(value); - var targetIsNullable = targetProperty.PropertyType.IsNullable() || targetProperty.PropertyType == typeof(string); + var prop = Expression.Property(context.ExpressionBody, context.TargetProperty.Name); - if (targetProperty.PropertyType.IsNullable()) + var filterProp = Expression.Property(Expression.Constant(context.FilterObject), context.FilterProperty); + + var targetIsNullable = context.TargetProperty.PropertyType.IsNullable() || context.TargetProperty.PropertyType == typeof(string); + + if (context.TargetProperty.PropertyType.IsNullable()) prop = Expression.Property(prop, nameof(Nullable.Value)); switch (OperatorType) { case OperatorType.Equal: - return Expression.Equal(prop, param); + return Expression.Equal(prop, filterProp); case OperatorType.NotEqual: - return Expression.NotEqual(prop, param); + return Expression.NotEqual(prop, filterProp); case OperatorType.GreaterThan: - return Expression.GreaterThan(prop, param); + return Expression.GreaterThan(prop, filterProp); case OperatorType.GreaterThanOrEqual: - return Expression.GreaterThanOrEqual(prop, param); + return Expression.GreaterThanOrEqual(prop, filterProp); case OperatorType.LessThan: - return Expression.LessThan(prop, param); + return Expression.LessThan(prop, filterProp); case OperatorType.LessThanOrEqual: - return Expression.LessThanOrEqual(prop, param); + return Expression.LessThanOrEqual(prop, filterProp); case OperatorType.IsNull: - return targetIsNullable ? Expression.Equal(Expression.Property(expressionBody, targetProperty.Name), Expression.Constant(null)) : null; + return targetIsNullable ? Expression.Equal(Expression.Property(context.ExpressionBody, context.TargetProperty.Name), Expression.Constant(null)) : null; case OperatorType.IsNotNull: - return targetIsNullable ? Expression.Not(Expression.Equal(Expression.Property(expressionBody, targetProperty.Name), Expression.Constant(null))) : null; + return targetIsNullable ? Expression.Not(Expression.Equal(Expression.Property(context.ExpressionBody, context.TargetProperty.Name), Expression.Constant(null))) : null; } return Expression.Empty(); diff --git a/src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs b/src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs index 171e80e..e92ba24 100644 --- a/src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs +++ b/src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs @@ -24,34 +24,38 @@ public StringFilterOptionsAttribute(StringFilterOption option, StringComparison public StringComparison? Comparison { get; set; } - public override Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public override Expression BuildExpression(ExpressionBuildContext context) { if (Comparison == null) - return BuildExpressionWithoutComparison(this.Option, expressionBody, targetProperty, value); + { + return BuildExpressionWithoutComparison(this.Option, context); + } else - return BuildExpressionWithComparison(this.Option, expressionBody, targetProperty, value); + { + return BuildExpressionWithComparison(this.Option, context); + } } - private Expression BuildExpressionWithComparison(StringFilterOption option, Expression expressionBody, PropertyInfo property, object value) + private Expression BuildExpressionWithComparison(StringFilterOption option, ExpressionBuildContext context) { - var method = typeof(string).GetMethod(option.ToString(), types: new[] { typeof(string), typeof(StringComparison) }); + var method = typeof(string).GetMethod(option.ToString(), types: new[] { typeof(string), typeof(StringComparison) }); var comparison = Expression.Call( method: method, - instance: Expression.Property(expressionBody, property.Name), - arguments: new[] { Expression.Constant(value), Expression.Constant(Comparison) }); + instance: Expression.Property(context.ExpressionBody, context.TargetProperty.Name), + arguments: new Expression[] { context.FilterPropertyExpression, Expression.Constant(Comparison) }); return comparison; } - private Expression BuildExpressionWithoutComparison(StringFilterOption option, Expression expressionBody, PropertyInfo property, object value) + private Expression BuildExpressionWithoutComparison(StringFilterOption option, ExpressionBuildContext context) { var method = typeof(string).GetMethod(option.ToString(), types: new[] { typeof(string) }); var comparison = Expression.Call( method: method, - instance: Expression.Property(expressionBody, property.Name), - arguments: new[] { Expression.Constant(value) }); + instance: Expression.Property(context.ExpressionBody, context.TargetProperty.Name), + arguments: new[] { context.FilterPropertyExpression }); return comparison; } diff --git a/src/AutoFilterer/Attributes/ToLowerContainsComparisonAttribute.cs b/src/AutoFilterer/Attributes/ToLowerContainsComparisonAttribute.cs index 42151ac..9456baf 100644 --- a/src/AutoFilterer/Attributes/ToLowerContainsComparisonAttribute.cs +++ b/src/AutoFilterer/Attributes/ToLowerContainsComparisonAttribute.cs @@ -11,7 +11,7 @@ namespace AutoFilterer.Attributes; /// public class ToLowerContainsComparisonAttribute : FilteringOptionsBaseAttribute { - public override Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public override Expression BuildExpression(ExpressionBuildContext context) { var containsMethod = typeof(string).GetMethod(nameof(string.Contains), types: new[] { typeof(string) }); @@ -20,9 +20,9 @@ public override Expression BuildExpression(Expression expressionBody, PropertyIn var comparison = Expression.Equal( Expression.Call( method: containsMethod, - instance: Expression.Call(method: toLowerMethod, instance: Expression.Property(expressionBody, targetProperty.Name) + instance: Expression.Call(method: toLowerMethod, instance: Expression.Property(context.ExpressionBody, context.TargetProperty.Name) ), - arguments: new[] { Expression.Call(method: toLowerMethod, instance: Expression.Constant(value)) }), + arguments: new[] { Expression.Call(method: toLowerMethod, instance: context.FilterPropertyExpression) }), Expression.Constant(true)); return comparison; diff --git a/src/AutoFilterer/ExpressionBuildContext.cs b/src/AutoFilterer/ExpressionBuildContext.cs new file mode 100644 index 0000000..92eac9b --- /dev/null +++ b/src/AutoFilterer/ExpressionBuildContext.cs @@ -0,0 +1,38 @@ +using AutoFilterer.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace AutoFilterer; +public class ExpressionBuildContext +{ + public ExpressionBuildContext( + Expression expressionBody, + PropertyInfo targetProperty, + PropertyInfo filterProperty, + Expression filterPropertyExpression, + IFilter filterObject, + object propertyValue) + { + ExpressionBody = expressionBody; + TargetProperty = targetProperty; + FilterProperty = filterProperty; + FilterPropertyExpression = filterPropertyExpression; + FilterObject = filterObject; + FilterObjectPropertyValue = propertyValue; + } + + public Expression ExpressionBody { get; } + + public PropertyInfo TargetProperty { get; } + + public PropertyInfo FilterProperty { get; } + + public Expression FilterPropertyExpression { get; } + + public IFilter FilterObject { get; } + + public object FilterObjectPropertyValue { get; } +} diff --git a/src/AutoFilterer/Types/FilterBase.cs b/src/AutoFilterer/Types/FilterBase.cs index 323f34f..8297fad 100644 --- a/src/AutoFilterer/Types/FilterBase.cs +++ b/src/AutoFilterer/Types/FilterBase.cs @@ -45,9 +45,13 @@ public virtual Expression BuildExpression(Type entityType, Expression body) { try { - var val = filterProperty.GetValue(this); - if (val == null || filterProperty.GetCustomAttribute() != null) + var filterPropertyValue = filterProperty.GetValue(this); + var filterPropertyExpression = Expression.Property(Expression.Constant(this), filterProperty); + + if (filterPropertyValue == null || filterProperty.GetCustomAttribute() != null) + { continue; + } var attributes = filterProperty.GetCustomAttributes(inherit: true); @@ -68,7 +72,9 @@ public virtual Expression BuildExpression(Type entityType, Expression body) var bodyParameter = finalExpression is MemberExpression ? finalExpression : body; - var expression = attribute.BuildExpressionForProperty(bodyParameter, targetProperty, filterProperty, val); + var expression = attribute.BuildExpressionForProperty( + new ExpressionBuildContext(bodyParameter, targetProperty, filterProperty, filterPropertyExpression, this, filterPropertyValue)); + innerExpression = innerExpression.Combine(expression, attribute.CombineWith); } } diff --git a/src/AutoFilterer/Types/OperatorFilter.cs b/src/AutoFilterer/Types/OperatorFilter.cs index cfc29d1..bb1d86c 100644 --- a/src/AutoFilterer/Types/OperatorFilter.cs +++ b/src/AutoFilterer/Types/OperatorFilter.cs @@ -22,37 +22,37 @@ public class OperatorFilter : IFilterableType public virtual bool? IsNotNull { get; set; } public virtual CombineType CombineWith { get; set; } - public virtual Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public virtual Expression BuildExpression(ExpressionBuildContext context) { Expression expression = null; if (Eq != null) - expression = expression.Combine(OperatorComparisonAttribute.Equal.BuildExpression(expressionBody, targetProperty, filterProperty, Eq), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.Equal.BuildExpression(ContextFor(context, nameof(Eq), Eq)), CombineWith); if (Gt != null) - expression = expression.Combine(OperatorComparisonAttribute.GreaterThan.BuildExpression(expressionBody, targetProperty, filterProperty, Gt), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.GreaterThan.BuildExpression(ContextFor(context, nameof(Gt), Gt)), CombineWith); if (Lt != null) - expression = expression.Combine(OperatorComparisonAttribute.LessThan.BuildExpression(expressionBody, targetProperty, filterProperty, Lt), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.LessThan.BuildExpression(ContextFor(context, nameof(Lt), Lt)), CombineWith); if (Gte != null) - expression = expression.Combine(OperatorComparisonAttribute.GreaterThanOrEqual.BuildExpression(expressionBody, targetProperty, filterProperty, Gte), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.GreaterThanOrEqual.BuildExpression(ContextFor(context, nameof(Gte), Gte)), CombineWith); if (Lte != null) - expression = expression.Combine(OperatorComparisonAttribute.LessThanOrEqual.BuildExpression(expressionBody, targetProperty, filterProperty, Lte), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.LessThanOrEqual.BuildExpression(ContextFor(context, nameof(Lte), Lte)), CombineWith); if (Not != null) - expression = expression.Combine(OperatorComparisonAttribute.NotEqual.BuildExpression(expressionBody, targetProperty, filterProperty, Not), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.NotEqual.BuildExpression(ContextFor(context, nameof(Not), Not)), CombineWith); if (IsNull != null) { if (IsNull.Value) { - expression = expression.Combine(OperatorComparisonAttribute.IsNull.BuildExpression(expressionBody, targetProperty, filterProperty, IsNull.Value), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.IsNull.BuildExpression(ContextFor(context, nameof(IsNull), null)), CombineWith); } else { - expression = expression.Combine(OperatorComparisonAttribute.IsNotNull.BuildExpression(expressionBody, targetProperty, filterProperty, IsNull.Value), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.IsNotNull.BuildExpression(ContextFor(context, nameof(IsNull), null)), CombineWith); } } @@ -60,14 +60,28 @@ public virtual Expression BuildExpression(Expression expressionBody, PropertyInf { if (IsNotNull.Value) { - expression = expression.Combine(OperatorComparisonAttribute.IsNotNull.BuildExpression(expressionBody, targetProperty, filterProperty, IsNotNull.Value), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.IsNotNull.BuildExpression(ContextFor(context, nameof(IsNotNull), null)), CombineWith); } else { - expression = expression.Combine(OperatorComparisonAttribute.IsNull.BuildExpression(expressionBody, targetProperty, filterProperty, IsNotNull.Value), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.IsNull.BuildExpression(ContextFor(context, nameof(IsNotNull), null)), CombineWith); } } return expression; } + + private ExpressionBuildContext ContextFor(ExpressionBuildContext originalContext, string propertyName, T? value) + { + var innerProperty = originalContext.FilterProperty.PropertyType.GetProperty(propertyName); + var innerPropertyExpression = Expression.Property(originalContext.FilterPropertyExpression, innerProperty); + + return new ExpressionBuildContext( + originalContext.ExpressionBody, + originalContext.TargetProperty, + innerProperty, + innerPropertyExpression, + originalContext.FilterObject, + value); + } } diff --git a/src/AutoFilterer/Types/Range.cs b/src/AutoFilterer/Types/Range.cs index 59198ed..e7d40a8 100644 --- a/src/AutoFilterer/Types/Range.cs +++ b/src/AutoFilterer/Types/Range.cs @@ -57,7 +57,7 @@ public override string ToString() return $"{this.Min?.ToString() ?? "-"}|{this.Max?.ToString() ?? "-"}"; } - public virtual Expression BuildExpression(Expression body, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public virtual Expression BuildExpression(ExpressionBuildContext context) { return GetRangeComparison(); @@ -65,9 +65,11 @@ BinaryExpression GetRangeComparison() { BinaryExpression minExp = default, maxExp = default; - var propertyExpression = Expression.Property(body, targetProperty.Name); - if (targetProperty.PropertyType.IsNullable()) + var propertyExpression = Expression.Property(context.ExpressionBody, context.TargetProperty.Name); + if (context.TargetProperty.PropertyType.IsNullable()) + { propertyExpression = Expression.Property(propertyExpression, nameof(Nullable.Value)); + } if (Min != null) { @@ -75,7 +77,9 @@ BinaryExpression GetRangeComparison() propertyExpression, Expression.Constant(Min)); if (Max == null) + { return minExp; + } } if (Max != null) @@ -84,7 +88,9 @@ BinaryExpression GetRangeComparison() propertyExpression, Expression.Constant(Max)); if (Min == null) + { return maxExp; + } } return Expression.And(minExp, maxExp); diff --git a/src/AutoFilterer/Types/StringFilter.cs b/src/AutoFilterer/Types/StringFilter.cs index 1889c25..df058b8 100644 --- a/src/AutoFilterer/Types/StringFilter.cs +++ b/src/AutoFilterer/Types/StringFilter.cs @@ -87,53 +87,54 @@ public class StringFilter : IFilterableType /// public virtual StringComparison? Compare { get; set; } - public virtual Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value) + public virtual Expression BuildExpression(ExpressionBuildContext context) { Expression expression = null; if (Eq != null) - expression = expression.Combine(OperatorComparisonAttribute.Equal.BuildExpression(expressionBody, targetProperty, filterProperty, Eq), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.Equal.BuildExpression(ContextFor(context, nameof(Eq), Eq)), CombineWith); if (Not != null) - expression = expression.Combine(OperatorComparisonAttribute.NotEqual.BuildExpression(expressionBody, targetProperty, filterProperty, Not), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.NotEqual.BuildExpression(ContextFor(context, nameof(Not), Not)), CombineWith); if (IsNull != null) - expression = expression.Combine(OperatorComparisonAttribute.IsNull.BuildExpression(expressionBody, targetProperty, filterProperty, IsNull), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.IsNull.BuildExpression(ContextFor(context, nameof(IsNull), null)), CombineWith); if (IsNotNull != null) - expression = expression.Combine(OperatorComparisonAttribute.IsNotNull.BuildExpression(expressionBody, targetProperty, filterProperty, IsNotNull), CombineWith); + expression = expression.Combine(OperatorComparisonAttribute.IsNotNull.BuildExpression(ContextFor(context, nameof(IsNotNull), null)), CombineWith); if (Equals != null) - expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, Equals), CombineWith); + expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare }.BuildExpression(ContextFor(context, nameof(Equals), Equals)), CombineWith); if (Contains != null) - expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.Contains) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, Contains), CombineWith); + expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.Contains) { Comparison = Compare }.BuildExpression(ContextFor(context, nameof(Contains), Contains)), CombineWith); if (NotContains != null) - expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.Contains) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, NotContains)), CombineWith); + expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.Contains) { Comparison = Compare }.BuildExpression(ContextFor(context, nameof(NotContains), NotContains))), CombineWith); if (StartsWith != null) - expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.StartsWith) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, StartsWith), CombineWith); + expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.StartsWith) { Comparison = Compare }.BuildExpression(ContextFor(context, nameof(StartsWith), StartsWith)), CombineWith); if (NotStartsWith != null) - expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.StartsWith) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, NotStartsWith)), CombineWith); + expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.StartsWith) { Comparison = Compare }.BuildExpression(ContextFor(context, nameof(NotStartsWith), NotStartsWith))), CombineWith); if (EndsWith != null) - expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.EndsWith) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, EndsWith), CombineWith); + expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.EndsWith) { Comparison = Compare }.BuildExpression(ContextFor(context, nameof(EndsWith), EndsWith)), CombineWith); if (NotEndsWith != null) - expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.EndsWith) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, NotEndsWith)), CombineWith); - + expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.EndsWith) { Comparison = Compare }.BuildExpression(ContextFor(context, nameof(NotEndsWith), NotEndsWith))), CombineWith); if (IsEmpty != null) { if (IsEmpty.Value) { - expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, ""), CombineWith); + expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare } + .BuildExpression(ContextForConstant(context, string.Empty)), CombineWith); } else { - expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, "")), CombineWith); + expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare } + .BuildExpression(ContextForConstant(context, string.Empty))), CombineWith); } } @@ -141,14 +142,41 @@ public virtual Expression BuildExpression(Expression expressionBody, PropertyInf { if (IsNotEmpty.Value) { - expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, "")), CombineWith); + expression = expression.Combine(Expression.Not(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare } + .BuildExpression(ContextForConstant(context, string.Empty))), CombineWith); } else { - expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare }.BuildExpression(expressionBody, targetProperty, filterProperty, ""), CombineWith); + expression = expression.Combine(new StringFilterOptionsAttribute(StringFilterOption.Equals) { Comparison = Compare } + .BuildExpression(ContextForConstant(context, string.Empty)), CombineWith); } } return expression; } + + private ExpressionBuildContext ContextFor(ExpressionBuildContext originalContext, string propertyName, string value) + { + var innerProperty = originalContext.FilterProperty.PropertyType.GetProperty(propertyName); + var innerPropertyExpression = Expression.Property(originalContext.FilterPropertyExpression, innerProperty); + + return new ExpressionBuildContext( + originalContext.ExpressionBody, + originalContext.TargetProperty, + innerProperty, + innerPropertyExpression, + originalContext.FilterObject, + value); + } + + private ExpressionBuildContext ContextForConstant(ExpressionBuildContext originalContext, string value) + { + return new ExpressionBuildContext( + originalContext.ExpressionBody, + originalContext.TargetProperty, + null, + Expression.Constant(value), + originalContext.FilterObject, + value); + } } diff --git a/tests/AutoFilterer.Tests/Types/FilterBaseTests.cs b/tests/AutoFilterer.Tests/Types/FilterBaseTests.cs index e17f408..bd9f942 100644 --- a/tests/AutoFilterer.Tests/Types/FilterBaseTests.cs +++ b/tests/AutoFilterer.Tests/Types/FilterBaseTests.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Linq; using Xunit; +using AutoFilterer.Attributes; namespace AutoFilterer.Tests.Types; @@ -287,4 +288,27 @@ public void BuildExpression_IntoNullableProperty_ShouldMatchCount(List dummyData) + { + // Arrange + var filter = new GivenNameFilterObject + { + GivenName = dummyData.First().GivenName + }; + + var query = dummyData.AsQueryable(); + + // Act + var actualQuery = query.ApplyFilter(filter); + + // Assert + Assert.DoesNotContain("\"", actualQuery.Expression.ToString()); + } + + public class GivenNameFilterObject : FilterBase + { + public string GivenName { get; set; } + } } From ab58e29e41360c936e7c8cdf9883d2d2f6f9ab3e Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Sat, 7 Oct 2023 21:51:34 +0300 Subject: [PATCH 2/7] Update DynamicFilter.cs --- src/AutoFilterer.Dynamics/DynamicFilter.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/AutoFilterer.Dynamics/DynamicFilter.cs b/src/AutoFilterer.Dynamics/DynamicFilter.cs index 650a1c5..d803e3a 100644 --- a/src/AutoFilterer.Dynamics/DynamicFilter.cs +++ b/src/AutoFilterer.Dynamics/DynamicFilter.cs @@ -66,12 +66,15 @@ public Expression BuildExpression(Type entityType, Expression body) foreach (var key in this.Keys) { + var filterPropertyExpression = Expression.Property(Expression.Constant(this), key); var filterValue = new DynamicFilter(this[key]); if (IsPrimitive(key)) { var targetProperty = entityType.GetProperty(key); var value = Convert.ChangeType((string)filterValue, targetProperty.PropertyType); - var exp = OperatorComparisonAttribute.Equal.BuildExpression(body, targetProperty, filterProperty: null, value); + //var exp = OperatorComparisonAttribute.Equal.BuildExpression(body, targetProperty, filterProperty: null, value); + + var exp = OperatorComparisonAttribute.Equal.BuildExpression(new ExpressionBuildContext(body, targetProperty, null, filterPropertyExpression, this, value)); var combined = finalExpression.Combine(exp, CombineWith); finalExpression = body.Combine(combined, CombineWith); @@ -87,7 +90,7 @@ public Expression BuildExpression(Type entityType, Expression body) var comparisonKeyword = splitted[1]; if (specialKeywords.TryGetValue(comparisonKeyword, out IFilterableType filterable)) { - var exp = filterable.BuildExpression(body, targetProperty, filterProperty: null, value); + var exp = filterable.BuildExpression(new ExpressionBuildContext(body, targetProperty, null, filterPropertyExpression, this, value)); var combined = finalExpression.Combine(exp, CombineWith); finalExpression = body.Combine(combined, CombineWith); From 6e098f360f0c3f074e33053343e8904d71e2169a Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Sat, 7 Oct 2023 22:45:55 +0300 Subject: [PATCH 3/7] Make all the tests are passing --- .../Attributes/OperatorComparisonAttribute.cs | 11 +++++++++-- src/AutoFilterer/Types/OperatorFilter.cs | 8 +++++++- tests/AutoFilterer.Tests/Types/FilterBaseTests.cs | 5 ++--- tests/AutoFilterer.Tests/Types/OperatorQueryTests.cs | 7 +++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs b/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs index 59e97c0..ac822e9 100644 --- a/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs +++ b/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs @@ -22,13 +22,20 @@ public override Expression BuildExpression(ExpressionBuildContext context) { var prop = Expression.Property(context.ExpressionBody, context.TargetProperty.Name); - var filterProp = Expression.Property(Expression.Constant(context.FilterObject), context.FilterProperty); + var filterProp = context.FilterPropertyExpression; + + if (context.FilterProperty.PropertyType.IsNullable()) + { + filterProp = Expression.Property(filterProp, nameof(Nullable.Value)); + } var targetIsNullable = context.TargetProperty.PropertyType.IsNullable() || context.TargetProperty.PropertyType == typeof(string); if (context.TargetProperty.PropertyType.IsNullable()) + { prop = Expression.Property(prop, nameof(Nullable.Value)); - + } + switch (OperatorType) { case OperatorType.Equal: diff --git a/src/AutoFilterer/Types/OperatorFilter.cs b/src/AutoFilterer/Types/OperatorFilter.cs index bb1d86c..7192af5 100644 --- a/src/AutoFilterer/Types/OperatorFilter.cs +++ b/src/AutoFilterer/Types/OperatorFilter.cs @@ -27,7 +27,13 @@ public virtual Expression BuildExpression(ExpressionBuildContext context) Expression expression = null; if (Eq != null) - expression = expression.Combine(OperatorComparisonAttribute.Equal.BuildExpression(ContextFor(context, nameof(Eq), Eq)), CombineWith); + { + expression = expression.Combine( + OperatorComparisonAttribute.Equal.BuildExpression( + ContextFor(context, nameof(Eq), Eq) + ), + CombineWith); + } if (Gt != null) expression = expression.Combine(OperatorComparisonAttribute.GreaterThan.BuildExpression(ContextFor(context, nameof(Gt), Gt)), CombineWith); diff --git a/tests/AutoFilterer.Tests/Types/FilterBaseTests.cs b/tests/AutoFilterer.Tests/Types/FilterBaseTests.cs index bd9f942..3ed13bc 100644 --- a/tests/AutoFilterer.Tests/Types/FilterBaseTests.cs +++ b/tests/AutoFilterer.Tests/Types/FilterBaseTests.cs @@ -169,10 +169,9 @@ public void ApplyFilterTo_WithTwoField_ShouldMatchCount(List dummyData) filterBase.CombineWith = CombineType.Or; var orResult = query.ApplyFilter(filterBase).ToList(); - // Assert - Assert.True(result.Count == dummyData.Count(x => x.Email == filterBase.Email && x.IsActive == filterBase.IsActive)); - Assert.True(orResult.Count == dummyData.Count(x => x.Email == filterBase.Email || x.IsActive == filterBase.IsActive)); + Assert.Equal(result.Count, dummyData.Count(x => x.Email == filterBase.Email && x.IsActive == filterBase.IsActive)); + Assert.Equal(orResult.Count, dummyData.Count(x => x.Email == filterBase.Email || x.IsActive == filterBase.IsActive)); } [Theory, AutoMoqData] diff --git a/tests/AutoFilterer.Tests/Types/OperatorQueryTests.cs b/tests/AutoFilterer.Tests/Types/OperatorQueryTests.cs index 38e72b5..97a13ec 100644 --- a/tests/AutoFilterer.Tests/Types/OperatorQueryTests.cs +++ b/tests/AutoFilterer.Tests/Types/OperatorQueryTests.cs @@ -11,11 +11,17 @@ using System.Collections.Generic; using System.Linq; using Xunit; +using System; namespace AutoFilterer.Tests.Types { public class OperatorQueryTests { + public OperatorQueryTests() + { + AutoFiltererConsts.IgnoreExceptions = false; + } + [Theory, AutoMoqData(count: 64)] public void BuildExpression_TotalPageWithAnd_SholdMatchCount(List data, BookFilter_OperatorFilter_TotalPage filter) { @@ -77,6 +83,7 @@ IQueryable GetAndQuery(IQueryable query, BookFilter_OperatorFilter_T public void BuildExpression_TotalPageEqWithAnd_ShouldMatchCount(List data, int totalPage) { // Arrange + var filter = new BookFilter_OperatorFilter_TotalPage { TotalPage = new OperatorFilter From 270518a7eb055085c53562f127a4070faa8e4203 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Sat, 7 Oct 2023 22:47:56 +0300 Subject: [PATCH 4/7] CodeFactor feedbacks --- src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs | 4 ---- src/AutoFilterer/Attributes/CompareToAttribute.cs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs b/src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs index 8f8588f..b8adc4b 100644 --- a/src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs +++ b/src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs @@ -27,10 +27,6 @@ public override Expression BuildExpression(ExpressionBuildContext context) Expression.Property(context.ExpressionBody, context.TargetProperty) }); - - - // x => filter.Status.Contains(x.Status) - return containsExpression; } } diff --git a/src/AutoFilterer/Attributes/CompareToAttribute.cs b/src/AutoFilterer/Attributes/CompareToAttribute.cs index 71ff4f2..b3deadf 100644 --- a/src/AutoFilterer/Attributes/CompareToAttribute.cs +++ b/src/AutoFilterer/Attributes/CompareToAttribute.cs @@ -7,7 +7,6 @@ using System.Linq.Expressions; using System.Reflection; using System; -using AutoFilterer.Types; namespace AutoFilterer.Attributes; @@ -99,7 +98,6 @@ public virtual Expression BuildExpressionForProperty(ExpressionBuildContext cont return BuildDefaultExpression(context); } - //public virtual Expression BuildDefaultExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, MemberExpression filterPropertyExpression) public virtual Expression BuildDefaultExpression(ExpressionBuildContext context) { if (context.FilterObjectPropertyValue is IFilter filter) @@ -116,8 +114,6 @@ public virtual Expression BuildDefaultExpression(ExpressionBuildContext context) } } - - if (context.FilterObjectPropertyValue is IFilterableType filterableProperty) { return filterableProperty.BuildExpression(context); From 15f9e6682588ab3e6da6e919a75584d8cc06dff1 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 17 Oct 2023 17:22:47 +0300 Subject: [PATCH 5/7] Still handle Constant expressions --- .../FilteringOptionsBaseAttribute.cs | 19 ++++++++++++++++++- .../Attributes/OperatorComparisonAttribute.cs | 7 +------ .../StringFilterOptionsAttribute.cs | 9 ++++++--- .../Extensions/NullableExtensions.cs | 5 +++++ 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs b/src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs index 26816d9..f1e53d2 100644 --- a/src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs +++ b/src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs @@ -1,7 +1,7 @@ using AutoFilterer.Abstractions; +using AutoFilterer.Extensions; using System; using System.Linq.Expressions; -using System.Reflection; namespace AutoFilterer.Attributes; @@ -9,4 +9,21 @@ namespace AutoFilterer.Attributes; public abstract class FilteringOptionsBaseAttribute : Attribute, IFilterableType { public abstract Expression BuildExpression(ExpressionBuildContext context); + + protected Expression BuildFilterExpression(ExpressionBuildContext context) + { + var filterProp = context.FilterPropertyExpression; + + if (context.FilterProperty is null) + { + return Expression.Constant(context.FilterObjectPropertyValue); + } + + if (context.FilterProperty.PropertyType.IsNullable()) + { + filterProp = Expression.Property(filterProp, nameof(Nullable.Value)); + } + + return filterProp; + } } diff --git a/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs b/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs index ac822e9..09ed271 100644 --- a/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs +++ b/src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs @@ -22,12 +22,7 @@ public override Expression BuildExpression(ExpressionBuildContext context) { var prop = Expression.Property(context.ExpressionBody, context.TargetProperty.Name); - var filterProp = context.FilterPropertyExpression; - - if (context.FilterProperty.PropertyType.IsNullable()) - { - filterProp = Expression.Property(filterProp, nameof(Nullable.Value)); - } + var filterProp = BuildFilterExpression(context); var targetIsNullable = context.TargetProperty.PropertyType.IsNullable() || context.TargetProperty.PropertyType == typeof(string); diff --git a/src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs b/src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs index e92ba24..b3b94e7 100644 --- a/src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs +++ b/src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs @@ -38,12 +38,13 @@ public override Expression BuildExpression(ExpressionBuildContext context) private Expression BuildExpressionWithComparison(StringFilterOption option, ExpressionBuildContext context) { - var method = typeof(string).GetMethod(option.ToString(), types: new[] { typeof(string), typeof(StringComparison) }); + var method = typeof(string).GetMethod(option.ToString(), types: new[] { typeof(string), typeof(StringComparison) }); + var filterProp = BuildFilterExpression(context); var comparison = Expression.Call( method: method, instance: Expression.Property(context.ExpressionBody, context.TargetProperty.Name), - arguments: new Expression[] { context.FilterPropertyExpression, Expression.Constant(Comparison) }); + arguments: new Expression[] { filterProp, Expression.Constant(Comparison) }); return comparison; } @@ -52,10 +53,12 @@ private Expression BuildExpressionWithoutComparison(StringFilterOption option, E { var method = typeof(string).GetMethod(option.ToString(), types: new[] { typeof(string) }); + var filterProp = BuildFilterExpression(context); + var comparison = Expression.Call( method: method, instance: Expression.Property(context.ExpressionBody, context.TargetProperty.Name), - arguments: new[] { context.FilterPropertyExpression }); + arguments: new[] { filterProp }); return comparison; } diff --git a/src/AutoFilterer/Extensions/NullableExtensions.cs b/src/AutoFilterer/Extensions/NullableExtensions.cs index 92e05dc..5f441dc 100644 --- a/src/AutoFilterer/Extensions/NullableExtensions.cs +++ b/src/AutoFilterer/Extensions/NullableExtensions.cs @@ -11,6 +11,11 @@ public static class NullableExtensions { public static bool IsNullable(this Type type) { + if (type is null) + { + return false; + } + return type.Name == typeof(Nullable<>).Name; } From 1c1a1a9c9e166625ae91ffecc294920ef7f6c98b Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 17 Oct 2023 17:23:08 +0300 Subject: [PATCH 6/7] Implement ExpressionBuildContext to DynamicFilter --- src/AutoFilterer.Dynamics/DynamicFilter.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/AutoFilterer.Dynamics/DynamicFilter.cs b/src/AutoFilterer.Dynamics/DynamicFilter.cs index d803e3a..e2d952c 100644 --- a/src/AutoFilterer.Dynamics/DynamicFilter.cs +++ b/src/AutoFilterer.Dynamics/DynamicFilter.cs @@ -66,15 +66,17 @@ public Expression BuildExpression(Type entityType, Expression body) foreach (var key in this.Keys) { - var filterPropertyExpression = Expression.Property(Expression.Constant(this), key); - var filterValue = new DynamicFilter(this[key]); + var getter = this.GetType().GetMethod("get_Item"); + var filterPropertyExpression = Expression.Call(Expression.Constant(this), getter, Expression.Constant(key)); + var filterValue = getter.Invoke(this, parameters: [key]); if (IsPrimitive(key)) { var targetProperty = entityType.GetProperty(key); var value = Convert.ChangeType((string)filterValue, targetProperty.PropertyType); //var exp = OperatorComparisonAttribute.Equal.BuildExpression(body, targetProperty, filterProperty: null, value); - var exp = OperatorComparisonAttribute.Equal.BuildExpression(new ExpressionBuildContext(body, targetProperty, null, filterPropertyExpression, this, value)); + var exp = OperatorComparisonAttribute.Equal.BuildExpression( + new ExpressionBuildContext(body, targetProperty, null, filterPropertyExpression, this, value)); var combined = finalExpression.Combine(exp, CombineWith); finalExpression = body.Combine(combined, CombineWith); From c5509bc34a320ecfea90b71137f66fe9364b35dc Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 17 Oct 2023 17:27:13 +0300 Subject: [PATCH 7/7] Fix pipeline build --- src/AutoFilterer.Dynamics/DynamicFilter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AutoFilterer.Dynamics/DynamicFilter.cs b/src/AutoFilterer.Dynamics/DynamicFilter.cs index e2d952c..b1f8d85 100644 --- a/src/AutoFilterer.Dynamics/DynamicFilter.cs +++ b/src/AutoFilterer.Dynamics/DynamicFilter.cs @@ -68,7 +68,7 @@ public Expression BuildExpression(Type entityType, Expression body) { var getter = this.GetType().GetMethod("get_Item"); var filterPropertyExpression = Expression.Call(Expression.Constant(this), getter, Expression.Constant(key)); - var filterValue = getter.Invoke(this, parameters: [key]); + var filterValue = getter.Invoke(this, parameters: new object[] { key }); if (IsPrimitive(key)) { var targetProperty = entityType.GetProperty(key);