@@ -23,13 +23,13 @@ private static MethodInfo GetEnumerableMethod(string methodName, int parameterCo
2323 private static MethodInfo GetStringMethod ( string methodName , params Type [ ] parameterTypes ) =>
2424 typeof ( string ) . GetMethod ( methodName , parameterTypes ?? Type . EmptyTypes ) ;
2525
26- public static Result < Expression > Evaluate ( QueryExpression expression , ParameterExpression parameterExpression , Dictionary < string , string > propertyMapping )
26+ public static Result < Expression > Evaluate ( QueryExpression expression , ParameterExpression parameterExpression , PropertyMappingTree propertyMappingTree )
2727 {
2828 if ( expression == null ) return Result . Fail ( "Expression cannot be null" ) ;
2929 if ( parameterExpression == null ) return Result . Fail ( "Parameter expression cannot be null" ) ;
30- if ( propertyMapping == null ) return Result . Fail ( "Property mapping cannot be null" ) ;
30+ if ( propertyMappingTree == null ) return Result . Fail ( "Property mapping tree cannot be null" ) ;
3131
32- var context = new FilterEvaluationContext ( parameterExpression , propertyMapping ) ;
32+ var context = new FilterEvaluationContext ( parameterExpression , propertyMappingTree ) ;
3333 return EvaluateExpression ( expression , context ) ;
3434 }
3535
@@ -52,7 +52,7 @@ private static Result<Expression> EvaluatePropertyPathExpression(
5252 ( Expression ) context . CurrentLambda . Parameter :
5353 context . RootParameter ;
5454
55- var propertyPathResult = BuildPropertyPath ( propertyPath , baseExpression , context . PropertyMapping ) ;
55+ var propertyPathResult = BuildPropertyPath ( propertyPath , baseExpression , context . PropertyMappingTree ) ;
5656 if ( propertyPathResult . IsFailed ) return Result . Fail ( propertyPathResult . Errors ) ;
5757
5858 var ( finalProperty , nullChecks ) = propertyPathResult . Value ;
@@ -72,67 +72,62 @@ private static Result<Expression> EvaluatePropertyPathExpression(
7272 private static Result < ( MemberExpression Property , List < Expression > NullChecks ) > BuildPropertyPath (
7373 PropertyPath propertyPath ,
7474 Expression startExpression ,
75- Dictionary < string , string > propertyMapping )
75+ PropertyMappingTree propertyMappingTree )
7676 {
7777 var current = startExpression ;
7878 var nullChecks = new List < Expression > ( ) ;
79- var currentPropertyMapping = propertyMapping ;
79+ var currentMappingTree = propertyMappingTree ;
8080
8181 foreach ( var ( segment , isLast ) in propertyPath . Segments . Select ( ( s , i ) => ( s , i == propertyPath . Segments . Count - 1 ) ) )
8282 {
83- var propertyResult = ResolvePropertySegment ( segment , current , currentPropertyMapping ) ;
84- if ( propertyResult . IsFailed ) return Result . Fail ( propertyResult . Errors ) ;
83+ if ( ! currentMappingTree . TryGetProperty ( segment , out var propertyNode ) )
84+ return Result . Fail ( $ "Invalid property ' { segment } ' in path" ) ;
8585
86- current = propertyResult . Value ;
86+ current = Expression . Property ( current , propertyNode . ActualPropertyName ) ;
8787
8888 // Add null check for intermediate reference types only (not the final property)
8989 if ( ! isLast && IsNullableReferenceType ( current . Type ) )
9090 {
9191 nullChecks . Add ( Expression . NotEqual ( current , Expression . Constant ( null , current . Type ) ) ) ;
9292 }
9393
94- // Update property mapping for nested object navigation
94+ // Navigate to nested mapping for next segment
9595 if ( ! isLast )
9696 {
97- currentPropertyMapping = PropertyMappingHelper . CreatePropertyMapping ( current . Type ) ;
97+ if ( ! propertyNode . HasNestedMapping )
98+ return Result . Fail ( $ "Property '{ segment } ' does not support nested navigation") ;
99+
100+ currentMappingTree = propertyNode . NestedMapping ;
98101 }
99102 }
100103
101104 return Result . Ok ( ( ( MemberExpression ) current , nullChecks ) ) ;
102105 }
103106
104- private static Result < MemberExpression > ResolvePropertySegment (
105- string segment ,
106- Expression parentExpression ,
107- Dictionary < string , string > propertyMapping )
108- {
109- if ( ! propertyMapping . TryGetValue ( segment , out var propertyName ) )
110- return Result . Fail ( $ "Invalid property '{ segment } ' in path") ;
111-
112- return Expression . Property ( parentExpression , propertyName ) ;
113- }
114-
115107 private static Result < MemberExpression > ResolvePropertyPathForCollection (
116108 PropertyPath propertyPath ,
117109 Expression baseExpression ,
118- Dictionary < string , string > propertyMapping )
110+ PropertyMappingTree propertyMappingTree )
119111 {
120112 var current = baseExpression ;
121- var currentPropertyMapping = propertyMapping ;
113+ var currentMappingTree = propertyMappingTree ;
122114
123115 for ( int i = 0 ; i < propertyPath . Segments . Count ; i ++ )
124116 {
125117 var segment = propertyPath . Segments [ i ] ;
126- var propertyResult = ResolvePropertySegment ( segment , current , currentPropertyMapping ) ;
127- if ( propertyResult . IsFailed )
128- return Result . Fail ( propertyResult . Errors ) ;
129118
130- current = propertyResult . Value ;
119+ if ( ! currentMappingTree . TryGetProperty ( segment , out var propertyNode ) )
120+ return Result . Fail ( $ "Invalid property '{ segment } ' in lambda expression property path") ;
131121
132- // Update property mapping for nested object navigation
122+ current = Expression . Property ( current , propertyNode . ActualPropertyName ) ;
123+
124+ // Navigate to nested mapping for next segment
133125 if ( i < propertyPath . Segments . Count - 1 )
134126 {
135- currentPropertyMapping = PropertyMappingHelper . CreatePropertyMapping ( current . Type ) ;
127+ if ( ! propertyNode . HasNestedMapping )
128+ return Result . Fail ( $ "Property '{ segment } ' does not support nested navigation in lambda expression") ;
129+
130+ currentMappingTree = propertyNode . NestedMapping ;
136131 }
137132 }
138133
@@ -146,7 +141,7 @@ private static bool IsNullableReferenceType(Type type)
146141
147142 private static bool IsPrimitiveType ( Type type )
148143 {
149- return type . IsPrimitive || type == typeof ( string ) || type == typeof ( decimal ) ||
144+ return type . IsPrimitive || type == typeof ( string ) || type == typeof ( decimal ) ||
150145 type == typeof ( DateTime ) || type == typeof ( Guid ) ||
151146 Nullable . GetUnderlyingType ( type ) != null ;
152147 }
@@ -313,7 +308,7 @@ private static Result<Expression> EvaluateIdentifierExpression(InfixExpression e
313308 {
314309 var identifier = exp . Left . TokenLiteral ( ) ;
315310
316- if ( ! context . PropertyMapping . TryGetValue ( identifier , out var propertyName ) )
311+ if ( ! context . PropertyMappingTree . TryGetProperty ( identifier , out var propertyNode ) )
317312 {
318313 return Result . Fail ( $ "Invalid property '{ identifier } ' within filter") ;
319314 }
@@ -322,7 +317,7 @@ private static Result<Expression> EvaluateIdentifierExpression(InfixExpression e
322317 ( Expression ) context . CurrentLambda . Parameter :
323318 context . RootParameter ;
324319
325- var identifierProperty = Expression . Property ( baseExpression , propertyName ) ;
320+ var identifierProperty = Expression . Property ( baseExpression , propertyNode . ActualPropertyName ) ;
326321 return EvaluateValueComparison ( exp , identifierProperty ) ;
327322 }
328323
@@ -374,7 +369,7 @@ private static Result<Expression> EvaluateLambdaExpression(QueryLambdaExpression
374369 ( Expression ) context . CurrentLambda . Parameter :
375370 context . RootParameter ;
376371
377- var collectionResult = ResolveCollectionProperty ( lambdaExp . Property , baseExpression , context . PropertyMapping ) ;
372+ var collectionResult = ResolveCollectionProperty ( lambdaExp . Property , baseExpression , context . PropertyMappingTree ) ;
378373 if ( collectionResult . IsFailed ) return Result . Fail ( collectionResult . Errors ) ;
379374
380375 var collectionProperty = collectionResult . Value ;
@@ -396,19 +391,19 @@ private static Expression CreateLambdaLinqCall(string function, MemberExpression
396391 : CreateAllExpression ( collection , lambda , elementType ) ;
397392 }
398393
399- private static Result < MemberExpression > ResolveCollectionProperty ( QueryExpression property , Expression baseExpression , Dictionary < string , string > propertyMapping )
394+ private static Result < MemberExpression > ResolveCollectionProperty ( QueryExpression property , Expression baseExpression , PropertyMappingTree propertyMappingTree )
400395 {
401396 switch ( property )
402397 {
403398 case Identifier identifier :
404- if ( ! propertyMapping . TryGetValue ( identifier . TokenLiteral ( ) , out var propertyName ) )
399+ if ( ! propertyMappingTree . TryGetProperty ( identifier . TokenLiteral ( ) , out var propertyNode ) )
405400 {
406401 return Result . Fail ( $ "Invalid property '{ identifier . TokenLiteral ( ) } ' in lambda expression") ;
407402 }
408- return Expression . Property ( baseExpression , propertyName ) ;
403+ return Expression . Property ( baseExpression , propertyNode . ActualPropertyName ) ;
409404
410405 case PropertyPath propertyPath :
411- return ResolvePropertyPathForCollection ( propertyPath , baseExpression , propertyMapping ) ;
406+ return ResolvePropertyPathForCollection ( propertyPath , baseExpression , propertyMappingTree ) ;
412407
413408 default :
414409 return Result . Fail ( $ "Unsupported property type in lambda expression: { property . GetType ( ) . Name } ") ;
@@ -459,16 +454,16 @@ private static Result<Expression> EvaluateLambdaBodyIdentifier(InfixExpression e
459454 {
460455 return EvaluateValueComparison ( exp , context . CurrentLambda . Parameter ) ;
461456 }
462-
457+
463458 return Result . Fail ( $ "Lambda parameter '{ context . CurrentLambda . ParameterName } ' cannot be used directly in comparisons for complex types") ;
464459 }
465460
466- if ( ! context . PropertyMapping . TryGetValue ( identifierName , out var propertyName ) )
461+ if ( ! context . PropertyMappingTree . TryGetProperty ( identifierName , out var propertyNode ) )
467462 {
468463 return Result . Fail ( $ "Invalid property '{ identifierName } ' within filter") ;
469464 }
470465
471- var identifierProperty = Expression . Property ( context . RootParameter , propertyName ) ;
466+ var identifierProperty = Expression . Property ( context . RootParameter , propertyNode . ActualPropertyName ) ;
472467 return EvaluateValueComparison ( exp , identifierProperty ) ;
473468 }
474469
@@ -495,9 +490,9 @@ private static Result<Expression> EvaluateLambdaPropertyPath(InfixExpression exp
495490 var elementType = lambdaParameter . Type ;
496491
497492 // Build property path from lambda parameter
498- var pathResult = BuildLambdaPropertyPath ( current , propertyPath . Segments . Skip ( 1 ) , elementType ) ;
493+ var pathResult = BuildLambdaPropertyPath ( current , propertyPath . Segments . Skip ( 1 ) . ToList ( ) , elementType ) ;
499494 if ( pathResult . IsFailed ) return pathResult ;
500-
495+
501496 current = pathResult . Value ;
502497
503498 var finalProperty = ( MemberExpression ) current ;
@@ -531,36 +526,35 @@ private static Expression CreateAllExpression(MemberExpression collection, Lambd
531526 return Expression . AndAlso ( hasElements , allMatch ) ;
532527 }
533528
534- private static Result < Expression > BuildLambdaPropertyPath ( Expression startExpression , IEnumerable < string > segments , Type elementType )
529+ private static Result < Expression > BuildLambdaPropertyPath ( Expression startExpression , List < string > segments , Type elementType )
535530 {
536531 var current = startExpression ;
537- var lambdaPropertyMapping = PropertyMappingHelper . CreatePropertyMapping ( elementType ) ;
532+ var currentMappingTree = PropertyMappingTreeBuilder . BuildMappingTree ( elementType , GetDefaultMaxDepth ( ) ) ;
538533
539534 foreach ( var segment in segments )
540535 {
541- if ( ! lambdaPropertyMapping . TryGetValue ( segment , out var propertyName ) )
536+ if ( ! currentMappingTree . TryGetProperty ( segment , out var propertyNode ) )
542537 {
543- // If not found in current type, update mapping for nested type and try again
544- if ( current is MemberExpression memberExp )
545- {
546- lambdaPropertyMapping = PropertyMappingHelper . CreatePropertyMapping ( memberExp . Type ) ;
547- if ( ! lambdaPropertyMapping . TryGetValue ( segment , out propertyName ) )
548- {
549- return Result . Fail ( $ "Invalid property '{ segment } ' in lambda property path") ;
550- }
551- }
552- else
553- {
554- return Result . Fail ( $ "Invalid property '{ segment } ' in lambda property path") ;
555- }
538+ return Result . Fail ( $ "Invalid property '{ segment } ' in lambda property path") ;
556539 }
557540
558- current = Expression . Property ( current , propertyName ) ;
541+ current = Expression . Property ( current , propertyNode . ActualPropertyName ) ;
542+
543+ // Update mapping tree for nested navigation
544+ if ( propertyNode . HasNestedMapping )
545+ {
546+ currentMappingTree = propertyNode . NestedMapping ;
547+ }
559548 }
560549
561550 return Result . Ok ( current ) ;
562551 }
563552
553+ private static int GetDefaultMaxDepth ( )
554+ {
555+ return new QueryOptions ( ) . MaxPropertyMappingDepth ;
556+ }
557+
564558 private static Type GetCollectionElementType ( Type collectionType )
565559 {
566560 // Handle IEnumerable<T>
0 commit comments