From f39944d0e48be4d55d6d53c5d6c620221bcf8a44 Mon Sep 17 00:00:00 2001 From: Kevin Dost Date: Fri, 23 Oct 2020 18:15:58 +0200 Subject: [PATCH] Add filtering on null. --- Sieve/Services/SieveProcessor.cs | 39 ++++++++++++++++++++------------ SieveUnitTests/General.cs | 36 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/Sieve/Services/SieveProcessor.cs b/Sieve/Services/SieveProcessor.cs index c57bc4d..a7d2a9c 100644 --- a/Sieve/Services/SieveProcessor.cs +++ b/Sieve/Services/SieveProcessor.cs @@ -56,6 +56,7 @@ namespace Sieve.Services where TFilterTerm : IFilterTerm, new() where TSortTerm : ISortTerm, new() { + private const string nullFilterValue = "null"; private readonly IOptions _options; private readonly ISieveCustomSortMethods _customSortMethods; private readonly ISieveCustomFilterMethods _customFilterMethods; @@ -179,15 +180,14 @@ namespace Sieve.Services var (fullPropertyName, property) = GetSieveProperty(false, true, filterTermName); if (property != null) { - var converter = TypeDescriptor.GetConverter(property.PropertyType); Expression propertyValue = parameter; Expression nullCheck = null; - - foreach (var name in fullPropertyName.Split('.')) + var names = fullPropertyName.Split('.'); + for (var i = 0; i < names.Length; i++) { - propertyValue = Expression.PropertyOrField(propertyValue, name); + propertyValue = Expression.PropertyOrField(propertyValue, names[i]); - if (propertyValue.Type.IsNullable()) + if (i != names.Length - 1 && propertyValue.Type.IsNullable()) { nullCheck = GenerateFilterNullCheckExpression(propertyValue, nullCheck); } @@ -195,15 +195,13 @@ namespace Sieve.Services if (filterTerm.Values == null) continue; + var converter = TypeDescriptor.GetConverter(property.PropertyType); foreach (var filterTermValue in filterTerm.Values) { - - dynamic constantVal = converter.CanConvertFrom(typeof(string)) - ? converter.ConvertFrom(filterTermValue) - : Convert.ChangeType(filterTermValue, property.PropertyType); - - Expression filterValue = GetClosureOverConstant(constantVal, property.PropertyType); - + var isFilterTermValueNull = filterTermValue.ToLower() == nullFilterValue; + var filterValue = isFilterTermValueNull + ? Expression.Constant(null, property.PropertyType) + : ConvertStringValueToConstantExpression(filterTermValue, property, converter); if (filterTerm.OperatorIsCaseInsensitive) { @@ -223,9 +221,13 @@ namespace Sieve.Services expression = Expression.Not(expression); } - if (nullCheck != null) + var filterValueNullCheck = !isFilterTermValueNull && propertyValue.Type.IsNullable() + ? GenerateFilterNullCheckExpression(propertyValue, nullCheck) + : nullCheck; + + if (filterValueNullCheck != null) { - expression = Expression.AndAlso(nullCheck, expression); + expression = Expression.AndAlso(filterValueNullCheck, expression); } if (innerExpression == null) @@ -272,6 +274,15 @@ namespace Sieve.Services : Expression.AndAlso(nullCheckExpression, Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type))); } + private Expression ConvertStringValueToConstantExpression(string value, PropertyInfo property, TypeConverter converter) + { + dynamic constantVal = converter.CanConvertFrom(typeof(string)) + ? converter.ConvertFrom(value) + : Convert.ChangeType(value, property.PropertyType); + + return GetClosureOverConstant(constantVal, property.PropertyType); + } + private static Expression GetExpression(TFilterTerm filterTerm, dynamic filterValue, dynamic propertyValue) { switch (filterTerm.OperatorParsed) diff --git a/SieveUnitTests/General.cs b/SieveUnitTests/General.cs index 07e220f..17df973 100644 --- a/SieveUnitTests/General.cs +++ b/SieveUnitTests/General.cs @@ -528,5 +528,41 @@ namespace SieveUnitTests Assert.AreEqual(sortedPosts[0].Id, 2); Assert.AreEqual(sortedPosts[1].Id, 1); } + + [TestMethod] + public void FilteringOnNullWorks() + { + var posts = new List + { + new Post() { + Id = 1, + Title = null, + LikeCount = 0, + IsDraft = false, + CategoryId = null, + TopComment = null, + FeaturedComment = null + }, + new Post() { + Id = 2, + Title = null, + LikeCount = 0, + IsDraft = false, + CategoryId = null, + TopComment = null, + FeaturedComment = new Comment { Id = 1, Text = null } + }, + }.AsQueryable(); + + var model = new SieveModel() + { + Filters = "FeaturedComment.Text==null", + }; + + var result = _processor.Apply(model, posts); + Assert.AreEqual(1, result.Count()); + var filteredPosts = result.ToList(); + Assert.AreEqual(filteredPosts[0].Id, 2); + } } }