@@ -33,117 +33,66 @@ public FieldProjectionInfo AddRequirements(
3333 return projection ;
3434 }
3535
36- // Separate simple fields and navigation paths
37- var scalarFieldsToAdd = new List < string > ( ) ;
38- var navigationPaths = new Dictionary < string , HashSet < string > > ( StringComparer . OrdinalIgnoreCase ) ;
36+ var mergedScalars = new HashSet < string > ( projection . ScalarFields , StringComparer . OrdinalIgnoreCase ) ;
37+ var navRequirements = new Dictionary < string , HashSet < string > > ( StringComparer . OrdinalIgnoreCase ) ;
3938
4039 foreach ( var field in requiredPropertyNames )
4140 {
42- if ( field . Contains ( '.' ) )
41+ var dotIndex = field . IndexOf ( '.' ) ;
42+ if ( dotIndex < 0 )
4343 {
44- // Navigation path like "Parent.Id"
45- var parts = field . Split ( '.' , 2 ) ;
46- var navName = parts [ 0 ] ;
47- var navProperty = parts [ 1 ] ;
48-
49- if ( ! navigationPaths . TryGetValue ( navName , out var properties ) )
44+ // Scalar field - skip navigation property names
45+ if ( FindNavigation ( navigationProperties , field ) == null )
5046 {
51- properties = new ( StringComparer . OrdinalIgnoreCase ) ;
52- navigationPaths [ navName ] = properties ;
47+ mergedScalars . Add ( field ) ;
5348 }
5449
55- // Only add if it doesn't contain further dots (single-level navigation)
56- if ( ! navProperty . Contains ( '.' ) )
57- {
58- properties . Add ( navProperty ) ;
59- }
50+ continue ;
6051 }
61- else
62- {
63- // Simple field - check if it's a navigation property
64- var isNavigation = navigationProperties ? . ContainsKey ( field ) == true ;
65-
66- if ( isNavigation ||
67- projection . ScalarFields . Contains ( field ) ||
68- projection . KeyNames ? . Contains ( field , StringComparer . OrdinalIgnoreCase ) == true )
69- {
70- // Skip navigation names - they'll be handled via navigation paths
71- continue ;
72- }
7352
74- scalarFieldsToAdd . Add ( field ) ;
75- }
76- }
53+ var navProperty = field [ ( dotIndex + 1 ) ..] ;
7754
78- // Merge scalar fields
79- var mergedScalars = new HashSet < string > ( projection . ScalarFields , StringComparer . OrdinalIgnoreCase ) ;
80- foreach ( var field in scalarFieldsToAdd )
81- {
82- mergedScalars . Add ( field ) ;
83- }
84-
85- // Merge navigations
86- var infos = projection . Navigations ;
87- Dictionary < string , NavigationProjectionInfo > mergedNavigations ;
88- if ( infos == null )
89- {
90- mergedNavigations = [ ] ;
91- }
92- else
93- {
94- mergedNavigations = new ( infos ) ;
95- }
96-
97- // Process navigation paths from filter fields
98- foreach ( var ( navName , requiredProps ) in navigationPaths )
99- {
100- // Skip if no navigation metadata available for this entity type
101- if ( navigationProperties == null )
55+ // Only handle single-level navigation paths
56+ if ( navProperty . Contains ( '.' ) )
10257 {
10358 continue ;
10459 }
10560
106- // Try to find the navigation - use case-insensitive search
107- Navigation ? navMetadata = null ;
108- foreach ( var ( key , value ) in navigationProperties )
61+ var navName = field [ ..dotIndex ] ;
62+ if ( ! navRequirements . TryGetValue ( navName , out var props ) )
10963 {
110- if ( string . Equals ( key , navName , StringComparison . OrdinalIgnoreCase ) )
111- {
112- navMetadata = value ;
113- break ;
114- }
64+ props = new ( StringComparer . OrdinalIgnoreCase ) ;
65+ navRequirements [ navName ] = props ;
11566 }
11667
68+ props . Add ( navProperty ) ;
69+ }
70+
71+ // Merge navigation requirements
72+ var mergedNavigations = projection . Navigations != null
73+ ? new Dictionary < string , NavigationProjectionInfo > ( projection . Navigations )
74+ : new Dictionary < string , NavigationProjectionInfo > ( ) ;
75+
76+ foreach ( var ( navName , requiredProps ) in navRequirements )
77+ {
78+ var navMetadata = FindNavigation ( navigationProperties , navName ) ;
11779 if ( navMetadata == null )
11880 {
11981 continue ;
12082 }
12183
122- var navType = navMetadata . Type ;
12384 if ( mergedNavigations . TryGetValue ( navName , out var existingNav ) )
12485 {
125- // Navigation exists in GraphQL query - add filter-required properties to its projection
12686 var updatedScalars = new HashSet < string > ( existingNav . Projection . ScalarFields , StringComparer . OrdinalIgnoreCase ) ;
127- foreach ( var prop in requiredProps )
128- {
129- updatedScalars . Add ( prop ) ;
130- }
131-
132- var updatedProjection = existingNav . Projection with
133- {
134- ScalarFields = updatedScalars
135- } ;
87+ updatedScalars . UnionWith ( requiredProps ) ;
13688 mergedNavigations [ navName ] = existingNav with
13789 {
138- Projection = updatedProjection
90+ Projection = existingNav . Projection with { ScalarFields = updatedScalars }
13991 } ;
14092 }
14193 else
14294 {
143- // Create navigation projection for filter-only navigations.
144- // Don't include key/FK columns - the filter only needs the specific properties it accesses.
145- var navProjection = new FieldProjectionInfo ( requiredProps , null , null , null ) ;
146- mergedNavigations [ navName ] = new ( navType , navMetadata . IsCollection , navProjection ) ;
95+ mergedNavigations [ navName ] = new ( navMetadata . Type , navMetadata . IsCollection , new ( requiredProps , null , null , null ) ) ;
14796 }
14897 }
14998
@@ -154,6 +103,24 @@ public FieldProjectionInfo AddRequirements(
154103 } ;
155104 }
156105
106+ static Navigation ? FindNavigation ( IReadOnlyDictionary < string , Navigation > ? properties , string name )
107+ {
108+ if ( properties == null )
109+ {
110+ return null ;
111+ }
112+
113+ foreach ( var ( key , value ) in properties )
114+ {
115+ if ( string . Equals ( key , name , StringComparison . OrdinalIgnoreCase ) )
116+ {
117+ return value ;
118+ }
119+ }
120+
121+ return null ;
122+ }
123+
157124 public Task < bool > ShouldIncludeWithProjection (
158125 object userContext ,
159126 TDbContext data ,
0 commit comments