@@ -8,9 +8,6 @@ namespace TinyRepository.Extensions;
88
99public static class QueryableExtensions
1010{
11- /// <summary>
12- /// Applica Include multipli.
13- /// </summary>
1411 public static IQueryable < T > IncludeMultiple < T > ( this IQueryable < T > query , params Expression < Func < T , object > > [ ] includes ) where T : class
1512 {
1613 if ( includes == null || includes . Length == 0 )
@@ -27,10 +24,28 @@ public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params
2724 return query ;
2825 }
2926
30- /// <summary>
31- /// Applica ordering dinamico per singola proprietà (supporta percorsi con dot, es. "Author.Name").
32- /// Se orderByProperty è null/empty restituisce la query originale.
33- /// </summary>
27+ public static IQueryable < T > IncludePaths < T > ( this IQueryable < T > query , params string [ ] includePaths ) where T : class
28+ {
29+ if ( includePaths == null || includePaths . Length == 0 )
30+ {
31+ return query ;
32+ }
33+
34+ // Rimuoviamo duplicati e ordiniamo in modo che i percorsi "padre" vengano prima dei figli.
35+ // Questo non è strettamente necessario per EF Core, ma aiuta in scenari di costruzione di query leggibili.
36+ var normalized = NormalizeAndOrderPaths ( includePaths ) ;
37+
38+ foreach ( var path in normalized )
39+ {
40+ if ( ! string . IsNullOrWhiteSpace ( path ) )
41+ {
42+ query = query . Include ( path ) ;
43+ }
44+ }
45+
46+ return query ;
47+ }
48+
3449 public static IQueryable < T > ApplyOrderByProperty < T > ( this IQueryable < T > source , string ? orderByProperty , bool descending = false )
3550 {
3651 if ( string . IsNullOrWhiteSpace ( orderByProperty ) )
@@ -71,11 +86,6 @@ public static IQueryable<T> ApplyOrderByProperty<T>(this IQueryable<T> source, s
7186 return ordered ;
7287 }
7388
74- /// <summary>
75- /// Applica multiple ordinamenti (OrderBy / ThenBy) in base ai SortDescriptor.
76- /// Se allowedProperties è non-null e non vuota, ogni property deve far parte della whitelist o viene lanciata un'eccezione.
77- /// Supporta proprietà annidate con dot path ("Author.LastName").
78- /// </summary>
7989 public static IQueryable < T > ApplyOrdering < T > ( this IQueryable < T > source , IEnumerable < SortDescriptor > sortDescriptors ,
8090 IEnumerable < string > ? allowedProperties = null )
8191 {
@@ -141,8 +151,7 @@ public static IQueryable<T> ApplyOrdering<T>(this IQueryable<T> source, IEnumera
141151
142152 var queryableType = typeof ( Queryable ) ;
143153 var methods = queryableType . GetMethods ( BindingFlags . Public | BindingFlags . Static )
144- . Where ( m => m . Name == methodName && m . GetParameters ( ) . Length == 2 )
145- . ToList ( ) ;
154+ . Where ( m => m . Name == methodName && m . GetParameters ( ) . Length == 2 ) . ToList ( ) ;
146155
147156 var method = methods . FirstOrDefault ( ) ;
148157
@@ -165,4 +174,22 @@ public static IQueryable<T> ApplyOrdering<T>(this IQueryable<T> source, IEnumera
165174
166175 return orderedQuery ?? source ;
167176 }
177+
178+ private static string [ ] NormalizeAndOrderPaths ( string [ ] includePaths )
179+ {
180+ var set = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
181+ foreach ( var p in includePaths )
182+ {
183+ if ( string . IsNullOrWhiteSpace ( p ) )
184+ {
185+ continue ;
186+ }
187+
188+ var trimmed = p . Trim ( ) ;
189+ set . Add ( trimmed ) ;
190+ }
191+
192+ // Order by number of segments ascending: parents before children
193+ return set . OrderBy ( p => p . Count ( c => c == '.' ) ) . ThenBy ( p => p , StringComparer . OrdinalIgnoreCase ) . ToArray ( ) ;
194+ }
168195}
0 commit comments