From a1f7756fc95503e9539efdba8195f974e1f89e98 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Wed, 15 Jul 2026 16:09:54 +0200 Subject: [PATCH] fix(backend): compute track listens count via SQL instead of an unloaded navigation TrackApplicationResponse.FromEntity counted track.ListeningHistories, but none of the queries that built a response ever Included it, so the collection was always empty and listensCount was always 0. Each query now projects the count as a correlated subquery before mapping to the response. --- .../Musify.Application/PlayLists/GetPlayListTracks.cs | 9 +++++++-- backend/Musify.Application/Tracks/CreateTrack.cs | 2 +- .../Musify.Application/Tracks/GetListeningHistory.cs | 7 ++++--- backend/Musify.Application/Tracks/GetTrackById.cs | 8 ++++---- backend/Musify.Application/Tracks/GetTracks.cs | 9 +++++++-- backend/Musify.Application/Tracks/GetTracksByUserId.cs | 9 +++++++-- .../Tracks/Responses/TrackApplicationResponse.cs | 10 +++------- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/backend/Musify.Application/PlayLists/GetPlayListTracks.cs b/backend/Musify.Application/PlayLists/GetPlayListTracks.cs index 47e7bed..940dfd1 100644 --- a/backend/Musify.Application/PlayLists/GetPlayListTracks.cs +++ b/backend/Musify.Application/PlayLists/GetPlayListTracks.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Musify.Application.Contracts; using Musify.Application.Shared; using Musify.Application.Tracks.Responses; +using X.PagedList; using X.PagedList.EF; namespace Musify.Application.PlayLists @@ -32,11 +33,15 @@ namespace Musify.Application.PlayLists var totalCount = await tracksQuery.CountAsync(cancellationToken); - var pagedTracks = await tracksQuery + var pagedEntities = await tracksQuery .OrderBy(plt => plt.Position) - .Select(plt => TrackApplicationResponse.FromEntity(plt.Track)) + .Select(plt => new { plt.Track, ListensCount = plt.Track.ListeningHistories.Count }) .ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken); + var pagedTracks = new StaticPagedList( + pagedEntities.Select(x => TrackApplicationResponse.FromEntity(x.Track, x.ListensCount)), + pagedEntities.PageNumber, pagedEntities.PageSize, pagedEntities.TotalItemCount); + return PaginatedResponse.FromPagedList(pagedTracks); } } diff --git a/backend/Musify.Application/Tracks/CreateTrack.cs b/backend/Musify.Application/Tracks/CreateTrack.cs index e891ff6..9afafe0 100644 --- a/backend/Musify.Application/Tracks/CreateTrack.cs +++ b/backend/Musify.Application/Tracks/CreateTrack.cs @@ -123,7 +123,7 @@ namespace Musify.Application.Tracks return Error.Failure(description: $"Failed to create track for {request.Title}"); } - return TrackApplicationResponse.FromEntity(trackEntity); + return TrackApplicationResponse.FromEntity(trackEntity, listensCount: 0); } } } diff --git a/backend/Musify.Application/Tracks/GetListeningHistory.cs b/backend/Musify.Application/Tracks/GetListeningHistory.cs index 99332c1..679bd71 100644 --- a/backend/Musify.Application/Tracks/GetListeningHistory.cs +++ b/backend/Musify.Application/Tracks/GetListeningHistory.cs @@ -17,10 +17,11 @@ namespace Musify.Application.Tracks var listeningHistory = database.ListeningHistories .Where(l => l.UserId == query.UserId) .OrderByDescending(l => l.ListenedAt) - .Select(t => TrackApplicationResponse.FromEntity(t.Track)) + .Select(t => new { t.Track, ListensCount = t.Track.ListeningHistories.Count }) + .AsEnumerable() + .DistinctBy(t => t.Track.Id) .Take(ListSize) - .DistinctBy(t => t.Id) - .AsEnumerable(); + .Select(t => TrackApplicationResponse.FromEntity(t.Track, t.ListensCount)); return ValueTask.FromResult(listeningHistory); } diff --git a/backend/Musify.Application/Tracks/GetTrackById.cs b/backend/Musify.Application/Tracks/GetTrackById.cs index 861194c..21d4fb7 100644 --- a/backend/Musify.Application/Tracks/GetTrackById.cs +++ b/backend/Musify.Application/Tracks/GetTrackById.cs @@ -14,15 +14,15 @@ namespace Musify.Application.Tracks { public async ValueTask> Handle(GetTrackByIdQuery request, CancellationToken cancellationToken) { - var track = await database.Tracks.AsNoTracking() + var entity = await database.Tracks.AsNoTracking() .Where(t => t.Id == request.TrackId) - .Select(t => TrackApplicationResponse.FromEntity(t)) + .Select(t => new { Track = t, ListensCount = t.ListeningHistories.Count }) .SingleOrDefaultAsync(cancellationToken); - if (track is null) + if (entity is null) return Error.NotFound(); - return track; + return TrackApplicationResponse.FromEntity(entity.Track, entity.ListensCount); } } } diff --git a/backend/Musify.Application/Tracks/GetTracks.cs b/backend/Musify.Application/Tracks/GetTracks.cs index 2d8f6a2..a69f332 100644 --- a/backend/Musify.Application/Tracks/GetTracks.cs +++ b/backend/Musify.Application/Tracks/GetTracks.cs @@ -3,6 +3,7 @@ using Mediator; using Microsoft.EntityFrameworkCore; using Musify.Application.Shared; using Musify.Application.Tracks.Responses; +using X.PagedList; using X.PagedList.EF; using Musify.Application.Contracts; @@ -29,11 +30,15 @@ namespace Musify.Application.Tracks var totalCount = await tracksQuery.CountAsync(cancellationToken); - var pagedTracks = await tracksQuery + var pagedEntities = await tracksQuery .OrderBy(t => t.Id) - .Select(t => TrackApplicationResponse.FromEntity(t.Track)) + .Select(t => new { t.Track, ListensCount = t.Track.ListeningHistories.Count }) .ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken); + var pagedTracks = new StaticPagedList( + pagedEntities.Select(x => TrackApplicationResponse.FromEntity(x.Track, x.ListensCount)), + pagedEntities.PageNumber, pagedEntities.PageSize, pagedEntities.TotalItemCount); + return PaginatedResponse.FromPagedList(pagedTracks); } } diff --git a/backend/Musify.Application/Tracks/GetTracksByUserId.cs b/backend/Musify.Application/Tracks/GetTracksByUserId.cs index 48f1f6d..cd1c063 100644 --- a/backend/Musify.Application/Tracks/GetTracksByUserId.cs +++ b/backend/Musify.Application/Tracks/GetTracksByUserId.cs @@ -3,6 +3,7 @@ using Mediator; using Microsoft.EntityFrameworkCore; using Musify.Application.Shared; using Musify.Application.Tracks.Responses; +using X.PagedList; using X.PagedList.EF; using Musify.Application.Contracts; @@ -36,11 +37,15 @@ namespace Musify.Application.Tracks var totalCount = await tracksQuery.CountAsync(cancellationToken); - var pagedTracks = await tracksQuery + var pagedEntities = await tracksQuery .OrderBy(t => t.Track.CreatedAt) - .Select(t => TrackApplicationResponse.FromEntity(t.Track)) + .Select(t => new { t.Track, ListensCount = t.Track.ListeningHistories.Count }) .ToPagedListAsync(pageNumber, pageSize, totalCount, cancellationToken); + var pagedTracks = new StaticPagedList( + pagedEntities.Select(x => TrackApplicationResponse.FromEntity(x.Track, x.ListensCount)), + pagedEntities.PageNumber, pagedEntities.PageSize, pagedEntities.TotalItemCount); + return PaginatedResponse.FromPagedList(pagedTracks); } } diff --git a/backend/Musify.Application/Tracks/Responses/TrackApplicationResponse.cs b/backend/Musify.Application/Tracks/Responses/TrackApplicationResponse.cs index 9c83465..6756946 100644 --- a/backend/Musify.Application/Tracks/Responses/TrackApplicationResponse.cs +++ b/backend/Musify.Application/Tracks/Responses/TrackApplicationResponse.cs @@ -10,17 +10,13 @@ namespace Musify.Application.Tracks.Responses DateTime CreatedAt, DateTime UpdatedAt) { - public static TrackApplicationResponse FromEntity(Track track) - { - var count = track.ListeningHistories.Count(x => x.TrackId == track.Id); - - return new TrackApplicationResponse( + public static TrackApplicationResponse FromEntity(Track track, int listensCount) => + new( track.Id, track.Title, track.Duration, - count, + listensCount, track.CreatedAt, track.UpdatedAt); - } } } \ No newline at end of file