diff --git a/src/ApplicationCore/Interfaces/IAsyncRepository.cs b/src/ApplicationCore/Interfaces/IAsyncRepository.cs index dac6b14..459f607 100644 --- a/src/ApplicationCore/Interfaces/IAsyncRepository.cs +++ b/src/ApplicationCore/Interfaces/IAsyncRepository.cs @@ -7,10 +7,11 @@ namespace Microsoft.eShopWeb.ApplicationCore.Interfaces public interface IAsyncRepository where T : BaseEntity { Task GetByIdAsync(int id); - Task> ListAllAsync(); - Task> ListAsync(ISpecification spec); + Task> ListAllAsync(); + Task> ListAsync(ISpecification spec); Task AddAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(T entity); + Task CountAsync(ISpecification spec); } } diff --git a/src/ApplicationCore/Interfaces/IRepository.cs b/src/ApplicationCore/Interfaces/IRepository.cs index 6d56ec1..f7bf513 100644 --- a/src/ApplicationCore/Interfaces/IRepository.cs +++ b/src/ApplicationCore/Interfaces/IRepository.cs @@ -12,5 +12,6 @@ namespace Microsoft.eShopWeb.ApplicationCore.Interfaces T Add(T entity); void Update(T entity); void Delete(T entity); + int Count(ISpecification spec); } } diff --git a/src/ApplicationCore/Interfaces/ISpecification.cs b/src/ApplicationCore/Interfaces/ISpecification.cs index 27e388f..a61420f 100644 --- a/src/ApplicationCore/Interfaces/ISpecification.cs +++ b/src/ApplicationCore/Interfaces/ISpecification.cs @@ -9,5 +9,11 @@ namespace Microsoft.eShopWeb.ApplicationCore.Interfaces Expression> Criteria { get; } List>> Includes { get; } List IncludeStrings { get; } + Expression> OrderBy { get; } + Expression> OrderByDescending { get; } + + int Take { get; } + int Skip { get; } + bool isPagingEnabled { get;} } } diff --git a/src/ApplicationCore/Specifications/BaseSpecification.cs b/src/ApplicationCore/Specifications/BaseSpecification.cs index beaa21c..1017f3d 100644 --- a/src/ApplicationCore/Specifications/BaseSpecification.cs +++ b/src/ApplicationCore/Specifications/BaseSpecification.cs @@ -14,6 +14,12 @@ namespace Microsoft.eShopWeb.ApplicationCore.Specifications public Expression> Criteria { get; } public List>> Includes { get; } = new List>>(); public List IncludeStrings { get; } = new List(); + public Expression> OrderBy { get; private set; } + public Expression> 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> includeExpression) { @@ -23,5 +29,19 @@ namespace Microsoft.eShopWeb.ApplicationCore.Specifications { IncludeStrings.Add(includeString); } + protected virtual void ApplyPaging(int skip, int take) + { + Skip = skip; + Take = take; + isPagingEnabled = true; + } + protected virtual void ApplyOrderBy(Expression> orderByExpression) + { + OrderBy = orderByExpression; + } + protected virtual void ApplyOrderByDescending(Expression> orderByDescendingExpression) + { + OrderByDescending = orderByDescendingExpression; + } } } diff --git a/src/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs b/src/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs new file mode 100644 index 0000000..f24b8c6 --- /dev/null +++ b/src/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs @@ -0,0 +1,14 @@ +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.ApplicationCore.Specifications +{ + public class CatalogFilterPaginatedSpecification : BaseSpecification + { + 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); + } + } +} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs index 968d4c1..4a0f624 100644 --- a/src/Infrastructure/Data/EfRepository.cs +++ b/src/Infrastructure/Data/EfRepository.cs @@ -20,6 +20,10 @@ namespace Microsoft.eShopWeb.Infrastructure.Data { _dbContext = dbContext; } + private IQueryable ApplySpecification(ISpecification spec) + { + return SpecificationEvaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } public virtual T GetById(int id) { @@ -42,44 +46,26 @@ namespace Microsoft.eShopWeb.Infrastructure.Data return _dbContext.Set().AsEnumerable(); } - public async Task> ListAllAsync() + public async Task> ListAllAsync() { return await _dbContext.Set().ToListAsync(); } public IEnumerable List(ISpecification spec) { - // fetch a Queryable that includes all expression-based includes - var queryableResultWithIncludes = spec.Includes - .Aggregate(_dbContext.Set().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(); + return ApplySpecification(spec).AsEnumerable(); } - public async Task> ListAsync(ISpecification spec) + public async Task> ListAsync(ISpecification spec) { - // fetch a Queryable that includes all expression-based includes - var queryableResultWithIncludes = spec.Includes - .Aggregate(_dbContext.Set().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 await secondaryResult - .Where(spec.Criteria) - .ToListAsync(); + return await ApplySpecification(spec).ToListAsync(); + } + public int Count(ISpecification spec) + { + return ApplySpecification(spec).Count(); + } + public async Task CountAsync(ISpecification spec) + { + return await ApplySpecification(spec).CountAsync(); } public T Add(T entity) diff --git a/src/Infrastructure/Data/SpecificationEvaluator.cs b/src/Infrastructure/Data/SpecificationEvaluator.cs new file mode 100644 index 0000000..0372e48 --- /dev/null +++ b/src/Infrastructure/Data/SpecificationEvaluator.cs @@ -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 where T : BaseEntity + { + public static IQueryable GetQuery(IQueryable inputQuery, ISpecification 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; + } + } +} diff --git a/src/Web/Interfaces/IBasketService.cs b/src/Web/Interfaces/IBasketViewModelService.cs similarity index 100% rename from src/Web/Interfaces/IBasketService.cs rename to src/Web/Interfaces/IBasketViewModelService.cs diff --git a/src/Web/Services/CatalogService.cs b/src/Web/Services/CatalogService.cs index e24794b..fbbd323 100644 --- a/src/Web/Services/CatalogService.cs +++ b/src/Web/Services/CatalogService.cs @@ -42,14 +42,12 @@ namespace Microsoft.eShopWeb.Web.Services _logger.LogInformation("GetCatalogItems called."); var filterSpecification = new CatalogFilterSpecification(brandId, typeId); - var root = _itemRepository.List(filterSpecification); + var filterPaginatedSpecification = + new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId); - var totalItems = root.Count(); - - var itemsOnPage = root - .Skip(itemsPage * pageIndex) - .Take(itemsPage) - .ToList(); + // the implementation below using ForEach and Count. We need a List. + var itemsOnPage = _itemRepository.List(filterPaginatedSpecification).ToList(); + var totalItems = _itemRepository.Count(filterSpecification); itemsOnPage.ForEach(x => { diff --git a/src/WebRazorPages/Services/CatalogService.cs b/src/WebRazorPages/Services/CatalogService.cs index d41a218..8f4d968 100644 --- a/src/WebRazorPages/Services/CatalogService.cs +++ b/src/WebRazorPages/Services/CatalogService.cs @@ -43,14 +43,12 @@ namespace Microsoft.eShopWeb.RazorPages.Services _logger.LogInformation("GetCatalogItems called."); var filterSpecification = new CatalogFilterSpecification(brandId, typeId); - var root = _itemRepository.List(filterSpecification); + var filterPaginatedSpecification = + new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId); - var totalItems = root.Count(); - - var itemsOnPage = root - .Skip(itemsPage * pageIndex) - .Take(itemsPage) - .ToList(); + // the implementation below using ForEach and Count. We need a List. + var itemsOnPage = _itemRepository.List(filterPaginatedSpecification).ToList(); + var totalItems = _itemRepository.Count(filterSpecification); itemsOnPage.ForEach(x => {