Paging infrastructure implemented through specifications. (#151)
* Paging criterias added in BaseSpecification. New paginated specification added (CatalogFilterPaginatedSpecification) . EFRepository cleaned up and paging implementation added. Usage of the new paging infrastructure in CatalogService (Web and WebRazor). * Use IReadOnlyList<T> instead of List<T> as return type from repositories. * - Ordering possibility added in the specification. - Evaluation of the specification placed in separate class.
This commit is contained in:
@@ -7,10 +7,11 @@ namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
|||||||
public interface IAsyncRepository<T> where T : BaseEntity
|
public interface IAsyncRepository<T> where T : BaseEntity
|
||||||
{
|
{
|
||||||
Task<T> GetByIdAsync(int id);
|
Task<T> GetByIdAsync(int id);
|
||||||
Task<List<T>> ListAllAsync();
|
Task<IReadOnlyList<T>> ListAllAsync();
|
||||||
Task<List<T>> ListAsync(ISpecification<T> spec);
|
Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec);
|
||||||
Task<T> AddAsync(T entity);
|
Task<T> AddAsync(T entity);
|
||||||
Task UpdateAsync(T entity);
|
Task UpdateAsync(T entity);
|
||||||
Task DeleteAsync(T entity);
|
Task DeleteAsync(T entity);
|
||||||
|
Task<int> CountAsync(ISpecification<T> spec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
|||||||
T Add(T entity);
|
T Add(T entity);
|
||||||
void Update(T entity);
|
void Update(T entity);
|
||||||
void Delete(T entity);
|
void Delete(T entity);
|
||||||
|
int Count(ISpecification<T> spec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,11 @@ namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
|||||||
Expression<Func<T, bool>> Criteria { get; }
|
Expression<Func<T, bool>> Criteria { get; }
|
||||||
List<Expression<Func<T, object>>> Includes { get; }
|
List<Expression<Func<T, object>>> Includes { get; }
|
||||||
List<string> IncludeStrings { get; }
|
List<string> IncludeStrings { get; }
|
||||||
|
Expression<Func<T, object>> OrderBy { get; }
|
||||||
|
Expression<Func<T, object>> OrderByDescending { get; }
|
||||||
|
|
||||||
|
int Take { get; }
|
||||||
|
int Skip { get; }
|
||||||
|
bool isPagingEnabled { get;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ namespace Microsoft.eShopWeb.ApplicationCore.Specifications
|
|||||||
public Expression<Func<T, bool>> Criteria { get; }
|
public Expression<Func<T, bool>> Criteria { get; }
|
||||||
public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
|
public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
|
||||||
public List<string> IncludeStrings { get; } = new List<string>();
|
public List<string> IncludeStrings { get; } = new List<string>();
|
||||||
|
public Expression<Func<T, object>> OrderBy { get; private set; }
|
||||||
|
public Expression<Func<T, object>> OrderByDescending { get; private set; }
|
||||||
|
|
||||||
|
public int Take { get; private set; }
|
||||||
|
public int Skip { get; private set; }
|
||||||
|
public bool isPagingEnabled { get; private set; } = false;
|
||||||
|
|
||||||
protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
|
protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
|
||||||
{
|
{
|
||||||
@@ -23,5 +29,19 @@ namespace Microsoft.eShopWeb.ApplicationCore.Specifications
|
|||||||
{
|
{
|
||||||
IncludeStrings.Add(includeString);
|
IncludeStrings.Add(includeString);
|
||||||
}
|
}
|
||||||
|
protected virtual void ApplyPaging(int skip, int take)
|
||||||
|
{
|
||||||
|
Skip = skip;
|
||||||
|
Take = take;
|
||||||
|
isPagingEnabled = true;
|
||||||
|
}
|
||||||
|
protected virtual void ApplyOrderBy(Expression<Func<T, object>> orderByExpression)
|
||||||
|
{
|
||||||
|
OrderBy = orderByExpression;
|
||||||
|
}
|
||||||
|
protected virtual void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression)
|
||||||
|
{
|
||||||
|
OrderByDescending = orderByDescendingExpression;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.ApplicationCore.Specifications
|
||||||
|
{
|
||||||
|
public class CatalogFilterPaginatedSpecification : BaseSpecification<CatalogItem>
|
||||||
|
{
|
||||||
|
public CatalogFilterPaginatedSpecification(int skip, int take, int? brandId, int? typeId)
|
||||||
|
: base(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&
|
||||||
|
(!typeId.HasValue || i.CatalogTypeId == typeId))
|
||||||
|
{
|
||||||
|
ApplyPaging(skip, take);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,10 @@ namespace Microsoft.eShopWeb.Infrastructure.Data
|
|||||||
{
|
{
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
}
|
}
|
||||||
|
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
|
||||||
|
{
|
||||||
|
return SpecificationEvaluator<T>.GetQuery(_dbContext.Set<T>().AsQueryable(), spec);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual T GetById(int id)
|
public virtual T GetById(int id)
|
||||||
{
|
{
|
||||||
@@ -42,44 +46,26 @@ namespace Microsoft.eShopWeb.Infrastructure.Data
|
|||||||
return _dbContext.Set<T>().AsEnumerable();
|
return _dbContext.Set<T>().AsEnumerable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<T>> ListAllAsync()
|
public async Task<IReadOnlyList<T>> ListAllAsync()
|
||||||
{
|
{
|
||||||
return await _dbContext.Set<T>().ToListAsync();
|
return await _dbContext.Set<T>().ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<T> List(ISpecification<T> spec)
|
public IEnumerable<T> List(ISpecification<T> spec)
|
||||||
{
|
{
|
||||||
// fetch a Queryable that includes all expression-based includes
|
return ApplySpecification(spec).AsEnumerable();
|
||||||
var queryableResultWithIncludes = spec.Includes
|
|
||||||
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
|
||||||
(current, include) => current.Include(include));
|
|
||||||
|
|
||||||
// modify the IQueryable to include any string-based include statements
|
|
||||||
var secondaryResult = spec.IncludeStrings
|
|
||||||
.Aggregate(queryableResultWithIncludes,
|
|
||||||
(current, include) => current.Include(include));
|
|
||||||
|
|
||||||
// return the result of the query using the specification's criteria expression
|
|
||||||
return secondaryResult
|
|
||||||
.Where(spec.Criteria)
|
|
||||||
.AsEnumerable();
|
|
||||||
}
|
}
|
||||||
public async Task<List<T>> ListAsync(ISpecification<T> spec)
|
public async Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec)
|
||||||
{
|
{
|
||||||
// fetch a Queryable that includes all expression-based includes
|
return await ApplySpecification(spec).ToListAsync();
|
||||||
var queryableResultWithIncludes = spec.Includes
|
}
|
||||||
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
public int Count(ISpecification<T> spec)
|
||||||
(current, include) => current.Include(include));
|
{
|
||||||
|
return ApplySpecification(spec).Count();
|
||||||
// modify the IQueryable to include any string-based include statements
|
}
|
||||||
var secondaryResult = spec.IncludeStrings
|
public async Task<int> CountAsync(ISpecification<T> spec)
|
||||||
.Aggregate(queryableResultWithIncludes,
|
{
|
||||||
(current, include) => current.Include(include));
|
return await ApplySpecification(spec).CountAsync();
|
||||||
|
|
||||||
// return the result of the query using the specification's criteria expression
|
|
||||||
return await secondaryResult
|
|
||||||
.Where(spec.Criteria)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public T Add(T entity)
|
public T Add(T entity)
|
||||||
|
|||||||
44
src/Infrastructure/Data/SpecificationEvaluator.cs
Normal file
44
src/Infrastructure/Data/SpecificationEvaluator.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.Infrastructure.Data
|
||||||
|
{
|
||||||
|
public class SpecificationEvaluator<T> where T : BaseEntity
|
||||||
|
{
|
||||||
|
public static IQueryable<T> GetQuery(IQueryable<T> inputQuery, ISpecification<T> specification)
|
||||||
|
{
|
||||||
|
var query = inputQuery;
|
||||||
|
|
||||||
|
// modify the IQueryable using the specification's criteria expression
|
||||||
|
if (specification.Criteria != null)
|
||||||
|
query = query.Where(specification.Criteria);
|
||||||
|
|
||||||
|
// Includes all expression-based includes
|
||||||
|
query = specification.Includes.Aggregate(query,
|
||||||
|
(current, include) => current.Include(include));
|
||||||
|
|
||||||
|
// Include any string-based include statements
|
||||||
|
query = specification.IncludeStrings.Aggregate(query,
|
||||||
|
(current, include) => current.Include(include));
|
||||||
|
|
||||||
|
// Apply ordering if expressions are set
|
||||||
|
if (specification.OrderBy != null)
|
||||||
|
query = query.OrderBy(specification.OrderBy);
|
||||||
|
else if (specification.OrderByDescending != null)
|
||||||
|
query = query.OrderByDescending(specification.OrderByDescending);
|
||||||
|
|
||||||
|
// Apply paging if enabled
|
||||||
|
if (specification.isPagingEnabled)
|
||||||
|
{
|
||||||
|
query = query.Skip(specification.Skip)
|
||||||
|
.Take(specification.Take);
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,14 +42,12 @@ namespace Microsoft.eShopWeb.Web.Services
|
|||||||
_logger.LogInformation("GetCatalogItems called.");
|
_logger.LogInformation("GetCatalogItems called.");
|
||||||
|
|
||||||
var filterSpecification = new CatalogFilterSpecification(brandId, typeId);
|
var filterSpecification = new CatalogFilterSpecification(brandId, typeId);
|
||||||
var root = _itemRepository.List(filterSpecification);
|
var filterPaginatedSpecification =
|
||||||
|
new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId);
|
||||||
|
|
||||||
var totalItems = root.Count();
|
// the implementation below using ForEach and Count. We need a List.
|
||||||
|
var itemsOnPage = _itemRepository.List(filterPaginatedSpecification).ToList();
|
||||||
var itemsOnPage = root
|
var totalItems = _itemRepository.Count(filterSpecification);
|
||||||
.Skip(itemsPage * pageIndex)
|
|
||||||
.Take(itemsPage)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
itemsOnPage.ForEach(x =>
|
itemsOnPage.ForEach(x =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,14 +43,12 @@ namespace Microsoft.eShopWeb.RazorPages.Services
|
|||||||
_logger.LogInformation("GetCatalogItems called.");
|
_logger.LogInformation("GetCatalogItems called.");
|
||||||
|
|
||||||
var filterSpecification = new CatalogFilterSpecification(brandId, typeId);
|
var filterSpecification = new CatalogFilterSpecification(brandId, typeId);
|
||||||
var root = _itemRepository.List(filterSpecification);
|
var filterPaginatedSpecification =
|
||||||
|
new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId);
|
||||||
|
|
||||||
var totalItems = root.Count();
|
// the implementation below using ForEach and Count. We need a List.
|
||||||
|
var itemsOnPage = _itemRepository.List(filterPaginatedSpecification).ToList();
|
||||||
var itemsOnPage = root
|
var totalItems = _itemRepository.Count(filterSpecification);
|
||||||
.Skip(itemsPage * pageIndex)
|
|
||||||
.Take(itemsPage)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
itemsOnPage.ForEach(x =>
|
itemsOnPage.ForEach(x =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user