Skip to content

Commit 3f23ef6

Browse files
authored
Merge pull request #87 from Tr1sma/bolt/optimize-stats-service-14777551252098779145
⚡ Bolt: Optimize StatsService aggregation queries
2 parents 8c68739 + e8d0ec7 commit 3f23ef6

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

BookLoggerApp.Infrastructure/Services/StatsService.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ public async Task<int> GetTotalBooksReadAsync(CancellationToken ct = default)
2424

2525
public async Task<int> GetTotalPagesReadAsync(CancellationToken ct = default)
2626
{
27-
var completedBooks = await _unitOfWork.Books.GetBooksByStatusAsync(ReadingStatus.Completed);
28-
return completedBooks.Where(b => b.PageCount.HasValue).Sum(b => b.PageCount!.Value);
27+
// Optimized: Calculate sum in database to avoid loading all completed books into memory.
28+
return await _unitOfWork.Context.Set<Book>()
29+
.Where(b => b.Status == ReadingStatus.Completed && b.PageCount.HasValue)
30+
.SumAsync(b => b.PageCount!.Value, ct);
2931
}
3032

3133
public async Task<int> GetTotalMinutesReadAsync(CancellationToken ct = default)
3234
{
33-
var allSessions = await _unitOfWork.ReadingSessions.GetAllAsync();
34-
return allSessions.Sum(s => s.Minutes);
35+
// Optimized: Calculate sum in database using existing repository method.
36+
return await _unitOfWork.ReadingSessions.GetTotalMinutesAsync(ct);
3537
}
3638

3739
public async Task<int> GetCurrentStreakAsync(CancellationToken ct = default)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using FluentAssertions;
2+
using BookLoggerApp.Core.Models;
3+
using BookLoggerApp.Infrastructure.Data;
4+
using BookLoggerApp.Infrastructure.Services;
5+
using BookLoggerApp.Infrastructure.Repositories;
6+
using BookLoggerApp.Tests.TestHelpers;
7+
using Xunit;
8+
using Microsoft.EntityFrameworkCore;
9+
10+
namespace BookLoggerApp.Tests.Services;
11+
12+
public class StatsServicePerformanceTests : IDisposable
13+
{
14+
private readonly AppDbContext _context;
15+
private readonly IUnitOfWork _unitOfWork;
16+
private readonly StatsService _service;
17+
18+
public StatsServicePerformanceTests()
19+
{
20+
_context = TestDbContext.Create();
21+
_unitOfWork = new UnitOfWork(_context);
22+
_service = new StatsService(_unitOfWork);
23+
}
24+
25+
public void Dispose()
26+
{
27+
_context.Dispose();
28+
}
29+
30+
[Fact]
31+
public async Task GetTotalMinutesReadAsync_ShouldCalculateSumDatabaseSide_AndBeCorrect()
32+
{
33+
// Arrange
34+
await _unitOfWork.ReadingSessions.AddAsync(new ReadingSession
35+
{
36+
Minutes = 60,
37+
StartedAt = DateTime.UtcNow
38+
});
39+
await _unitOfWork.ReadingSessions.AddAsync(new ReadingSession
40+
{
41+
Minutes = 30,
42+
StartedAt = DateTime.UtcNow
43+
});
44+
await _context.SaveChangesAsync();
45+
46+
// Act
47+
var totalMinutes = await _service.GetTotalMinutesReadAsync();
48+
49+
// Assert
50+
totalMinutes.Should().Be(90);
51+
}
52+
53+
[Fact]
54+
public async Task GetTotalPagesReadAsync_ShouldCalculateSumDatabaseSide_AndBeCorrect()
55+
{
56+
// Arrange
57+
await _unitOfWork.Books.AddAsync(new Book
58+
{
59+
Title = "Book 1",
60+
Status = ReadingStatus.Completed,
61+
PageCount = 100
62+
});
63+
await _unitOfWork.Books.AddAsync(new Book
64+
{
65+
Title = "Book 2",
66+
Status = ReadingStatus.Completed,
67+
PageCount = 200
68+
});
69+
await _unitOfWork.Books.AddAsync(new Book
70+
{
71+
Title = "Book 3",
72+
Status = ReadingStatus.Reading, // Should be ignored
73+
PageCount = 50
74+
});
75+
await _context.SaveChangesAsync();
76+
77+
// Act
78+
var totalPages = await _service.GetTotalPagesReadAsync();
79+
80+
// Assert
81+
totalPages.Should().Be(300);
82+
}
83+
}

0 commit comments

Comments
 (0)