@@ -24,13 +24,13 @@ private static MethodInfo GetEnumerableMethod(string methodName, int parameterCo
2424 private static MethodInfo GetStringMethod ( string methodName , params Type [ ] parameterTypes ) =>
2525 typeof ( string ) . GetMethod ( methodName , parameterTypes ?? Type . EmptyTypes ) ;
2626
27- public static Result < Expression > Evaluate ( QueryExpression expression , ParameterExpression parameterExpression , PropertyMappingTree propertyMappingTree )
27+ public static Result < Expression > Evaluate ( QueryExpression expression , ParameterExpression parameterExpression , PropertyMappingTree propertyMappingTree , int maxPropertyMappingDepth = 5 )
2828 {
2929 if ( expression == null ) return Result . Fail ( "Expression cannot be null" ) ;
3030 if ( parameterExpression == null ) return Result . Fail ( "Parameter expression cannot be null" ) ;
3131 if ( propertyMappingTree == null ) return Result . Fail ( "Property mapping tree cannot be null" ) ;
3232
33- var context = new FilterEvaluationContext ( parameterExpression , propertyMappingTree ) ;
33+ var context = new FilterEvaluationContext ( parameterExpression , propertyMappingTree , maxPropertyMappingDepth ) ;
3434 return EvaluateExpression ( expression , context ) ;
3535 }
3636
@@ -223,13 +223,13 @@ private static Result<ConstantExpression> CreateConstantExpression(QueryExpressi
223223 return literal switch
224224 {
225225 IntegerLiteral intLit => CreateIntegerOrEnumConstant ( intLit . Value , expression . Type ) ,
226- DateLiteral dateLit => Result . Ok ( CreateDateConstant ( dateLit , expression ) ) ,
226+ DateLiteral dateLit => Result . Ok ( CreateDateConstant ( dateLit , expression . Type ) ) ,
227227 GuidLiteral guidLit => Result . Ok ( Expression . Constant ( guidLit . Value , expression . Type ) ) ,
228228 DecimalLiteral decLit => Result . Ok ( Expression . Constant ( decLit . Value , expression . Type ) ) ,
229229 FloatLiteral floatLit => Result . Ok ( Expression . Constant ( floatLit . Value , expression . Type ) ) ,
230230 DoubleLiteral dblLit => Result . Ok ( Expression . Constant ( dblLit . Value , expression . Type ) ) ,
231231 StringLiteral strLit => CreateStringOrEnumConstant ( strLit . Value , expression . Type ) ,
232- DateTimeLiteral dtLit => Result . Ok ( Expression . Constant ( dtLit . Value , expression . Type ) ) ,
232+ DateTimeLiteral dtLit => Result . Ok ( CreateDateTimeConstant ( dtLit , expression . Type ) ) ,
233233 BooleanLiteral boolLit => Result . Ok ( Expression . Constant ( boolLit . Value , expression . Type ) ) ,
234234 NullLiteral _ => Result . Ok ( Expression . Constant ( null , expression . Type ) ) ,
235235 _ => Result . Fail ( $ "Unsupported literal type: { literal . GetType ( ) . Name } ")
@@ -244,35 +244,66 @@ private static Result<ConstantExpression> CreateConstantExpression(QueryExpressi
244244 return Result . Ok ( ( constantResult . Value , property ) ) ;
245245 }
246246
247- private static ConstantExpression CreateDateConstant ( DateLiteral dateLiteral , Expression expression )
247+ private static ConstantExpression CreateDateConstant ( DateLiteral dateLiteral , Type targetType )
248248 {
249- if ( expression . Type == typeof ( DateTime ? ) )
249+ var underlyingType = Nullable . GetUnderlyingType ( targetType ) ?? targetType ;
250+
251+ if ( underlyingType == typeof ( DateTimeOffset ) )
250252 {
251- return Expression . Constant ( dateLiteral . Value . Date , typeof ( DateTime ) ) ;
253+ var value = new DateTimeOffset ( dateLiteral . Value . Date , TimeSpan . Zero ) ;
254+ return Expression . Constant ( value , targetType ) ;
252255 }
253- else
256+
257+ return Expression . Constant ( dateLiteral . Value . Date , targetType ) ;
258+ }
259+
260+ private static ConstantExpression CreateDateTimeConstant ( DateTimeLiteral dtLiteral , Type targetType )
261+ {
262+ var underlyingType = Nullable . GetUnderlyingType ( targetType ) ?? targetType ;
263+
264+ if ( underlyingType == typeof ( DateTimeOffset ) )
254265 {
255- return Expression . Constant ( dateLiteral . Value . Date , expression . Type ) ;
266+ if ( DateTimeOffset . TryParse ( dtLiteral . TokenLiteral ( ) , System . Globalization . CultureInfo . InvariantCulture , System . Globalization . DateTimeStyles . None , out var dto ) )
267+ {
268+ return Expression . Constant ( dto , targetType ) ;
269+ }
270+ return Expression . Constant ( new DateTimeOffset ( dtLiteral . Value ) , targetType ) ;
256271 }
272+
273+ return Expression . Constant ( dtLiteral . Value , targetType ) ;
257274 }
258275
259276 private static Expression CreateContainsExpression ( Expression expression , ConstantExpression value )
260277 {
261278 var expressionToLower = Expression . Call ( expression , StringToLowerMethod ) ;
262279 var valueToLower = Expression . Call ( value , StringToLowerMethod ) ;
263- return Expression . Call ( expressionToLower , StringContainsMethod , valueToLower ) ;
280+ var containsCall = Expression . Call ( expressionToLower , StringContainsMethod , valueToLower ) ;
281+
282+ // Guard against null: (expression != null && expression.ToLower().Contains(value.ToLower()))
283+ var nullCheck = Expression . NotEqual ( expression , Expression . Constant ( null , typeof ( string ) ) ) ;
284+ return Expression . AndAlso ( nullCheck , containsCall ) ;
264285 }
265286
266287 private static Expression CreateEqualityExpression ( Expression expression , ConstantExpression value , bool isEqual )
267288 {
268- // For string comparisons, make them case-insensitive
289+ // For string comparisons, make them case-insensitive with null safety
269290 if ( expression . Type == typeof ( string ) )
270291 {
271292 var expressionToLower = Expression . Call ( expression , StringToLowerMethod ) ;
272293 var valueToLower = Expression . Call ( value , StringToLowerMethod ) ;
273- return isEqual
274- ? Expression . Equal ( expressionToLower , valueToLower )
275- : Expression . NotEqual ( expressionToLower , valueToLower ) ;
294+ var nullCheck = Expression . NotEqual ( expression , Expression . Constant ( null , typeof ( string ) ) ) ;
295+
296+ if ( isEqual )
297+ {
298+ // eq: (expression != null && expression.ToLower() == value.ToLower())
299+ return Expression . AndAlso ( nullCheck , Expression . Equal ( expressionToLower , valueToLower ) ) ;
300+ }
301+ else
302+ {
303+ // ne: (expression == null || expression.ToLower() != value.ToLower())
304+ var isNull = Expression . Equal ( expression , Expression . Constant ( null , typeof ( string ) ) ) ;
305+ return Expression . OrElse ( isNull , Expression . NotEqual ( expressionToLower , valueToLower ) ) ;
306+ }
276307 }
277308
278309 // For non-string types, use standard equality
@@ -430,7 +461,7 @@ private static Result<Expression> EvaluateLambdaBodyPropertyPath(InfixExpression
430461 propertyPath . Segments [ 0 ] . Equals ( context . CurrentLambda . ParameterName , StringComparison . OrdinalIgnoreCase ) ;
431462
432463 return isLambdaParameterPath
433- ? EvaluateLambdaPropertyPath ( exp , propertyPath , context . CurrentLambda . Parameter )
464+ ? EvaluateLambdaPropertyPath ( exp , propertyPath , context . CurrentLambda . Parameter , context . MaxPropertyMappingDepth )
434465 : EvaluatePropertyPathExpression ( exp , propertyPath , context ) ;
435466 }
436467
@@ -474,14 +505,14 @@ private static Result<Expression> EvaluateLambdaBodyLogicalOperator(InfixExpress
474505 } ;
475506 }
476507
477- private static Result < Expression > EvaluateLambdaPropertyPath ( InfixExpression exp , PropertyPath propertyPath , ParameterExpression lambdaParameter )
508+ private static Result < Expression > EvaluateLambdaPropertyPath ( InfixExpression exp , PropertyPath propertyPath , ParameterExpression lambdaParameter , int maxPropertyMappingDepth )
478509 {
479510 // Skip the first segment (lambda parameter name) and build property path from lambda parameter
480511 var current = ( Expression ) lambdaParameter ;
481512 var elementType = lambdaParameter . Type ;
482513
483514 // Build property path from lambda parameter
484- var pathResult = BuildLambdaPropertyPath ( current , propertyPath . Segments . Skip ( 1 ) . ToList ( ) , elementType ) ;
515+ var pathResult = BuildLambdaPropertyPath ( current , propertyPath . Segments . Skip ( 1 ) . ToList ( ) , elementType , maxPropertyMappingDepth ) ;
485516 if ( pathResult . IsFailed ) return pathResult ;
486517
487518 current = pathResult . Value ;
@@ -517,10 +548,10 @@ private static Expression CreateAllExpression(MemberExpression collection, Lambd
517548 return Expression . AndAlso ( hasElements , allMatch ) ;
518549 }
519550
520- private static Result < Expression > BuildLambdaPropertyPath ( Expression startExpression , List < string > segments , Type elementType )
551+ private static Result < Expression > BuildLambdaPropertyPath ( Expression startExpression , List < string > segments , Type elementType , int maxPropertyMappingDepth )
521552 {
522553 var current = startExpression ;
523- var currentMappingTree = PropertyMappingTreeBuilder . BuildMappingTree ( elementType , GetDefaultMaxDepth ( ) ) ;
554+ var currentMappingTree = PropertyMappingTreeBuilder . BuildMappingTree ( elementType , maxPropertyMappingDepth ) ;
524555
525556 foreach ( var segment in segments )
526557 {
@@ -541,10 +572,6 @@ private static Result<Expression> BuildLambdaPropertyPath(Expression startExpres
541572 return Result . Ok ( current ) ;
542573 }
543574
544- private static int GetDefaultMaxDepth ( )
545- {
546- return new QueryOptions ( ) . MaxPropertyMappingDepth ;
547- }
548575
549576 private static Type GetCollectionElementType ( Type collectionType )
550577 {
0 commit comments