@@ -434,6 +434,13 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
434434 }
435435
436436 var rightExpr = CreateRightExpr ( temp . leftExpr , temp . right , temp . op ) ;
437+
438+ // Handle nested collection filtering
439+ if ( temp . leftExpr is MethodCallExpression methodCall && IsNestedCollectionExpression ( methodCall ) )
440+ {
441+ return CreateNestedCollectionFilterExpression < T > ( methodCall , rightExpr , temp . op ) ;
442+ }
443+
437444 return temp . op . GetExpression < T > ( temp . leftExpr , rightExpr , config ? . DbContextType ) ;
438445 } ) ;
439446 }
@@ -469,8 +476,17 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
469476 var propertyInfoForMethod = GetPropertyInfo ( genericArgType , propName ) ;
470477 Expression lambdaBody = Expression . PropertyOrField ( innerParameter , propertyInfoForMethod . Name ) ;
471478
472- var type = typeof ( IEnumerable < > ) . MakeGenericType ( propertyType ) ;
473- lambdaBody = Expression . Lambda ( Expression . Convert ( lambdaBody , type ) , innerParameter ) ;
479+ // Ensure the lambda body returns IEnumerable<T> for SelectMany
480+ var expectedType = typeof ( IEnumerable < > ) . MakeGenericType ( propertyType ) ;
481+ if ( lambdaBody . Type != expectedType && ! expectedType . IsAssignableFrom ( lambdaBody . Type ) )
482+ {
483+ // Convert to IEnumerable<T> if needed (e.g., List<T> to IEnumerable<T>)
484+ lambdaBody = Expression . Convert ( lambdaBody , expectedType ) ;
485+ }
486+
487+ // Create lambda with the correct return type
488+ var lambdaType = typeof ( Func < , > ) . MakeGenericType ( genericArgType , expectedType ) ;
489+ lambdaBody = Expression . Lambda ( lambdaType , lambdaBody , innerParameter ) ;
474490
475491 return Expression . Call ( selectMethod , member , lambdaBody ) ;
476492 }
@@ -496,10 +512,13 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
496512 var innerGenericType = GetInnerGenericType ( call . Method . ReturnType ) ;
497513 var propertyInfoForMethod = GetPropertyInfo ( innerGenericType , propName ) ;
498514
499- var linqMethod = IsEnumerable ( innerGenericType ) ? "SelectMany" : "Select" ;
515+ var propertyType = propertyInfoForMethod . PropertyType ;
516+ var linqMethod = IsEnumerable ( propertyType ) ? "SelectMany" : "Select" ;
517+ var resultType = IsEnumerable ( propertyType ) ? propertyType . GetGenericArguments ( ) [ 0 ] : propertyType ;
518+
500519 var selectMethod = typeof ( Enumerable ) . GetMethods ( )
501520 . First ( m => m . Name == linqMethod && m . GetParameters ( ) . Length == 2 )
502- . MakeGenericMethod ( innerGenericType , propertyInfoForMethod . PropertyType ) ;
521+ . MakeGenericMethod ( innerGenericType , resultType ) ;
503522
504523 var innerParameter = Expression . Parameter ( innerGenericType , "y" ) ;
505524 var lambdaBody = Expression . PropertyOrField ( innerParameter , propertyInfoForMethod . Name ) ;
@@ -601,6 +620,101 @@ private static Expression HandleGuidConversion(Expression expression, Type prope
601620
602621 return Expression . Call ( selectMethod , expression , toStringLambda ) ;
603622 }
623+
624+ private static bool IsNestedCollectionExpression ( MethodCallExpression methodCall )
625+ {
626+ // Check if this is a nested SelectMany expression indicating nested collection navigation
627+ if ( methodCall . Method . Name == "SelectMany" && methodCall . Arguments . Count == 2 )
628+ {
629+ // Check if the source is also a SelectMany (indicating nesting)
630+ if ( methodCall . Arguments [ 0 ] is MethodCallExpression sourceCall &&
631+ sourceCall . Method . Name == "SelectMany" )
632+ {
633+ return true ;
634+ }
635+ }
636+ return false ;
637+ }
638+
639+ private static Expression CreateNestedCollectionFilterExpression < T > ( MethodCallExpression methodCall , Expression rightExpr , ComparisonOperator op )
640+ {
641+ // For nested collection expressions like Ingredients.Preparations.Text
642+ // We need to unwind the SelectMany chain and create nested Any expressions
643+ // like: x.Ingredients.Any(i => i.Preparations.Any(p => p.Text == "value"))
644+
645+ var expressions = UnwindSelectManyChain ( methodCall ) ;
646+ if ( expressions . Count < 2 )
647+ {
648+ // Fallback to regular collection expression
649+ return op . GetExpression < T > ( methodCall , rightExpr , null ) ;
650+ }
651+
652+ // Build nested Any expressions from the inside out
653+ var currentExpression = expressions . Last ( ) ;
654+ var currentParameter = Expression . Parameter ( currentExpression . CollectionElementType , $ "item{ expressions . Count - 1 } ") ;
655+ var finalPropertyAccess = Expression . PropertyOrField ( currentParameter , currentExpression . PropertyName ) ;
656+
657+ // Create the innermost comparison
658+ var comparison = op . GetExpression < T > ( finalPropertyAccess , rightExpr , null ) ;
659+ var innerLambda = Expression . Lambda ( comparison , currentParameter ) ;
660+
661+ // Build the Any chain from inside out
662+ for ( int i = expressions . Count - 2 ; i >= 0 ; i -- )
663+ {
664+ var collectionInfo = expressions [ i ] ;
665+ var param = Expression . Parameter ( collectionInfo . CollectionElementType , $ "item{ i } ") ;
666+ var collectionAccess = Expression . PropertyOrField ( param , collectionInfo . PropertyName ) ;
667+
668+ // Create Any method call
669+ var anyMethod = typeof ( Enumerable ) . GetMethods ( )
670+ . First ( m => m . Name == "Any" && m . GetParameters ( ) . Length == 2 )
671+ . MakeGenericMethod ( collectionInfo . CollectionElementType ) ;
672+
673+ var anyCall = Expression . Call ( anyMethod , collectionAccess , innerLambda ) ;
674+ innerLambda = Expression . Lambda ( anyCall , param ) ;
675+ }
676+
677+ return innerLambda . Body ;
678+ }
679+
680+ private class CollectionInfo
681+ {
682+ public Type CollectionElementType { get ; set ; }
683+ public string PropertyName { get ; set ; }
684+ }
685+
686+ private static List < CollectionInfo > UnwindSelectManyChain ( MethodCallExpression methodCall )
687+ {
688+ var result = new List < CollectionInfo > ( ) ;
689+ var current = methodCall ;
690+
691+ while ( current != null && current . Method . Name == "SelectMany" )
692+ {
693+ // Extract the property access from the lambda
694+ if ( current . Arguments [ 1 ] is LambdaExpression lambda &&
695+ lambda . Body is MemberExpression member )
696+ {
697+ var elementType = current . Method . GetGenericArguments ( ) [ 0 ] ;
698+ result . Insert ( 0 , new CollectionInfo
699+ {
700+ CollectionElementType = elementType ,
701+ PropertyName = member . Member . Name
702+ } ) ;
703+ }
704+
705+ // Move to the next level
706+ if ( current . Arguments [ 0 ] is MethodCallExpression nextCall )
707+ {
708+ current = nextCall ;
709+ }
710+ else
711+ {
712+ break ;
713+ }
714+ }
715+
716+ return result ;
717+ }
604718}
605719
606720
0 commit comments