From 1438d9bf1af1eef80c24916c48e2814ede0e99d1 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Wed, 15 Jul 2026 16:12:26 +0200 Subject: [PATCH] fix(backend): stop loading a user's entire listening history into memory GetListeningHistory used AsEnumerable() before DistinctBy/Take, which forced EF Core to materialize every history row for the user just to keep the last 15 distinct tracks. Now groups by track and takes the 15 most recently played track ids in SQL, then fetches only those. --- .../Tracks/GetListeningHistory.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/backend/Musify.Application/Tracks/GetListeningHistory.cs b/backend/Musify.Application/Tracks/GetListeningHistory.cs index 679bd71..c688dab 100644 --- a/backend/Musify.Application/Tracks/GetListeningHistory.cs +++ b/backend/Musify.Application/Tracks/GetListeningHistory.cs @@ -1,4 +1,5 @@ using Mediator; +using Microsoft.EntityFrameworkCore; using Musify.Application.Contracts; using Musify.Application.Tracks.Responses; @@ -12,18 +13,24 @@ namespace Musify.Application.Tracks { public const int ListSize = 15; - public ValueTask> Handle(GetListeningHistoryQuery query, CancellationToken cancellationToken) + public async ValueTask> Handle(GetListeningHistoryQuery query, CancellationToken cancellationToken) { - var listeningHistory = database.ListeningHistories + var recentTrackIds = await database.ListeningHistories .Where(l => l.UserId == query.UserId) - .OrderByDescending(l => l.ListenedAt) - .Select(t => new { t.Track, ListensCount = t.Track.ListeningHistories.Count }) - .AsEnumerable() - .DistinctBy(t => t.Track.Id) + .GroupBy(l => l.TrackId) + .OrderByDescending(g => g.Max(l => l.ListenedAt)) + .Select(g => g.Key) .Take(ListSize) - .Select(t => TrackApplicationResponse.FromEntity(t.Track, t.ListensCount)); + .ToListAsync(cancellationToken); - return ValueTask.FromResult(listeningHistory); + var tracksById = await database.Tracks + .Where(t => recentTrackIds.Contains(t.Id)) + .Select(t => new { t.Id, Track = t, ListensCount = t.ListeningHistories.Count }) + .ToDictionaryAsync(t => t.Id, cancellationToken); + + return recentTrackIds + .Select(id => tracksById[id]) + .Select(t => TrackApplicationResponse.FromEntity(t.Track, t.ListensCount)); } } }