From 29e63dff59e2e44358a4b55a036ddcd0ef78916a Mon Sep 17 00:00:00 2001 From: mohit Date: Tue, 20 Aug 2024 10:03:16 -0400 Subject: [PATCH 1/5] revert --- src/Gridify/Builder/LinqQueryBuilder.cs | 74 ++++++++----------- src/Gridify/Reflection/MethodInfoHelper.cs | 15 ---- test/Gridify.Tests/GridifyExtensionsShould.cs | 2 +- 3 files changed, 31 insertions(+), 60 deletions(-) diff --git a/src/Gridify/Builder/LinqQueryBuilder.cs b/src/Gridify/Builder/LinqQueryBuilder.cs index 9c788c5f..9493ac72 100644 --- a/src/Gridify/Builder/LinqQueryBuilder.cs +++ b/src/Gridify/Builder/LinqQueryBuilder.cs @@ -123,7 +123,7 @@ protected override Expression> BuildAlwaysFalseQuery(ParameterExpr MethodInfoHelper.GetCaseAwareEqualsMethod(), body, GetValueExpression(body.Type, value), - Expression.Constant(StringComparison.InvariantCultureIgnoreCase) + Expression.Constant(StringComparison.OrdinalIgnoreCase) ); break; case SyntaxKind.Equal when !valueExpression.IsNullOrDefault: @@ -149,7 +149,7 @@ protected override Expression> BuildAlwaysFalseQuery(ParameterExpr MethodInfoHelper.GetCaseAwareEqualsMethod(), body, GetValueExpression(body.Type, value), - Expression.Constant(StringComparison.InvariantCultureIgnoreCase)) + Expression.Constant(StringComparison.OrdinalIgnoreCase)) ); break; case SyntaxKind.NotEqual when !valueExpression.IsNullOrDefault: @@ -194,74 +194,60 @@ protected override Expression> BuildAlwaysFalseQuery(ParameterExpr case SyntaxKind.LessOrEqualThan when areBothStrings: be = GetLessThanOrEqualExpression(body, valueExpression, value); break; - case SyntaxKind.Like or SyntaxKind.NotLike: - if (areBothStrings && (valueExpression.IsCaseInsensitive || mapper.Configuration.CaseInsensitiveFiltering)) + case SyntaxKind.Like: // TODO: test and support case sensitivity + be = Expression.Call(body, MethodInfoHelper.GetStringContainsMethod(), GetValueExpression(body.Type, value)); + break; + case SyntaxKind.NotLike: + be = Expression.Not(Expression.Call(body, MethodInfoHelper.GetStringContainsMethod(), GetValueExpression(body.Type, value))); + break; + case SyntaxKind.StartsWith: + if (body.Type != typeof(string)) { - be = Expression.Call( - body, - MethodInfoHelper.GetCaseAwareStringContainsMethod(), - GetValueExpression(body.Type, value), - Expression.Constant(StringComparison.InvariantCultureIgnoreCase) - ); + body = Expression.Call(body, MethodInfoHelper.GetToStringMethod()); + be = Expression.Call(body, MethodInfoHelper.GetStartWithMethod(), GetValueExpression(body.Type, value?.ToString())); } else { - be = Expression.Call(body, MethodInfoHelper.GetStringContainsMethod(), GetValueExpression(body.Type, value)); + be = Expression.Call(body, MethodInfoHelper.GetStartWithMethod(), GetValueExpression(body.Type, value)); } - if (op.Kind == SyntaxKind.NotLike) - be = Expression.Not(be); - break; - case SyntaxKind.StartsWith or SyntaxKind.NotStartsWith: + case SyntaxKind.EndsWith: if (body.Type != typeof(string)) { body = Expression.Call(body, MethodInfoHelper.GetToStringMethod()); - be = Expression.Call(body, MethodInfoHelper.GetStartWithMethod(), GetValueExpression(body.Type, value?.ToString())); - } - else if (areBothStrings && (valueExpression.IsCaseInsensitive || mapper.Configuration.CaseInsensitiveFiltering)) - { - be = Expression.Call( - body, - MethodInfoHelper.GetCaseAwareStartsWithMethod(), - GetValueExpression(body.Type, value), - Expression.Constant(StringComparison.InvariantCultureIgnoreCase) - ); + be = Expression.Call(body, MethodInfoHelper.GetEndsWithMethod(), GetValueExpression(body.Type, value?.ToString())); } else { - be = Expression.Call(body, MethodInfoHelper.GetStartWithMethod(), GetValueExpression(body.Type, value)); + be = Expression.Call(body, MethodInfoHelper.GetEndsWithMethod(), GetValueExpression(body.Type, value)); } - if (op.Kind == SyntaxKind.NotStartsWith) - be = Expression.Not(be); - break; - case SyntaxKind.EndsWith or SyntaxKind.NotEndsWith: + case SyntaxKind.NotStartsWith: if (body.Type != typeof(string)) { body = Expression.Call(body, MethodInfoHelper.GetToStringMethod()); - be = Expression.Call(body, MethodInfoHelper.GetStartWithMethod(), GetValueExpression(body.Type, value?.ToString())); + be = Expression.Not(Expression.Call(body, MethodInfoHelper.GetStartWithMethod(), GetValueExpression(body.Type, value?.ToString()))); + } + else + { + be = Expression.Not(Expression.Call(body, MethodInfoHelper.GetStartWithMethod(), GetValueExpression(body.Type, value))); } - else if (areBothStrings && (valueExpression.IsCaseInsensitive || mapper.Configuration.CaseInsensitiveFiltering)) + + break; + case SyntaxKind.NotEndsWith: + if (body.Type != typeof(string)) { - be = Expression.Call( - body, - MethodInfoHelper.GetCaseAwareEndsWithMethod(), - GetValueExpression(body.Type, value), - Expression.Constant(StringComparison.InvariantCultureIgnoreCase) - ); + body = Expression.Call(body, MethodInfoHelper.GetToStringMethod()); + be = Expression.Not(Expression.Call(body, MethodInfoHelper.GetEndsWithMethod(), GetValueExpression(body.Type, value?.ToString()))); } else { - be = Expression.Call(body, MethodInfoHelper.GetEndsWithMethod(), GetValueExpression(body.Type, value)); + be = Expression.Not(Expression.Call(body, MethodInfoHelper.GetEndsWithMethod(), GetValueExpression(body.Type, value))); } - if (op.Kind == SyntaxKind.NotEndsWith) - be = Expression.Not(be); - break; - case SyntaxKind.CustomOperator: var token = (SyntaxToken)op; var customOperator = GridifyGlobalConfiguration.CustomOperators.Operators.First(q => q.GetOperator() == token!.Text); @@ -533,7 +519,7 @@ private BinaryExpression GetGreaterThanExpression(Expression body, ValueExpressi private static ConstantExpression GetStringComparisonCaseExpression(bool isCaseInsensitive) { return isCaseInsensitive - ? Expression.Constant(StringComparison.InvariantCultureIgnoreCase) + ? Expression.Constant(StringComparison.OrdinalIgnoreCase) : Expression.Constant(StringComparison.Ordinal); } diff --git a/src/Gridify/Reflection/MethodInfoHelper.cs b/src/Gridify/Reflection/MethodInfoHelper.cs index fe9d8c15..64414ddf 100644 --- a/src/Gridify/Reflection/MethodInfoHelper.cs +++ b/src/Gridify/Reflection/MethodInfoHelper.cs @@ -61,23 +61,8 @@ public static MethodInfo GetCaseAwareContainsMethod(Type tp) return typeof(Enumerable).GetMethods().Last(x => x.Name == "Contains").MakeGenericMethod(tp); } - public static MethodInfo GetCaseAwareStringContainsMethod() - { - return typeof(string).GetMethod("Contains", [typeof(string), typeof(StringComparison)])!; - } - public static MethodInfo GetCaseAwareEqualsMethod() { return typeof(string).GetMethod("Equals", [typeof(string), typeof(string), typeof(StringComparison)])!; } - - public static MethodInfo GetCaseAwareStartsWithMethod() - { - return typeof(string).GetMethod("StartsWith", [typeof(string), typeof(StringComparison)])!; - } - - public static MethodInfo GetCaseAwareEndsWithMethod() - { - return typeof(string).GetMethod("EndsWith", [typeof(string), typeof(StringComparison)])!; - } } diff --git a/test/Gridify.Tests/GridifyExtensionsShould.cs b/test/Gridify.Tests/GridifyExtensionsShould.cs index 167de72e..f0266cb2 100644 --- a/test/Gridify.Tests/GridifyExtensionsShould.cs +++ b/test/Gridify.Tests/GridifyExtensionsShould.cs @@ -581,7 +581,7 @@ public void ApplyFiltering_GreaterThanOrEqualBetweenTwoStrings() public void ApplyFiltering_GreaterThanOrEqual_CaseInsensitive_BetweenTwoStrings() { var actual = _fakeRepository.AsQueryable().ApplyFiltering("name >= j/i").ToList(); - var expected = _fakeRepository.Where(q => string.Compare(q.Name, "j", StringComparison.InvariantCultureIgnoreCase) >= 0).ToList(); + var expected = _fakeRepository.Where(q => string.Compare(q.Name, "j", StringComparison.OrdinalIgnoreCase) >= 0).ToList(); Assert.Equal(expected.Count, actual.Count); Assert.Equal(expected, actual); From b8e40860ba13fda880f1c67cdc328fd4468383f2 Mon Sep 17 00:00:00 2001 From: mohit Date: Tue, 20 Aug 2024 11:11:15 -0400 Subject: [PATCH 2/5] Revert "fix: issue 193 and case sensitivity support improvements" This reverts commit 132139bc --- src/Gridify/Builder/BaseQueryBuilder.cs | 20 +++++- src/Gridify/Builder/LinqQueryBuilder.cs | 67 ++++--------------- src/Gridify/Reflection/MethodInfoHelper.cs | 15 ++--- .../Issue193Tests.cs | 65 ------------------ 4 files changed, 35 insertions(+), 132 deletions(-) delete mode 100644 test/EntityFrameworkPostgreSqlIntegrationTests/Issue193Tests.cs diff --git a/src/Gridify/Builder/BaseQueryBuilder.cs b/src/Gridify/Builder/BaseQueryBuilder.cs index 3bb31807..592dbe88 100644 --- a/src/Gridify/Builder/BaseQueryBuilder.cs +++ b/src/Gridify/Builder/BaseQueryBuilder.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.Linq.Expressions; +using Gridify.Reflection; using Gridify.Syntax; namespace Gridify.Builder; @@ -123,7 +124,7 @@ public TQuery Build(ExpressionSyntax expression) return (result, isNested); } - var query = BuildQuery(mapTarget.Body, mapTarget.Parameters[0], right, op, gMap.Convertor); + var query = BuildQuery(mapTarget.Body, mapTarget.Parameters[0], right, op, gMap.Convertor, false); if (query == null) return null; if (hasIndexer) @@ -157,7 +158,8 @@ private static object AddIndexerNullCheck(LambdaExpression mapTarget, object que ParameterExpression parameter, ValueExpressionSyntax valueExpression, ISyntaxNode op, - Func? convertor) + Func? convertor, + bool isNested) { // Remove the boxing for value types if (body.NodeType == ExpressionType.Convert) body = ((UnaryExpression)body).Operand; @@ -197,7 +199,7 @@ private static object AddIndexerNullCheck(LambdaExpression mapTarget, object que { return BuildAlwaysFalseQuery(parameter); } - + if (value is DateTime dateTime) { if (mapper.Configuration.DefaultDateTimeKind.HasValue) @@ -207,6 +209,18 @@ private static object AddIndexerNullCheck(LambdaExpression mapTarget, object que } } + // handle case-Insensitive search + if (value is not null && (valueExpression.IsCaseInsensitive + || (mapper.Configuration.CaseInsensitiveFiltering && !isNested && body.Type == typeof(string))) + && op.Kind is not SyntaxKind.GreaterThan + && op.Kind is not SyntaxKind.LessThan + && op.Kind is not SyntaxKind.GreaterOrEqualThan + && op.Kind is not SyntaxKind.LessOrEqualThan) + { + value = value.ToString()?.ToLower(); + body = Expression.Call(body, MethodInfoHelper.GetToLowerMethod()); + } + var query = BuildQueryAccordingToValueType(body, parameter, value, op, valueExpression); return query; } diff --git a/src/Gridify/Builder/LinqQueryBuilder.cs b/src/Gridify/Builder/LinqQueryBuilder.cs index 9493ac72..3a4464e8 100644 --- a/src/Gridify/Builder/LinqQueryBuilder.cs +++ b/src/Gridify/Builder/LinqQueryBuilder.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Data; -using System.Globalization; using System.Linq; using System.Linq.Expressions; using Gridify.Reflection; @@ -25,7 +23,8 @@ public class LinqQueryBuilder(IGridifyMapper mapper) : BaseQueryBuilder> BuildAlwaysFalseQuery(ParameterExpr switch (op.Kind) { - case SyntaxKind.Equal when !valueExpression.IsNullOrDefault && areBothStrings && (valueExpression.IsCaseInsensitive || mapper.Configuration.CaseInsensitiveFiltering): - be = Expression.Call( - null, - MethodInfoHelper.GetCaseAwareEqualsMethod(), - body, - GetValueExpression(body.Type, value), - Expression.Constant(StringComparison.OrdinalIgnoreCase) - ); - break; case SyntaxKind.Equal when !valueExpression.IsNullOrDefault: be = Expression.Equal(body, GetValueExpression(body.Type, value)); break; @@ -142,15 +132,6 @@ protected override Expression> BuildAlwaysFalseQuery(ParameterExpr : Expression.Equal(body, Expression.Default(body.Type)); } - break; - case SyntaxKind.NotEqual when !valueExpression.IsNullOrDefault && areBothStrings && (valueExpression.IsCaseInsensitive || mapper.Configuration.CaseInsensitiveFiltering): - be = Expression.Not(Expression.Call( - null, - MethodInfoHelper.GetCaseAwareEqualsMethod(), - body, - GetValueExpression(body.Type, value), - Expression.Constant(StringComparison.OrdinalIgnoreCase)) - ); break; case SyntaxKind.NotEqual when !valueExpression.IsNullOrDefault: be = Expression.NotEqual(body, GetValueExpression(body.Type, value)); @@ -194,7 +175,7 @@ protected override Expression> BuildAlwaysFalseQuery(ParameterExpr case SyntaxKind.LessOrEqualThan when areBothStrings: be = GetLessThanOrEqualExpression(body, valueExpression, value); break; - case SyntaxKind.Like: // TODO: test and support case sensitivity + case SyntaxKind.Like: be = Expression.Call(body, MethodInfoHelper.GetStringContainsMethod(), GetValueExpression(body.Type, value)); break; case SyntaxKind.NotLike: @@ -285,7 +266,7 @@ protected override Expression> CombineWithOrOperator(Expression a.NodeType == ExpressionType.Lambda) as LambdaExpression; - var conditionExp = BuildQuery(targetExp!.Body, targetExp.Parameters[0], value, op, gMap.Convertor); + var conditionExp = BuildQuery(targetExp!.Body, targetExp.Parameters[0], value, op, gMap.Convertor, true); if (conditionExp is not LambdaExpression lambdaExp) return null; @@ -322,14 +303,14 @@ private static LambdaExpression ParseMethodCallExpression(MethodCallExpression e { case MemberExpression member: { - if (op.Kind is not (SyntaxKind.Equal or SyntaxKind.NotEqual) || - !member.Type.IsSimpleTypeCollection(out _)) return GetAnyExpression(member, predicate); - return predicate.Body switch + if (op.Kind is SyntaxKind.Equal or SyntaxKind.NotEqual && + member.Type.IsSimpleTypeCollection(out _) && + predicate.Body is BinaryExpression binaryExpression) { - BinaryExpression binaryExpression => GetContainsExpression(member, binaryExpression, op), - MethodCallExpression { Method.Name: "Equals" } methodCallExpression => GetCaseSensitiveContainsExpression(member, methodCallExpression, op), - _ => GetAnyExpression(member, predicate) - }; + return GetContainsExpression(member, binaryExpression, op); + } + + return GetAnyExpression(member, predicate); } case MethodCallExpression { Method.Name: "SelectMany" } subExp when subExp.Arguments.Last() @@ -368,28 +349,6 @@ when subExp.Arguments.Last() is LambdaExpression wherePredicate && } } - private static LambdaExpression GetCaseSensitiveContainsExpression(MemberExpression member, MethodCallExpression methodCallExpression, ISyntaxNode op) - { - var param = GetParameterExpression(member); - var prop = GetPropertyOrField(member, param); - - var tp = prop.Type.IsGenericType - ? prop.Type.GenericTypeArguments.First() // list - : prop.Type.GetElementType(); // array - - if (tp == null) throw new GridifyFilteringException($"Can not detect the '{member.Member.Name}' property type."); - - var containsMethod = MethodInfoHelper.GetCaseAwareContainsMethod(tp); - var ignoreCaseComparerExpression = Expression.Constant(StringComparer.InvariantCultureIgnoreCase); - var value = methodCallExpression.Arguments[1]; - Expression containsExp = Expression.Call(containsMethod, prop, value, ignoreCaseComparerExpression); - if (op.Kind == SyntaxKind.NotEqual) - { - containsExp = Expression.Not(containsExp); - } - return GetExpressionWithNullCheck(prop, param, containsExp); - } - private static LambdaExpression GetContainsExpression(MemberExpression member, BinaryExpression binaryExpression, ISyntaxNode op) { var param = GetParameterExpression(member); @@ -516,9 +475,9 @@ private BinaryExpression GetGreaterThanExpression(Expression body, ValueExpressi GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); } - private static ConstantExpression GetStringComparisonCaseExpression(bool isCaseInsensitive) + private ConstantExpression GetStringComparisonCaseExpression(bool isCaseInsensitive) { - return isCaseInsensitive + return isCaseInsensitive || mapper.Configuration.CaseInsensitiveFiltering ? Expression.Constant(StringComparison.OrdinalIgnoreCase) : Expression.Constant(StringComparison.Ordinal); } diff --git a/src/Gridify/Reflection/MethodInfoHelper.cs b/src/Gridify/Reflection/MethodInfoHelper.cs index 64414ddf..0a4a7e2f 100644 --- a/src/Gridify/Reflection/MethodInfoHelper.cs +++ b/src/Gridify/Reflection/MethodInfoHelper.cs @@ -6,6 +6,11 @@ namespace Gridify.Reflection; public static class MethodInfoHelper { + public static MethodInfo GetToLowerMethod() + { + return typeof(string).GetMethod("ToLower", [])!; + } + public static MethodInfo GetAnyMethod(Type type) { return typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(type); @@ -55,14 +60,4 @@ public static MethodInfo GetSelectMethod(this Type type) { return typeof(Enumerable).GetMethods().First(m => m.Name == "Select").MakeGenericMethod([type, type]); } - - public static MethodInfo GetCaseAwareContainsMethod(Type tp) - { - return typeof(Enumerable).GetMethods().Last(x => x.Name == "Contains").MakeGenericMethod(tp); - } - - public static MethodInfo GetCaseAwareEqualsMethod() - { - return typeof(string).GetMethod("Equals", [typeof(string), typeof(string), typeof(StringComparison)])!; - } } diff --git a/test/EntityFrameworkPostgreSqlIntegrationTests/Issue193Tests.cs b/test/EntityFrameworkPostgreSqlIntegrationTests/Issue193Tests.cs deleted file mode 100644 index f3e77631..00000000 --- a/test/EntityFrameworkPostgreSqlIntegrationTests/Issue193Tests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Gridify; -using Xunit; - -namespace EntityFrameworkPostgreSqlIntegrationTests; - -public class Issue193Tests -{ - - [Fact] - public void ApplyFiltering_WithCaseInsensitiveOperator_ShouldReturnExpectedResult() - { - // arrange - var dataSource = Test.GetTestDataSource(); - - var expected = dataSource.Where(q => q.FavouriteColorList.Contains("red", StringComparer.InvariantCultureIgnoreCase) | - q.FavouriteColorList.Contains("blue", StringComparer.InvariantCultureIgnoreCase)) - .ToList(); - - // act - var actual = dataSource.ApplyFiltering("FavouriteColorList=red/i|FavouriteColorList=blue/i").ToList(); - - // assert - Assert.NotEmpty(expected); - Assert.NotEmpty(actual); - Assert.Equal(expected.Count, actual.Count); - } - - [Fact] - public void ApplyFiltering_WithDefaultCaseInsensitiveFiltering_ShouldReturnExpectedResult() - { - // arrange - var dataSource = Test.GetTestDataSource(); - - var expected = dataSource.Where(q => q.FavouriteColorList.Contains("red", StringComparer.InvariantCultureIgnoreCase) | - q.FavouriteColorList.Contains("blue", StringComparer.InvariantCultureIgnoreCase)) - .ToList(); - - var mapper = new GridifyMapper(q => q.CaseInsensitiveFiltering = true).GenerateMappings(); - - // act - var actual = dataSource.ApplyFiltering("FavouriteColorList=red|FavouriteColorList=blue", mapper).ToList(); - - // assert - Assert.NotEmpty(expected); - Assert.NotEmpty(actual); - Assert.Equal(expected.Count, actual.Count); - } - - class Test - { - public string[] FavouriteColorList { get; set; } - - public static IQueryable GetTestDataSource() - { - return new List() - { - new() { FavouriteColorList = ["Green", "Blue"] }, - new() { FavouriteColorList = ["White", "Yellow"] }, - new() { FavouriteColorList = ["Red", "Orange"] }, - new() { FavouriteColorList = ["Purple", "Pink"] }, - new() { FavouriteColorList = ["Black", "Gray"] } - }.AsQueryable(); - } - } -} From 9123b3afe2b2e3cf39580e2b964f5242fcee2210 Mon Sep 17 00:00:00 2001 From: mohit Date: Tue, 20 Aug 2024 11:28:24 -0400 Subject: [PATCH 3/5] adding the 193 tests commented so we can fix this later. --- .../Gridify.Tests/IssueTests/Issue193Tests.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 test/Gridify.Tests/IssueTests/Issue193Tests.cs diff --git a/test/Gridify.Tests/IssueTests/Issue193Tests.cs b/test/Gridify.Tests/IssueTests/Issue193Tests.cs new file mode 100644 index 00000000..5112e724 --- /dev/null +++ b/test/Gridify.Tests/IssueTests/Issue193Tests.cs @@ -0,0 +1,68 @@ +// using System; +// using System.Collections.Generic; +// using System.Linq; +// using Gridify; +// using Xunit; +// +// namespace EntityFrameworkPostgreSqlIntegrationTests; +// +// public class Issue193Tests +// { +// +// [Fact] +// public void ApplyFiltering_WithCaseInsensitiveOperator_ShouldReturnExpectedResult() +// { +// // arrange +// var dataSource = Test.GetTestDataSource(); +// +// var expected = dataSource.Where(q => q.FavouriteColorList.Contains("red", StringComparer.InvariantCultureIgnoreCase) | +// q.FavouriteColorList.Contains("blue", StringComparer.InvariantCultureIgnoreCase)) +// .ToList(); +// +// // act +// var actual = dataSource.ApplyFiltering("FavouriteColorList=red/i|FavouriteColorList=blue/i").ToList(); +// +// // assert +// Assert.NotEmpty(expected); +// Assert.NotEmpty(actual); +// Assert.Equal(expected.Count, actual.Count); +// } +// +// [Fact] +// public void ApplyFiltering_WithDefaultCaseInsensitiveFiltering_ShouldReturnExpectedResult() +// { +// // arrange +// var dataSource = Test.GetTestDataSource(); +// +// var expected = dataSource.Where(q => q.FavouriteColorList.Contains("red", StringComparer.InvariantCultureIgnoreCase) | +// q.FavouriteColorList.Contains("blue", StringComparer.InvariantCultureIgnoreCase)) +// .ToList(); +// +// var mapper = new GridifyMapper(q => q.CaseInsensitiveFiltering = true).GenerateMappings(); +// +// // act +// var actual = dataSource.ApplyFiltering("FavouriteColorList=red|FavouriteColorList=blue", mapper).ToList(); +// +// // assert +// Assert.NotEmpty(expected); +// Assert.NotEmpty(actual); +// Assert.Equal(expected.Count, actual.Count); +// } +// +// class Test +// { +// public string[] FavouriteColorList { get; set; } +// +// public static IQueryable GetTestDataSource() +// { +// return new List() +// { +// new() { FavouriteColorList = ["Green", "Blue"] }, +// new() { FavouriteColorList = ["White", "Yellow"] }, +// new() { FavouriteColorList = ["Red", "Orange"] }, +// new() { FavouriteColorList = ["Purple", "Pink"] }, +// new() { FavouriteColorList = ["Black", "Gray"] } +// }.AsQueryable(); +// } +// } +// } From 7092bd16c0967522f8e7acd0e791946de8054c54 Mon Sep 17 00:00:00 2001 From: mohit Date: Tue, 20 Aug 2024 14:33:09 -0400 Subject: [PATCH 4/5] fix: issue 204 --- src/Gridify/Builder/LinqQueryBuilder.cs | 3 ++ .../Gridify.Tests/IssueTests/Issue204Tests.cs | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 test/Gridify.Tests/IssueTests/Issue204Tests.cs diff --git a/src/Gridify/Builder/LinqQueryBuilder.cs b/src/Gridify/Builder/LinqQueryBuilder.cs index 3a4464e8..ada3fee2 100644 --- a/src/Gridify/Builder/LinqQueryBuilder.cs +++ b/src/Gridify/Builder/LinqQueryBuilder.cs @@ -364,6 +364,9 @@ private static LambdaExpression GetContainsExpression(MemberExpression member, B if (op.Kind == SyntaxKind.NotEqual) { containsExp = Expression.Not(containsExp); + // issue #204 we need to add or null check as well for Not Contains + containsExp = Expression.OrElse(Expression.Equal(prop, Expression.Constant(null)), containsExp); + return Expression.Lambda(containsExp, param); } return GetExpressionWithNullCheck(prop, param, containsExp); } diff --git a/test/Gridify.Tests/IssueTests/Issue204Tests.cs b/test/Gridify.Tests/IssueTests/Issue204Tests.cs new file mode 100644 index 00000000..1da20b23 --- /dev/null +++ b/test/Gridify.Tests/IssueTests/Issue204Tests.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Gridify.Tests.IssueTests; + +public class Issue204Tests +{ + [Fact] + public void ApplyFiltering_NotEquals_ShouldMatch_NullItems() + { + // arrange + var dataSource = new List() + { + new() {FavouriteColorList = ["Green", "Blue"]}, + new() {FavouriteColorList = ["White", "Yellow"]}, + new() { FavouriteColorList = null }, + }.AsQueryable(); + + var expected = dataSource.Where(q => q.FavouriteColorList == null || !q.FavouriteColorList.Contains("Green")).ToList(); + var actual = dataSource.ApplyFiltering("FavouriteColorList!=Green").ToList(); + + // assert + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + private class Test + { + public List FavouriteColorList { get; set; } + } +} \ No newline at end of file From 4ecf66888ab2042bf3fc3746c54d17bed740d282 Mon Sep 17 00:00:00 2001 From: mohit Date: Wed, 21 Aug 2024 15:49:37 -0400 Subject: [PATCH 5/5] fix: exists and not exists with null values and case insensitive comparison --- src/Gridify/Builder/BaseQueryBuilder.cs | 9 +++- test/Gridify.Tests/GridifyExtensionsShould.cs | 1 + .../Gridify.Tests/IssueTests/Issue202Tests.cs | 48 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/Gridify.Tests/IssueTests/Issue202Tests.cs diff --git a/src/Gridify/Builder/BaseQueryBuilder.cs b/src/Gridify/Builder/BaseQueryBuilder.cs index 592dbe88..9875e0fb 100644 --- a/src/Gridify/Builder/BaseQueryBuilder.cs +++ b/src/Gridify/Builder/BaseQueryBuilder.cs @@ -217,8 +217,13 @@ private static object AddIndexerNullCheck(LambdaExpression mapTarget, object que && op.Kind is not SyntaxKind.GreaterOrEqualThan && op.Kind is not SyntaxKind.LessOrEqualThan) { - value = value.ToString()?.ToLower(); - body = Expression.Call(body, MethodInfoHelper.GetToLowerMethod()); + var strLowerValue = value.ToString()?.ToLower(); + value = strLowerValue; + + if(!string.IsNullOrEmpty(strLowerValue)) + { + body = Expression.Call(body, MethodInfoHelper.GetToLowerMethod()); + } } var query = BuildQueryAccordingToValueType(body, parameter, value, op, valueExpression); diff --git a/test/Gridify.Tests/GridifyExtensionsShould.cs b/test/Gridify.Tests/GridifyExtensionsShould.cs index f0266cb2..2e049955 100644 --- a/test/Gridify.Tests/GridifyExtensionsShould.cs +++ b/test/Gridify.Tests/GridifyExtensionsShould.cs @@ -52,6 +52,7 @@ public static IEnumerable GetSampleData() lst.Add(new TestClass(27, "ali reza", null)); lst.Add(new TestClass(27, "[ali]", null)); lst.Add(new TestClass(28, @"Esc/\pe", null)); + lst.Add(new TestClass(29, @"NullTag", null, tag: null)); return lst; } diff --git a/test/Gridify.Tests/IssueTests/Issue202Tests.cs b/test/Gridify.Tests/IssueTests/Issue202Tests.cs new file mode 100644 index 00000000..c6b93061 --- /dev/null +++ b/test/Gridify.Tests/IssueTests/Issue202Tests.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Gridify.Tests.IssueTests; + +public class Issue202Tests +{ + private readonly List _fakeRepository = [.. GridifyExtensionsShould.GetSampleData()]; + + [Fact] + // Issue #202 + public void ApplyFiltering_NotExists_GlobalCaseInsensitiveSearch() + { + var mapper = new GridifyMapper(m => m.CaseInsensitiveFiltering = true).GenerateMappings(); + + var gq = new GridifyQuery { Filter = "tag=" }; + + var expected = _fakeRepository.Where(q => string.IsNullOrEmpty(q.Tag)).ToList(); + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq, mapper) + .ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + // Issue #202 + public void ApplyFiltering_Exists_GlobalCaseInsensitiveSearch() + { + var mapper = new GridifyMapper(m => m.CaseInsensitiveFiltering = true).GenerateMappings(); + + var gq = new GridifyQuery { Filter = "tag!=" }; + + var expected = _fakeRepository.Where(q => !string.IsNullOrEmpty(q.Tag)).ToList(); + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq, mapper) + .ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } +} \ No newline at end of file