From 9ccc75db746eba4b1a5838053d1831c2b2763293 Mon Sep 17 00:00:00 2001 From: Marccus Date: Thu, 12 Dec 2024 14:56:18 -0300 Subject: [PATCH] Add support to like when filter is list --- .../Filter/ExpressionFactory.cs | 12 +++--- .../Filter/FiltersExtensions.cs | 41 +++++++++++++++---- .../Filter/WhereOperator.cs | 5 ++- tests/RestFulTests/FilterTests.cs | 12 ++++-- tests/RestFulTests/Models/UserSearch.cs | 4 +- 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/AspNetCore.IQueryable.Extensions/Filter/ExpressionFactory.cs b/src/AspNetCore.IQueryable.Extensions/Filter/ExpressionFactory.cs index f621aa1..d83ca77 100644 --- a/src/AspNetCore.IQueryable.Extensions/Filter/ExpressionFactory.cs +++ b/src/AspNetCore.IQueryable.Extensions/Filter/ExpressionFactory.cs @@ -59,8 +59,6 @@ static bool IsNullableType(Type t) internal static WhereClause GetCriteria(ICustomQueryable model, PropertyInfo propertyInfo) { bool isCollection = propertyInfo.IsPropertyACollection(); - //if (!isCollection && propertyInfo.IsPropertyObject(model)) - // return null; var criteria = new WhereClause(); @@ -70,12 +68,12 @@ internal static WhereClause GetCriteria(ICustomQueryable model, PropertyInfo pro { var data = (QueryOperatorAttribute)attr.First(a => a.GetType() == typeof(QueryOperatorAttribute)); criteria.UpdateAttributeData(data); - if (data.Operator != WhereOperator.Contains && isCollection) - throw new ArgumentException($"{propertyInfo.Name} - For array the only Operator available is Contains"); - } + if (isCollection && (data.Operator != WhereOperator.Contains && data.Operator != WhereOperator.ContainsWithLikeForList)) + throw new ArgumentException($"{propertyInfo.Name} - For array the only Operator available is Contains and ContainsWithLikeForList"); - if (isCollection) - criteria.Operator = WhereOperator.Contains; + if (!isCollection && data.Operator == WhereOperator.ContainsWithLikeForList) + throw new ArgumentException($"{propertyInfo.Name} - LikeInList Operator is only available to string arrays"); + } var customValue = propertyInfo.GetValue(model, null); if (customValue == null) diff --git a/src/AspNetCore.IQueryable.Extensions/Filter/FiltersExtensions.cs b/src/AspNetCore.IQueryable.Extensions/Filter/FiltersExtensions.cs index b727aca..229c351 100644 --- a/src/AspNetCore.IQueryable.Extensions/Filter/FiltersExtensions.cs +++ b/src/AspNetCore.IQueryable.Extensions/Filter/FiltersExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -99,30 +100,32 @@ private static Expression GetExpression(ExpressionParser expression) typeof(string).GetMethods() .First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1), expression.FilterBy); + case WhereOperator.ContainsWithLikeForList: + return ContainsWithLikeForListExpression(expression); default: return Expression.Equal(expression.FieldToFilter, expression.FilterBy); } } - + private static Expression LessThanOrEqualWhenNullable(Expression e1, Expression e2) { if (IsNullableType(e1.Type) && !IsNullableType(e2.Type)) e2 = Expression.Convert(e2, e1.Type); - + else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type)) e1 = Expression.Convert(e1, e2.Type); - + return Expression.LessThanOrEqual(e1, e2); } - + private static Expression GreaterThanOrEqualWhenNullable(Expression e1, Expression e2) { if (IsNullableType(e1.Type) && !IsNullableType(e2.Type)) e2 = Expression.Convert(e2, e1.Type); - + else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type)) e1 = Expression.Convert(e1, e2.Type); - + return Expression.GreaterThanOrEqual(e1, e2); } @@ -158,9 +161,33 @@ private static Expression ContainsExpression(ExpressionParser expressio return Expression.Call(expression.FieldToFilter, methodToApplyContains, expression.FilterBy); } - } + private static Expression ContainsWithLikeForListExpression(ExpressionParser expression) + { + Expression orExpression = null; + + if (expression.FilterBy is ConstantExpression constantExpression && constantExpression.Value is IEnumerable patterns) + { + foreach (var pattern in patterns) + { + var likePattern = Expression.Constant(pattern); + var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) }); + var containsExpression = Expression.Call(expression.FieldToFilter, containsMethod, likePattern); + + if (orExpression == null) + { + orExpression = containsExpression; + } + else + { + orExpression = Expression.OrElse(orExpression, containsExpression); + } + } + } + + return orExpression; + } } } diff --git a/src/AspNetCore.IQueryable.Extensions/Filter/WhereOperator.cs b/src/AspNetCore.IQueryable.Extensions/Filter/WhereOperator.cs index 9966dd6..cdc95c8 100644 --- a/src/AspNetCore.IQueryable.Extensions/Filter/WhereOperator.cs +++ b/src/AspNetCore.IQueryable.Extensions/Filter/WhereOperator.cs @@ -10,8 +10,9 @@ public enum WhereOperator LessThanOrEqualTo, Contains, StartsWith, - LessThanOrEqualWhenNullable, + LessThanOrEqualWhenNullable, GreaterThanOrEqualWhenNullable, - EqualsWhenNullable + EqualsWhenNullable, + ContainsWithLikeForList } } \ No newline at end of file diff --git a/tests/RestFulTests/FilterTests.cs b/tests/RestFulTests/FilterTests.cs index 4b2b84e..16acb00 100644 --- a/tests/RestFulTests/FilterTests.cs +++ b/tests/RestFulTests/FilterTests.cs @@ -22,12 +22,16 @@ public FilterTests() [Fact] public void Should_Filter_By_Has_Name_Attribute() { + int startIndex = new Random().Next(0, _users.Last().FirstName.Length - 3); + var randomStr = _users.First(x => x.FirstName.Length > 4).FirstName.Substring(startIndex, 3); + var userSearch = new UserSearch() { - Name = _users.Last().FirstName + Names = new() { randomStr } }; var sortingByFieldName = _users.AsQueryable().Filter(userSearch); + sortingByFieldName.Should().HaveCountGreaterOrEqualTo(1); } @@ -49,7 +53,7 @@ public void Should_Apply_Allfilter_Based_In_Class() { var userSearch = new UserSearch() { - Name = _users.Last().FirstName, + Names = new() { _users.Last().FirstName }, Username = _users.Last().Username }; var sortingByFieldName = _users.AsQueryable().Filter(userSearch); @@ -73,7 +77,7 @@ public void Should_Apply_Or_In_Filter() { var userSearch = new UserSearch() { - Name = _users.First().FirstName, + Names = new() { _users.First().FirstName }, Ssn = _users.First().SocialNumber.Identification, Username = _users.Last().Username }; @@ -88,7 +92,7 @@ public void Should_Apply_Exclusive_Or_In_Filter() { var userSearch = new UserSearch() { - Name = _users.First().FirstName, + Names = new() { _users.First().FirstName }, Username = _users.Last().Username }; var sortingByFieldName = _users.AsQueryable().Filter(userSearch); diff --git a/tests/RestFulTests/Models/UserSearch.cs b/tests/RestFulTests/Models/UserSearch.cs index 24dcb39..ed2fb2f 100644 --- a/tests/RestFulTests/Models/UserSearch.cs +++ b/tests/RestFulTests/Models/UserSearch.cs @@ -16,8 +16,8 @@ public class UserSearch : IQueryPaging, IQuerySort [QueryOperator(Operator = WhereOperator.GreaterThan)] public DateTime? Birthday { get; set; } - [QueryOperator(Operator = WhereOperator.Contains, HasName = "Firstname")] - public string Name { get; set; } + [QueryOperator(Operator = WhereOperator.ContainsWithLikeForList, HasName = "Firstname")] + public List Names { get; set; } [QueryOperator(Operator = WhereOperator.Contains, HasName = "SocialNumber.Identification")] public string Ssn { get; set; }