I have an api end point which returns the paginated result, the corresponding code for that is as below:
public Task<PaginatedDataResult<ProductSearchResult>> GetPaginatedSearchAsync(int currentPage,
int perPage,
string nameFilter,
string sortBy,
bool sortDesc,
CancellationToken cancellationToken = default)
{
IEnumerable<Expression<Func<Product, bool>>> GetFilters()
{
if (!string.IsNullOrEmpty(nameFilter))
{
var normalizedNameFilter = nameFilter.NormalizeUpper();
yield return p => p.NormalizedName.Contains(normalizedNameFilter);
}
}
Expression<Func<Product, ProductSearchResult>> projectionExpression =
p => new ProductSearchResult
{
Id = p.Id,
Name = p.Name,
EndDate = p.EndDate,
IsActive = p.IsActive,
ServicesCount = p.ProductServices.Count
};
return PaginatedFilterWithProjectionAsync(currentPage, perPage, sortBy, sortDesc, cancellationToken, projectionExpression, GetFilters().ToArray());
}
protected async Task<PaginatedDataResult<TProjectedEntity>>
PaginatedFilterWithProjectionAsync<TProjectedEntity>(int currentPage,
int perPage,
string sortBy,
bool sortDesc,
CancellationToken cancellationToken,
Expression<Func<TEntity, TProjectedEntity>> projectionExpression,
params Expression<Func<TEntity, bool>>[] filters
) where TProjectedEntity : class
{
var query = filters.Aggregate(DbSet.AsQueryable(), (q, f) => q.Where(f));
var queryWithProjection = query.Select(projectionExpression);
if (!string.IsNullOrEmpty(sortBy))
{
queryWithProjection = sortDesc
? queryWithProjection.OrderByDescending(sortBy)
: queryWithProjection.OrderBy(sortBy);
}
var filteredQueryOfProjectedEntity = queryWithProjection
.Skip((currentPage - 1) * perPage)
.Take(perPage);
var filteredListOfProjectedEntity = await filteredQueryOfProjectedEntity.ToListAsync(cancellationToken);
var count = await queryWithProjection.CountAsync(cancellationToken);
return new PaginatedDataResult<TProjectedEntity>
{
PerPage = perPage,
CurrentPage = currentPage,
TotalCount = count,
Data = filteredListOfProjectedEntity
};
}
Now, I have another end point which is returning a complete collection as below
public async Task<ICollection<ProductRelationship>> GetAllByRelationshipDataAsync(
bool includeDetail,
bool filterActiveProduct = false,
DateTime? filterActiveRelationshipDate = null,
CancellationToken cancellationToken = default)
{
var set = includeDetail ? IncludeDetail(DbSet) : DbSet;
//I have removed few parameters from the method and also few if conditions for brevity
if (filterActiveProduct)
{
set = set.Where(x => x.Product != null && x.Product.IsActive);
}
if (filterActiveRelationshipDate.HasValue)
{
var filterDate = filterActiveRelationshipDate.Value.Date;
set = set.Where(x => x.StartDate <= filterDate && (!x.EndDate.HasValue || x.EndDate.Value >= filterDate));
}
return await set.ToListAsync(cancellationToken);
}
The requirement is to change the above method to return a paginated result, so the changes I did are as below:
change the method signature to add four additional parameters (currentPage, perPage, sortBy, sortDesc)
Rather than returning a collection, calling a PaginatedAsync method to return paginated result
public async Task<PaginatedDataResult<ProductRelationship>> GetAllByRelationshipDataAsyncTest( bool includeDetail, int currentPage, int perPage, string sortBy, bool sortDesc, bool filterActiveProduct = false, DateTime? filterActiveRelationshipDate = null, CancellationToken cancellationToken = default) { var set = includeDetail ? IncludeDetail(DbSet) : DbSet; //I have removed few parameters from the method and also few if conditions for brevity if (filterActiveProduct) { set = set.Where(x => x.Product != null && x.Product.IsActive); } if (filterActiveRelationshipDate.HasValue) { var filterDate = filterActiveRelationshipDate.Value.Date; set = set.Where(x => x.StartDate <= filterDate && (!x.EndDate.HasValue || x.EndDate.Value >= filterDate)); } return await PaginatedAsync(set, currentPage, perPage, sortBy, sortDesc, cancellationToken); }PaginatedAsync method is the same copy of PaginatedFilterWithProjectionAsync method above but without the variables query and queryWithProjection, since I have got the queryWithProjection value in the variable set above.
I could also refactor the PaginatedFilterWithProjectionAsync method to include PaginatedAsync as most of the code is same, but that was already in use, that adds more testing effort. May be I am breaking open closed principleprotected async Task<PaginatedDataResult<TProjectedEntity>> PaginatedAsync<TProjectedEntity>(IQueryable<TProjectedEntity> queryWithProjection, int currentPage, int perPage, string sortBy, bool sortDesc, CancellationToken cancellationToken) { if (!string.IsNullOrEmpty(sortBy)) { queryWithProjection = sortDesc ? queryWithProjection.OrderByDescending(sortBy) : queryWithProjection.OrderBy(sortBy); } var filteredQueryOfProjectedEntity = queryWithProjection .Skip((currentPage - 1) * perPage) .Take(perPage); var filteredListOfProjectedEntity = await filteredQueryOfProjectedEntity.ToListAsync(cancellationToken); var count = await queryWithProjection.CountAsync(cancellationToken); return new PaginatedDataResult<TProjectedEntity> { PerPage = perPage, CurrentPage = currentPage, TotalCount = count, Data = filteredListOfProjectedEntity }; }
Please can anyone review and provide me with suggestions for better approach and point me the mistakes in the above code.