@@ -10,98 +10,149 @@ public static class QueryableExtensions
1010{
1111 internal static Dictionary < Type , object > EntityGridifyMapperByType = [ ] ;
1212
13- public static async Task < PagedResponse < TDto > > FilterOrderAndGetPagedAsync < TEntity , TDto > (
14- this IQueryable < TEntity > query ,
15- GridifyQueryModel model ,
16- Expression < Func < TEntity , TDto > > selectExpression ,
17- CancellationToken cancellationToken = default )
18- where TEntity : class
19- {
20- var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
13+ // ---------- Core helpers ----------
2114
22- model . OrderBy ??= mapper ! . GetDefaultOrderExpression ( ) ;
2315
24- query = query . ApplyFilteringAndOrdering ( model , mapper ) ;
25-
26- var dtoQuery = query . Select ( selectExpression ) ;
27-
28- var totalCount = await dtoQuery . CountAsync ( cancellationToken ) ;
29-
30- dtoQuery = dtoQuery . ApplyPaging ( model . Page , model . PageSize ) ;
31-
32- return new PagedResponse < TDto > ( await dtoQuery . ToListAsync ( cancellationToken ) ,
33- model . Page ,
34- model . PageSize ,
35- totalCount ) ;
36- }
37-
38- public static Task < PagedResponse < TEntity > > FilterOrderAndGetPagedAsync < TEntity > ( this IQueryable < TEntity > query ,
39- GridifyQueryModel model ,
40- CancellationToken cancellationToken = default )
41- where TEntity : class
16+ private static Expression < Func < T , object > > CreateSelector < T > ( string propertyName )
4217 {
43- return query . AsNoTracking ( )
44- . FilterOrderAndGetPagedAsync ( model , x => x , cancellationToken ) ;
18+ var p = Expression . Parameter ( typeof ( T ) , "x" ) ;
19+ var body = Expression . Convert ( Expression . Property ( p , propertyName ) , typeof ( object ) ) ;
20+ return Expression . Lambda < Func < T , object > > ( body , p ) ;
4521 }
4622
47- public static IQueryable < TEntity > ApplyFilter < TEntity > ( this IQueryable < TEntity > query , GridifyQueryModel model )
23+ private static FilterMapper < TEntity > RequireMapper < TEntity > ( )
4824 where TEntity : class
4925 {
50- var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
26+ if ( ! EntityGridifyMapperByType . TryGetValue ( typeof ( TEntity ) , out var raw ) ||
27+ raw is not FilterMapper < TEntity > mapper )
28+ {
29+ throw new KeyNotFoundException ( $ "No FilterMapper registered for entity type { typeof ( TEntity ) . Name } .") ;
30+ }
5131
52- return query . ApplyFiltering ( model , mapper ) ;
32+ return mapper ;
5333 }
5434
35+ // ---------- Filtering / Ordering ----------
36+ public static IQueryable < TEntity > ApplyFilter < TEntity > ( this IQueryable < TEntity > query , GridifyQueryModel model )
37+ where TEntity : class =>
38+ query . ApplyFiltering ( model , RequireMapper < TEntity > ( ) ) ;
39+
5540 public static IQueryable < TEntity > ApplyFilter < TEntity > ( this IQueryable < TEntity > query , string filter )
5641 where TEntity : class
5742 {
58- var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
59-
6043 var model = new GridifyQueryModel
6144 {
6245 Page = 1 ,
6346 PageSize = 1 ,
6447 OrderBy = null ,
6548 Filter = filter
6649 } ;
67- return query . ApplyFiltering ( model , mapper ) ;
50+ return query . ApplyFiltering ( model , RequireMapper < TEntity > ( ) ) ;
6851 }
6952
7053 public static IQueryable < TEntity > ApplyOrder < TEntity > ( this IQueryable < TEntity > query , GridifyQueryModel model )
7154 where TEntity : class
7255 {
73- var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
74-
75- model . OrderBy ??= mapper ! . GetDefaultOrderExpression ( ) ;
76-
56+ var mapper = RequireMapper < TEntity > ( ) ;
57+ model . OrderBy ??= mapper . GetDefaultOrderExpression ( ) ;
7758 return query . AsNoTracking ( )
7859 . ApplyOrdering ( model , mapper ) ;
7960 }
8061
62+ // ---------- Paging (simple) ----------
8163 public static async Task < PagedResponse < TEntity > > GetPagedAsync < TEntity > ( this IQueryable < TEntity > query ,
8264 GridifyQueryModel model ,
8365 CancellationToken cancellationToken = default )
8466 where TEntity : class
8567 {
8668 var totalCount = await query . CountAsync ( cancellationToken ) ;
87-
8869 query = query . ApplyPaging ( model . Page , model . PageSize ) ;
70+ var data = await query . ToListAsync ( cancellationToken ) ;
71+ return new PagedResponse < TEntity > ( data , model . Page , model . PageSize , totalCount ) ;
72+ }
73+
74+ public static async Task < PagedResponse < TDto > > GetPagedAsync < TEntity , TDto > ( this IQueryable < TEntity > query ,
75+ GridifyQueryModel model ,
76+ Expression < Func < TEntity , TDto > > selectExpression ,
77+ CancellationToken cancellationToken = default )
78+ where TEntity : class
79+ {
80+ var totalCount = await query . CountAsync ( cancellationToken ) ;
81+ var data = await query . Select ( selectExpression )
82+ . ApplyPaging ( model . Page , model . PageSize )
83+ . ToListAsync ( cancellationToken ) ;
84+ return new PagedResponse < TDto > ( data , model . Page , model . PageSize , totalCount ) ;
85+ }
86+
87+ // ---------- Filter + Order + Paging ----------
88+ public static async Task < PagedResponse < TDto > > FilterOrderAndGetPagedAsync < TEntity , TDto > (
89+ this IQueryable < TEntity > query ,
90+ GridifyQueryModel model ,
91+ Expression < Func < TEntity , TDto > > selectExpression ,
92+ CancellationToken cancellationToken = default )
93+ where TEntity : class
94+ {
95+ var mapper = RequireMapper < TEntity > ( ) ;
96+ model . OrderBy ??= mapper . GetDefaultOrderExpression ( ) ;
97+
98+ query = query . ApplyFilteringAndOrdering ( model , mapper ) ;
99+
100+ var totalCount = await query . CountAsync ( cancellationToken ) ;
101+
102+ var dtoQuery = query . Select ( selectExpression )
103+ . ApplyPaging ( model . Page , model . PageSize ) ;
104+
105+ var data = await dtoQuery . ToListAsync ( cancellationToken ) ;
106+ return new PagedResponse < TDto > ( data , model . Page , model . PageSize , totalCount ) ;
107+ }
108+
109+ public static Task < PagedResponse < TEntity > > FilterOrderAndGetPagedAsync < TEntity > ( this IQueryable < TEntity > query ,
110+ GridifyQueryModel model ,
111+ CancellationToken cancellationToken = default )
112+ where TEntity : class =>
113+ query . AsNoTracking ( )
114+ . FilterOrderAndGetPagedAsync ( model , x => x , cancellationToken ) ;
115+
116+ // ---------- Cursored ----------
117+ public static async Task < CursoredResponse < TDto > > FilterOrderAndGetCursoredAsync < TEntity , TDto > (
118+ this IQueryable < TEntity > query ,
119+ GridifyCursoredQueryModel model ,
120+ Expression < Func < TEntity , TDto > > selectExpression ,
121+ CancellationToken cancellationToken = default )
122+ where TEntity : class
123+ {
124+ var mapper = RequireMapper < TEntity > ( ) ;
125+
126+ var queryModel = model . ToGridifyQueryModel ( ) ;
127+ queryModel . OrderBy ??= mapper . GetDefaultOrderExpression ( ) ;
128+
129+ query = query . ApplyFilteringAndOrdering ( queryModel , mapper ) ;
130+
131+ var data = await query . Select ( selectExpression )
132+ . Take ( model . PageSize )
133+ . ToListAsync ( cancellationToken ) ;
89134
90- return new PagedResponse < TEntity > ( await query . ToListAsync ( cancellationToken ) ,
91- model . Page ,
92- model . PageSize ,
93- totalCount ) ;
135+ return new CursoredResponse < TDto > ( data , model . PageSize ) ;
94136 }
95137
138+ public static Task < CursoredResponse < TEntity > > FilterOrderAndGetCursoredAsync < TEntity > ( this IQueryable < TEntity > query ,
139+ GridifyCursoredQueryModel model ,
140+ CancellationToken cancellationToken = default )
141+ where TEntity : class =>
142+ query . AsNoTracking ( )
143+ . FilterOrderAndGetCursoredAsync ( model , x => x , cancellationToken ) ;
144+
145+ // ---------- Column Distinct ----------
96146 [ Obsolete ( "Use ColumnDistinctValueCursoredQueryModel instead." ) ]
97147 public static async Task < PagedResponse < object > > ColumnDistinctValuesAsync < TEntity > ( this IQueryable < TEntity > query ,
98148 ColumnDistinctValueQueryModel model ,
99149 Func < byte [ ] , string > ? decryptor = default ,
100150 CancellationToken cancellationToken = default )
151+ where TEntity : class
101152 {
102- var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
153+ var mapper = RequireMapper < TEntity > ( ) ;
103154
104- if ( ! mapper ! . IsEncrypted ( model . PropertyName ) )
155+ if ( ! mapper . IsEncrypted ( model . PropertyName ) )
105156 {
106157 var result = await query
107158 . ApplyFiltering ( model , mapper )
@@ -119,24 +170,27 @@ public static async Task<PagedResponse<object>> ColumnDistinctValuesAsync<TEntit
119170 . FirstOrDefaultAsync ( cancellationToken ) ;
120171
121172 if ( item is null || string . IsNullOrEmpty ( model . Filter ) )
122- {
123173 return new PagedResponse < object > ( [ ] , 1 , 1 , 0 ) ;
174+
175+ if ( decryptor is null )
176+ {
177+ throw new KeyNotFoundException ( "Decryptor is required for encrypted properties." ) ;
124178 }
125179
126- var decryptedItem = decryptor ! ( ( byte [ ] ) item ) ;
127- return new PagedResponse < object > ( [ decryptedItem ] , 1 , 1 , 1 ) ;
180+ var decrypted = decryptor ( ( byte [ ] ) item ) ;
181+ return new PagedResponse < object > ( [ decrypted ] , 1 , 1 , 1 ) ;
128182 }
129183
130184 public static async Task < CursoredResponse < object > > ColumnDistinctValuesAsync < TEntity > ( this IQueryable < TEntity > query ,
131185 ColumnDistinctValueCursoredQueryModel model ,
132186 Func < byte [ ] , string > ? decryptor = default ,
133187 CancellationToken cancellationToken = default )
188+ where TEntity : class
134189 {
135- var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
136-
190+ var mapper = RequireMapper < TEntity > ( ) ;
137191 var gridifyModel = model . ToGridifyQueryModel ( ) ;
138192
139- if ( ! mapper ! . IsEncrypted ( model . PropertyName ) )
193+ if ( ! mapper . IsEncrypted ( model . PropertyName ) )
140194 {
141195 var result = await query
142196 . ApplyFiltering ( gridifyModel , mapper )
@@ -155,71 +209,40 @@ public static async Task<CursoredResponse<object>> ColumnDistinctValuesAsync<TEn
155209 . FirstOrDefaultAsync ( cancellationToken ) ;
156210
157211 if ( item is null || string . IsNullOrEmpty ( model . Filter ) )
158- {
159212 return new CursoredResponse < object > ( [ ] , model . PageSize ) ;
160- }
161-
162- var decryptedItem = decryptor ! ( ( byte [ ] ) item ) ;
163- return new CursoredResponse < object > ( [ decryptedItem ] , model . PageSize ) ;
164- }
165213
166- public static async Task < CursoredResponse < TDto > > FilterOrderAndGetCursoredAsync < TEntity , TDto > (
167- this IQueryable < TEntity > query ,
168- GridifyCursoredQueryModel model ,
169- Expression < Func < TEntity , TDto > > selectExpression ,
170- CancellationToken cancellationToken = default )
171- where TEntity : class
172- {
173- var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
174-
175- var queryModel = model . ToGridifyQueryModel ( ) ;
176- queryModel . OrderBy ??= mapper ! . GetDefaultOrderExpression ( ) ;
177-
178- query = query . ApplyFilteringAndOrdering ( queryModel , mapper ) ;
179-
180- var dtoQuery = query . Select ( selectExpression ) ;
181-
182- var data = await dtoQuery
183- . Take ( model . PageSize )
184- . ToListAsync ( cancellationToken ) ;
185-
186- return new CursoredResponse < TDto > ( data , model . PageSize ) ;
187- }
214+ if ( decryptor is null )
215+ {
216+ throw new KeyNotFoundException ( "Decryptor is required for encrypted properties." ) ;
217+ }
188218
189- public static Task < CursoredResponse < TEntity > > FilterOrderAndGetCursoredAsync < TEntity > ( this IQueryable < TEntity > query ,
190- GridifyCursoredQueryModel model ,
191- CancellationToken cancellationToken = default )
192- where TEntity : class
193- {
194- return query
195- . AsNoTracking ( )
196- . FilterOrderAndGetCursoredAsync ( model , x => x , cancellationToken ) ;
219+ var decrypted = decryptor ( ( byte [ ] ) item ) ;
220+ return new CursoredResponse < object > ( [ decrypted ] , model . PageSize ) ;
197221 }
198222
223+ // ---------- Aggregation ----------
199224 public static async Task < object > AggregateAsync < TEntity > ( this IQueryable < TEntity > query ,
200225 AggregateQueryModel model ,
201226 CancellationToken cancellationToken = default )
202227 where TEntity : class
203228 {
204- var aggregateProperty = model . PropertyName ;
205-
206- var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
207-
208- var filteredQuery = query . ApplyFiltering ( model , mapper )
209- . ApplySelect ( aggregateProperty , mapper ) ;
229+ var mapper = RequireMapper < TEntity > ( ) ;
230+ var filtered = query . ApplyFiltering ( model , mapper )
231+ . ApplySelect ( model . PropertyName , mapper ) ;
210232
211233 return model . AggregateType switch
212234 {
213- AggregateType . UniqueCount => await filteredQuery . Distinct ( )
214- . CountAsync ( cancellationToken ) ,
215- AggregateType . Sum => await filteredQuery . SumAsync ( x => ( decimal ) x , cancellationToken ) ,
216- AggregateType . Average => await filteredQuery . AverageAsync ( x => ( decimal ) x , cancellationToken ) ,
217- AggregateType . Min => await filteredQuery . MinAsync ( cancellationToken ) ,
218- AggregateType . Max => await filteredQuery . MaxAsync ( cancellationToken ) ,
235+ AggregateType . UniqueCount => await filtered . Distinct ( )
236+ . CountAsync ( cancellationToken ) ,
237+ AggregateType . Sum => await filtered . SumAsync ( x => ( decimal ) x , cancellationToken ) ,
238+ AggregateType . Average => await filtered . AverageAsync ( x => ( decimal ) x , cancellationToken ) ,
239+ AggregateType . Min => await filtered . MinAsync ( cancellationToken ) ,
240+ AggregateType . Max => await filtered . MaxAsync ( cancellationToken ) ,
219241 _ => throw new NotImplementedException ( )
220242 } ;
221243 }
222244
245+ // ---------- Introspection ----------
223246 public static IEnumerable < MappingModel > GetMappings < TEntity > ( )
224247 {
225248 var mapper = EntityGridifyMapperByType [ typeof ( TEntity ) ] as FilterMapper < TEntity > ;
@@ -228,22 +251,12 @@ public static IEnumerable<MappingModel> GetMappings<TEntity>()
228251 . Select ( x => new MappingModel
229252 {
230253 Name = x . From ,
231- Type = x . To . Body is UnaryExpression
232- ? ( x . To . Body as UnaryExpression ) ! . Operand . Type . Name
233- : x . To . Body is MethodCallExpression
234- ? ( ( x . To . Body as MethodCallExpression ) ! . Arguments . LastOrDefault ( ) as LambdaExpression )
235- ? . ReturnType
236- . Name ?? x . To . Body . Type . Name
254+ Type = x . To . Body is UnaryExpression ue
255+ ? ue . Operand . Type . Name
256+ : x . To . Body is MethodCallExpression mc
257+ ? ( ( mc . Arguments . LastOrDefault ( ) as LambdaExpression ) ? . ReturnType ? . Name )
258+ ?? x . To . Body . Type . Name
237259 : x . To . Body . Type . Name
238260 } ) ;
239261 }
240-
241- private static Expression < Func < T , object > > CreateSelector < T > ( string propertyName )
242- {
243- var parameter = Expression . Parameter ( typeof ( T ) , "x" ) ;
244- var property = Expression . Property ( parameter , propertyName ) ;
245- var converted = Expression . Convert ( property , typeof ( object ) ) ;
246-
247- return Expression . Lambda < Func < T , object > > ( converted , parameter ) ;
248- }
249262}
0 commit comments