@@ -91,6 +91,57 @@ protected IActionResult JsonApiOk<T>(T entity, string resourceType)
9191 return Ok ( document ) ;
9292 }
9393
94+ /// <summary>
95+ /// Returns 200 OK for a single resource queryable with JSON:API query support (filter, include).
96+ /// Use this when you need includes to be automatically loaded from the database.
97+ /// </summary>
98+ protected async Task < IActionResult > JsonApiOkAsync < T > (
99+ IQueryable < T > queryable ,
100+ string resourceType
101+ )
102+ where T : class
103+ {
104+ QueryParameters parameters = GetJsonApiQueryParameters ( ) ;
105+
106+ var mappedIncludes = EfIncludePathHelper . MapIncludePathsToClrProperties < T > (
107+ parameters . Include
108+ ) ;
109+
110+ var ( mainFilters , includeFilters ) = IncludeFilterParser . SeparateIncludeFilters (
111+ parameters . Filter ,
112+ parameters . Include
113+ ) ;
114+
115+ IQueryable < T > filteredQuery = queryable ;
116+
117+ // Apply main entity filters
118+ if ( mainFilters != null )
119+ filteredQuery = filteredQuery . ApplyFilters ( mainFilters , Logger ) ;
120+
121+ // Apply includes (with or without filters)
122+ if ( includeFilters . Count > 0 )
123+ {
124+ filteredQuery = filteredQuery . ApplyFilteredIncludes (
125+ mappedIncludes ,
126+ includeFilters ,
127+ Logger
128+ ) ;
129+ }
130+ else if ( mappedIncludes . Count > 0 )
131+ {
132+ filteredQuery = filteredQuery . ApplyIncludes ( mappedIncludes ) ;
133+ }
134+
135+ // Execute query for single entity
136+ T ? entity = await filteredQuery . FirstOrDefaultAsync ( ) . ConfigureAwait ( false ) ;
137+
138+ if ( entity == null )
139+ return JsonApiNotFound ( ) ;
140+
141+ // Use existing JsonApiOk - entity now has includes loaded
142+ return JsonApiOk ( entity , resourceType ) ;
143+ }
144+
94145 /// <summary>
95146 /// Returns 200 OK with a collection of resources as JSON:API document.
96147 /// </summary>
@@ -178,14 +229,19 @@ string resourceType
178229 includeFilters . Count ,
179230 typeof ( T ) . Name
180231 ) ;
181- filteredQuery = filteredQuery . ApplyFilteredIncludes ( mappedIncludes , includeFilters , Logger ) ;
232+ filteredQuery = filteredQuery . ApplyFilteredIncludes (
233+ mappedIncludes ,
234+ includeFilters ,
235+ Logger
236+ ) ;
182237 }
183238 else if ( mappedIncludes . Count > 0 )
184239 {
185240 // Use single query with pagination to avoid EF Core split query issues
186- filteredQuery = parameters . Pagination != null
187- ? filteredQuery . ApplyIncludesSingleQuery ( mappedIncludes )
188- : filteredQuery . ApplyIncludes ( mappedIncludes ) ;
241+ filteredQuery =
242+ parameters . Pagination != null
243+ ? filteredQuery . ApplyIncludesSingleQuery ( mappedIncludes )
244+ : filteredQuery . ApplyIncludes ( mappedIncludes ) ;
189245
190246 Logger . LogDebug (
191247 "Applied {IncludeCount} includes for {EntityType} using {QueryType}" ,
@@ -202,10 +258,7 @@ string resourceType
202258
203259 if ( totalCount == 0 && parameters . Filter ? . Filters ? . Count > 0 )
204260 {
205- Logger . LogInformation (
206- "Query returned 0 results for {EntityType}" ,
207- typeof ( T ) . Name
208- ) ;
261+ Logger . LogInformation ( "Query returned 0 results for {EntityType}" , typeof ( T ) . Name ) ;
209262 }
210263 else if ( totalCount > 1000 && parameters . Pagination == null )
211264 {
0 commit comments