From e5614178c8fc64e14471d62795550abf9cc0408c 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)); } } }