Cold independent review of 017ef988d. Three findings taken, one filed out.
The important one is a regression this branch INTRODUCED. `SongMetadata.Artists`
is a nullable EF primitive collection (JSON in one column, not a navigation)
that FallbackMetadataProvider leaves unassigned when a song's tags fail to read,
and `string.Join` throws ArgumentNullException on a null sequence. The rerun
list previously did not load SongMetadata at all, so the throw was unreachable
there; adding the include promoted it to a live 500 that would have failed the
whole page. Confirmed by reverting the guard: ArgumentNullException, parameter
'values'. The file header claiming every member was guarded was false.
The empty case is filtered too, so an artist-less song loses its bare " - ".
Second: `GetPlaylistItemsHandler` had no handler-level test at all (its
controller tests stub the mediator), so the RemoteStream include added last
round was discharged by inspection -- the same method that produced #671. It
now runs the same 13-type matrix via a shared SelectionSeedData; removing the
include fails that matrix.
Third: dropped the dead `(i as Season).SeasonMetadata` include leg -- the Season
projection reads Show.ShowMetadata and the scalar SeasonNumber, never
SeasonMetadata.
Filed #690 for the pre-existing, out-of-scope finding: the paged TotalCount
ignores the search query, so the SPA renders empty pages.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
58 lines
3.1 KiB
C#
58 lines
3.1 KiB
C#
using ErsatzTV.Core.Domain;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace ErsatzTV.Application.MediaCollections;
|
|
|
|
internal static class RerunCollectionQueryExtensions
|
|
{
|
|
/// <summary>
|
|
/// The single source of truth for the navigation graph a <see cref="RerunCollection" /> needs before it
|
|
/// can be projected via <see cref="Mapper.ProjectToViewModel(RerunCollection)" />. Both the paged-list
|
|
/// and by-id handlers reload through this chain so the two cannot drift apart again (see #671 — the list
|
|
/// handler had no includes at all, so every row projected a null selection, while the by-id handler
|
|
/// covered only Movie/Season/Show/Artist and so returned a null selection for Song/OtherVideo/Image and
|
|
/// a 500 for Episode/MusicVideo).
|
|
/// Because the id and the display name are both read off these navigations, an un-included type does not
|
|
/// merely lose its label — it loses the selected id too, which is what silently cleared a stored
|
|
/// selection in the editor.
|
|
/// Deliberately narrower than the analogous playlist-item chain in <c>GetPlaylistItemsHandler</c>: the
|
|
/// rerun projection reads only each selection's id and title, never its artwork, so the
|
|
/// <c>.ThenInclude(… => …Artwork)</c> legs are omitted rather than paid for on every page.
|
|
/// </summary>
|
|
public static IQueryable<RerunCollection> IncludeSelectionDetails(this IQueryable<RerunCollection> query) =>
|
|
query
|
|
.Include(c => c.Collection)
|
|
.Include(c => c.MultiCollection)
|
|
.Include(c => c.SmartCollection)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as Movie).MovieMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as Show).ShowMetadata)
|
|
// No (i as Season).SeasonMetadata leg on purpose: ProjectToViewModel(Season) builds its name
|
|
// from Show.ShowMetadata and the scalar SeasonNumber, and never reads SeasonMetadata.
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as Season).Show)
|
|
.ThenInclude(s => s.ShowMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as Artist).ArtistMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as Episode).EpisodeMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as Episode).Season)
|
|
.ThenInclude(s => s.Show)
|
|
.ThenInclude(s => s.ShowMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as MusicVideo).MusicVideoMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as MusicVideo).Artist)
|
|
.ThenInclude(a => a.ArtistMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as OtherVideo).OtherVideoMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as Song).SongMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as Image).ImageMetadata)
|
|
.Include(c => c.MediaItem)
|
|
.ThenInclude(i => (i as RemoteStream).RemoteStreamMetadata);
|
|
}
|