From 64f150dc0798d3f9334b7b72a0e71b5f0935f1e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Michel?= Date: Tue, 2 Nov 2021 15:16:57 +0100 Subject: [PATCH] replace counter by specific query to avoid load each items in memory (#611) * replace counter by specific query to avoid load each items in memory * Create IBasketQueryService --- .../Interfaces/IBasketQueryService.cs | 9 ++ .../Interfaces/IOrderRepository.cs | 11 --- src/Infrastructure/Data/BasketQueryService.cs | 27 ++++++ src/Infrastructure/Data/EfRepository.cs | 90 +------------------ src/Infrastructure/Data/OrderRepository.cs | 22 ----- .../Configuration/ConfigureCoreServices.cs | 3 +- src/Web/Services/BasketViewModelService.cs | 17 ++-- 7 files changed, 50 insertions(+), 129 deletions(-) create mode 100644 src/ApplicationCore/Interfaces/IBasketQueryService.cs delete mode 100644 src/ApplicationCore/Interfaces/IOrderRepository.cs create mode 100644 src/Infrastructure/Data/BasketQueryService.cs delete mode 100644 src/Infrastructure/Data/OrderRepository.cs diff --git a/src/ApplicationCore/Interfaces/IBasketQueryService.cs b/src/ApplicationCore/Interfaces/IBasketQueryService.cs new file mode 100644 index 0000000..325a998 --- /dev/null +++ b/src/ApplicationCore/Interfaces/IBasketQueryService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.ApplicationCore.Interfaces +{ + public interface IBasketQueryService + { + Task CountTotalBasketItems(string username); + } +} diff --git a/src/ApplicationCore/Interfaces/IOrderRepository.cs b/src/ApplicationCore/Interfaces/IOrderRepository.cs deleted file mode 100644 index 9341587..0000000 --- a/src/ApplicationCore/Interfaces/IOrderRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.ApplicationCore.Interfaces -{ - - //public interface IOrderRepository : IAsyncRepository - //{ - // Task GetByIdWithItemsAsync(int id); - //} -} diff --git a/src/Infrastructure/Data/BasketQueryService.cs b/src/Infrastructure/Data/BasketQueryService.cs new file mode 100644 index 0000000..136c5cb --- /dev/null +++ b/src/Infrastructure/Data/BasketQueryService.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class BasketQueryService : IBasketQueryService + { + private readonly CatalogContext _dbContext; + + public BasketQueryService(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public async Task CountTotalBasketItems(string username) + { + var totalItems = await _dbContext.Baskets + .Where(basket => basket.BuyerId == username) + .SelectMany(item => item.Items) + .SumAsync(sum => sum.Quantity); + + return totalItems; + } + } +} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs index 8ae0186..0c027a7 100644 --- a/src/Infrastructure/Data/EfRepository.cs +++ b/src/Infrastructure/Data/EfRepository.cs @@ -1,94 +1,12 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; +using Ardalis.Specification.EntityFrameworkCore; using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace Microsoft.eShopWeb.Infrastructure.Data { public class EfRepository : RepositoryBase, IReadRepository, IRepository where T : class, IAggregateRoot -{ - public EfRepository(CatalogContext dbContext) : base(dbContext) { + public EfRepository(CatalogContext dbContext) : base(dbContext) + { + } } -} - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - // public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - // { - // protected readonly CatalogContext _dbContext; - - // public EfRepository(CatalogContext dbContext) - // { - // _dbContext = dbContext; - // } - - // public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - // { - // var keyValues = new object[] { id }; - // return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - // } - - // public async Task> ListAllAsync(CancellationToken cancellationToken = default) - // { - // return await _dbContext.Set().ToListAsync(cancellationToken); - // } - - // public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - // { - // var specificationResult = ApplySpecification(spec); - // return await specificationResult.ToListAsync(cancellationToken); - // } - - // public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - // { - // var specificationResult = ApplySpecification(spec); - // return await specificationResult.CountAsync(cancellationToken); - // } - - // public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - // { - // await _dbContext.Set().AddAsync(entity, cancellationToken); - // await _dbContext.SaveChangesAsync(cancellationToken); - - // return entity; - // } - - // public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - // { - // _dbContext.Entry(entity).State = EntityState.Modified; - // await _dbContext.SaveChangesAsync(cancellationToken); - // } - - // public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - // { - // _dbContext.Set().Remove(entity); - // await _dbContext.SaveChangesAsync(cancellationToken); - // } - - // public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - // { - // var specificationResult = ApplySpecification(spec); - // return await specificationResult.FirstAsync(cancellationToken); - // } - - // public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - // { - // var specificationResult = ApplySpecification(spec); - // return await specificationResult.FirstOrDefaultAsync(cancellationToken); - // } - - // private IQueryable ApplySpecification(ISpecification spec) - // { - // var evaluator = new SpecificationEvaluator(); - // return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - // } - // } } \ No newline at end of file diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index cfb5410..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - //public class OrderRepository : EfRepository, IOrderRepository - //{ - // public OrderRepository(CatalogContext dbContext) : base(dbContext) - // { - // } - - // public Task GetByIdWithItemsAsync(int id) - // { - // return _dbContext.Orders - // .Include(o => o.OrderItems) - // .ThenInclude(i => i.ItemOrdered) - // .FirstOrDefaultAsync(x => x.Id == id); - // } - //} -} diff --git a/src/Web/Configuration/ConfigureCoreServices.cs b/src/Web/Configuration/ConfigureCoreServices.cs index 5e152a2..2a2c1c6 100644 --- a/src/Web/Configuration/ConfigureCoreServices.cs +++ b/src/Web/Configuration/ConfigureCoreServices.cs @@ -13,13 +13,12 @@ namespace Microsoft.eShopWeb.Web.Configuration public static IServiceCollection AddCoreServices(this IServiceCollection services, IConfiguration configuration) { - //services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>)); services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>)); services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); services.AddScoped(); services.AddScoped(); - //services.AddScoped(); + services.AddScoped(); services.AddSingleton(new UriComposer(configuration.Get())); services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); services.AddTransient(); diff --git a/src/Web/Services/BasketViewModelService.cs b/src/Web/Services/BasketViewModelService.cs index ce66cfd..310cac7 100644 --- a/src/Web/Services/BasketViewModelService.cs +++ b/src/Web/Services/BasketViewModelService.cs @@ -14,14 +14,17 @@ namespace Microsoft.eShopWeb.Web.Services { private readonly IRepository _basketRepository; private readonly IUriComposer _uriComposer; + private readonly IBasketQueryService _basketQueryService; private readonly IRepository _itemRepository; public BasketViewModelService(IRepository basketRepository, IRepository itemRepository, - IUriComposer uriComposer) + IUriComposer uriComposer, + IBasketQueryService basketQueryService) { _basketRepository = basketRepository; _uriComposer = uriComposer; + _basketQueryService = basketQueryService; _itemRepository = itemRepository; } @@ -36,7 +39,7 @@ namespace Microsoft.eShopWeb.Web.Services } var viewModel = await Map(basket); return viewModel; - } + } private async Task CreateBasketForUser(string userId) { @@ -79,18 +82,16 @@ namespace Microsoft.eShopWeb.Web.Services return new BasketViewModel() { BuyerId = basket.BuyerId, - Id = basket.Id, + Id = basket.Id, Items = await GetBasketItems(basket.Items) }; } public async Task CountTotalBasketItems(string username) { - var basketSpec = new BasketWithItemsSpecification(username); - var basket = await _basketRepository.GetBySpecAsync(basketSpec); - if (basket == null) - return 0; - return basket.Items.Sum(i => i.Quantity); + var counter = await _basketQueryService.CountTotalBasketItems(username); + + return counter; } } }