Skip to content

Commit a860d7a

Browse files
authored
Merge pull request #52 from PandaTechAM/development
code cleanup and new overloads introduced.
2 parents 129bc83 + db6dda8 commit a860d7a

File tree

5 files changed

+133
-120
lines changed

5 files changed

+133
-120
lines changed

Readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Pandatech.GridifyExtensions
1+
# Pandatech.GridifyExtensions
22

33
Welcome to **Pandatech.GridifyExtensions**! This library extends the
44
powerful [Gridify](https://github.com/alirezanet/Gridify) package, providing additional functionalities and a more

src/GridifyExtensions/Extensions/QueryableExtensions.cs

Lines changed: 128 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

src/GridifyExtensions/GridifyExtensions.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
<PackageReadmeFile>Readme.md</PackageReadmeFile>
99
<Authors>Pandatech</Authors>
1010
<Copyright>MIT</Copyright>
11-
<Version>2.0.14</Version>
11+
<Version>2.1.0</Version>
1212
<PackageId>Pandatech.GridifyExtensions</PackageId>
1313
<Title>Pandatech.Gridify.Extensions</Title>
1414
<PackageTags>Pandatech, library, Gridify, Pagination, Filters</PackageTags>
1515
<Description>Pandatech.Gridify.Extensions simplifies and extends the functionality of the Gridify NuGet package. It provides additional extension methods and functionality to streamline data filtering and pagination, making it more intuitive and powerful to use in .NET applications. Our enhancements ensure more flexibility, reduce boilerplate code, and improve overall developer productivity when working with Gridify.</Description>
1616
<RepositoryUrl>https://github.com/PandaTechAM/be-lib-gridify-extensions</RepositoryUrl>
17-
<PackageReleaseNotes>Nuget updates</PackageReleaseNotes>
17+
<PackageReleaseNotes>New overloads has been added</PackageReleaseNotes>
1818
</PropertyGroup>
1919

2020
<ItemGroup>

test/GridifyExtensions.Demo/GridifyExtensions.Demo.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8" />
12-
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
12+
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
1313
</ItemGroup>
1414

1515
<ItemGroup>

test/GridifyExtensions.Tests/GridifyExtensions.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<ItemGroup>
1313
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
1414
<PackageReference Include="xunit" Version="2.9.3" />
15-
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
15+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
1616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1717
<PrivateAssets>all</PrivateAssets>
1818
</PackageReference>

0 commit comments

Comments
 (0)