Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert to property access instead constants #69

Merged
merged 7 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/AutoFilterer.Dynamics/DynamicFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,17 @@ public Expression BuildExpression(Type entityType, Expression body)

foreach (var key in this.Keys)
{
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: new object[] { 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);
Expand All @@ -87,7 +92,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);
Expand Down
4 changes: 2 additions & 2 deletions src/AutoFilterer/Abstractions/IFilterableType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace AutoFilterer.Abstractions;

/// <summary>
/// Any property type which is able to <see cref="BuildExpression(Expression, PropertyInfo, PropertyInfo, object)"/> over source property.
/// Any property type which is able to <see cref="BuildExpression(Expression, PropertyInfo, PropertyInfo, MemberExpression)"/> over source property.

Check warning on line 8 in src/AutoFilterer/Abstractions/IFilterableType.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has cref attribute 'BuildExpression(Expression, PropertyInfo, PropertyInfo, MemberExpression)' that could not be resolved

Check warning on line 8 in src/AutoFilterer/Abstractions/IFilterableType.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has cref attribute 'BuildExpression(Expression, PropertyInfo, PropertyInfo, MemberExpression)' that could not be resolved
/// <list type="table">
/// <item>
/// You can create new Complex Types via implementing this interface. It'll be automatically called if defined in an object which is implements <see cref="IFilter"/>.
Expand All @@ -14,5 +14,5 @@
/// </summary>
public interface IFilterableType
{
Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value);
Expression BuildExpression(ExpressionBuildContext context);
}
17 changes: 10 additions & 7 deletions src/AutoFilterer/Attributes/ArraySearchFilterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,24 @@ 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);

var containsExpression = Expression.Call(
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)
});

return containsExpression;
Expand Down
15 changes: 10 additions & 5 deletions src/AutoFilterer/Attributes/CollectionFilterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -39,6 +43,7 @@ public override Expression BuildExpression(Expression expressionBody, PropertyIn
arguments: new Expression[] { prop, innerLambda }
);
}

return expressionBody;
}
}
55 changes: 34 additions & 21 deletions src/AutoFilterer/Attributes/CompareToAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,68 +50,81 @@ 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<FilteringOptionsBaseAttribute>().FirstOrDefault(x => !(x is CompareToAttribute));
var attribute = context.FilterProperty.GetCustomAttributes<FilteringOptionsBaseAttribute>().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(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<CollectionFilterAttribute>.Instance.BuildExpression(expressionBody, targetProperty, filterProperty, value);
return Singleton<CollectionFilterAttribute>.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<ArraySearchFilterAttribute>.Instance.BuildExpression(expressionBody, targetProperty, filterProperty, value);
return Singleton<ArraySearchFilterAttribute>.Instance.BuildExpression(context);
}
else
{
return OperatorComparisonAttribute.Equal.BuildExpression(expressionBody, targetProperty, filterProperty, value);
return OperatorComparisonAttribute.Equal.BuildExpression(context);
}
}
}
}
21 changes: 19 additions & 2 deletions src/AutoFilterer/Attributes/FilteringOptionsBaseAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
using AutoFilterer.Abstractions;
using AutoFilterer.Extensions;
using System;
using System.Linq.Expressions;
using System.Reflection;

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);

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<bool>.Value));
}

return filterProp;
}
}
4 changes: 2 additions & 2 deletions src/AutoFilterer/Attributes/IgnoreFilterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
33 changes: 18 additions & 15 deletions src/AutoFilterer/Attributes/OperatorComparisonAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,37 @@ 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 = BuildFilterExpression(context);

var targetIsNullable = context.TargetProperty.PropertyType.IsNullable() || context.TargetProperty.PropertyType == typeof(string);

if (context.TargetProperty.PropertyType.IsNullable())
{
prop = Expression.Property(prop, nameof(Nullable<bool>.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();
Expand Down
25 changes: 16 additions & 9 deletions src/AutoFilterer/Attributes/StringFilterOptionsAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,41 @@ 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 filterProp = BuildFilterExpression(context);

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[] { filterProp, 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 filterProp = BuildFilterExpression(context);

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[] { filterProp });

return comparison;
}
Expand Down
Loading
Loading