diff --git a/JsonApiToolkit/Controllers/JsonApiController.cs b/JsonApiToolkit/Controllers/JsonApiController.cs
index 01ab022..9035413 100644
--- a/JsonApiToolkit/Controllers/JsonApiController.cs
+++ b/JsonApiToolkit/Controllers/JsonApiController.cs
@@ -178,7 +178,7 @@ string resourceType
includeFilters.Count,
typeof(T).Name
);
- filteredQuery = filteredQuery.ApplyFilteredIncludes(mappedIncludes, includeFilters);
+ filteredQuery = filteredQuery.ApplyFilteredIncludes(mappedIncludes, includeFilters, Logger);
}
else if (mappedIncludes.Count > 0)
{
diff --git a/JsonApiToolkit/Extensions/Querying/FilterExpressionBuilder.cs b/JsonApiToolkit/Extensions/Querying/FilterExpressionBuilder.cs
deleted file mode 100644
index c7ae788..0000000
--- a/JsonApiToolkit/Extensions/Querying/FilterExpressionBuilder.cs
+++ /dev/null
@@ -1,449 +0,0 @@
-using System.Collections;
-using System.Linq.Expressions;
-using System.Reflection;
-using JsonApiToolkit.Models.Querying.Filtering;
-using Microsoft.Extensions.Logging;
-
-namespace JsonApiToolkit.Extensions.Querying;
-
-///
-/// Builds LINQ expressions for JSON:API filter parameters.
-/// Converts filter syntax to strongly-typed expressions for Entity Framework.
-///
-public static class FilterExpressionBuilder
-{
- ///
- /// Builds a composite filter expression from filter conditions and nested groups.
- /// Supports dot notation for nested properties (e.g., "user.address.city").
- ///
- /// Filter group with conditions and nested groups
- /// Parameter expression for the entity
- /// Optional logger
- /// Expression for LINQ Where clause, or null if no valid filters
- public static Expression? BuildFilterExpression(
- FilterGroup group,
- ParameterExpression parameter,
- ILogger? logger = null
- )
- {
- var expressions = new List();
-
- foreach (FilterParameter filter in group.Filters)
- {
- Expression? expr;
- if (filter.Field.Contains('.'))
- {
- expr = BuildSingleFilterExpression(parameter, filter, logger);
- }
- else
- {
- PropertyInfo? property = QueryHelpers.GetPropertyByJsonName(
- typeof(T),
- filter.Field
- );
- if (property == null)
- {
- logger?.LogWarning(
- "Property '{Field}' not found on {Type}, skipping filter",
- filter.Field,
- typeof(T).Name
- );
- continue;
- }
- expr = BuildSingleFilterExpression(parameter, filter, logger);
- }
-
- if (expr != null)
- {
- expressions.Add(expr);
- }
- else
- {
- logger?.LogWarning("Failed to build filter for '{Field}'", filter.Field);
- }
- }
-
- foreach (FilterGroup nestedGroup in group.Groups)
- {
- Expression? nestedExpr = BuildFilterExpression(nestedGroup, parameter, logger);
- if (nestedExpr != null)
- {
- expressions.Add(nestedExpr);
- }
- }
-
- if (expressions.Count == 0)
- return null;
-
- if (expressions.Count == 1)
- {
- Expression singleExpression = expressions[0];
- if (group.LogicalOperator == LogicalOperator.Not)
- {
- return Expression.Not(singleExpression);
- }
- return singleExpression;
- }
-
- Expression? combinedExpression = null;
-
- // For NOT: apply De Morgan's law
- // NOT(A AND B) = NOT(A) OR NOT(B)
- if (group.LogicalOperator == LogicalOperator.Not)
- {
- foreach (Expression expr in expressions)
- {
- var notExpr = Expression.Not(expr);
- combinedExpression =
- combinedExpression == null
- ? notExpr
- : Expression.OrElse(combinedExpression, notExpr);
- }
- }
- else
- {
- foreach (Expression expr in expressions)
- {
- if (combinedExpression == null)
- {
- combinedExpression = expr;
- }
- else
- {
- combinedExpression = group.LogicalOperator switch
- {
- LogicalOperator.And => Expression.AndAlso(combinedExpression, expr),
- LogicalOperator.Or => Expression.OrElse(combinedExpression, expr),
- _ => Expression.AndAlso(combinedExpression, expr),
- };
- }
- }
- }
-
- return combinedExpression;
- }
-
- ///
- /// Builds a filter expression for a single FilterParameter.
- ///
- /// Parameter expression for the entity
- /// Filter parameter to build
- /// Optional logger
- /// Expression for the filter, or null if invalid
- public static Expression? BuildSingleFilterExpression(
- ParameterExpression parameter,
- FilterParameter filter,
- ILogger? logger = null
- )
- {
- if (filter.Field.Contains('.'))
- {
- return BuildSafeNestedFilterExpression(parameter, filter, logger);
- }
-
- PropertyInfo? property = QueryHelpers.GetPropertyByJsonName(parameter.Type, filter.Field);
- if (property == null)
- {
- logger?.LogWarning(
- "Property '{Field}' not found on {EntityType}",
- filter.Field,
- parameter.Type.Name
- );
- return null;
- }
-
- Expression propertyAccess = Expression.Property(parameter, property);
- return BuildPropertyFilterExpression(propertyAccess, filter, logger);
- }
-
- private static Expression BuildLikeExpression(Expression property, string value)
- {
- if (property.Type == typeof(string))
- {
- // For string types, use Contains directly
- MethodInfo? method = typeof(string).GetMethod("Contains", [typeof(string)]);
- return Expression.Call(property, method!, Expression.Constant(value));
- }
-
- // For non-string types, we need to handle nulls properly
- // Check if the property is nullable
- Type? underlyingType = Nullable.GetUnderlyingType(property.Type);
- if (underlyingType != null || !property.Type.IsValueType)
- {
- // Property is nullable or reference type - need null check
- // Create: property != null && property.ToString().Contains(value)
-
- // Null check
- Expression notNullCheck = Expression.NotEqual(
- property,
- Expression.Constant(null, property.Type)
- );
-
- // ToString call with null check
- MethodInfo? toStringMethod = property.Type.GetMethod("ToString", Type.EmptyTypes);
- if (toStringMethod == null)
- {
- // If no ToString method, use Object.ToString
- toStringMethod = typeof(object).GetMethod("ToString", Type.EmptyTypes);
- property = Expression.Convert(property, typeof(object));
- }
-
- MethodCallExpression toStringCall = Expression.Call(property, toStringMethod!);
- MethodInfo? containsMethod = typeof(string).GetMethod("Contains", [typeof(string)]);
- Expression containsCall = Expression.Call(
- toStringCall,
- containsMethod!,
- Expression.Constant(value)
- );
-
- // Combine: not null && contains
- return Expression.AndAlso(notNullCheck, containsCall);
- }
- else
- {
- // Non-nullable value type - can call ToString directly
- MethodInfo? toStringMethod = property.Type.GetMethod("ToString", Type.EmptyTypes);
- MethodCallExpression toStringCall = Expression.Call(property, toStringMethod!);
- MethodInfo? containsMethod = typeof(string).GetMethod("Contains", [typeof(string)]);
- return Expression.Call(toStringCall, containsMethod!, Expression.Constant(value));
- }
- }
-
- private static Expression BuildInExpression(
- Expression property,
- string value,
- Type propertyType
- )
- {
- var rawValues = value
- .Split(',')
- .Select(v => v.Trim())
- .Where(v => !string.IsNullOrEmpty(v))
- .ToList();
-
- var convertedValues = new List