fix(data): route all TrackRepository queries through soft-delete-filtered Query
This commit is contained in:
@@ -11,42 +11,39 @@ namespace DeepDrftData.Repositories;
|
|||||||
|
|
||||||
public class TrackRepository : Repository<DeepDrftContext, TrackEntity>
|
public class TrackRepository : Repository<DeepDrftContext, TrackEntity>
|
||||||
{
|
{
|
||||||
private readonly DeepDrftContext _context;
|
|
||||||
|
|
||||||
public TrackRepository(
|
public TrackRepository(
|
||||||
DeepDrftContext context,
|
DeepDrftContext context,
|
||||||
ILogger<Repository<DeepDrftContext, TrackEntity>> logger,
|
ILogger<Repository<DeepDrftContext, TrackEntity>> logger,
|
||||||
IDbExceptionClassifier? classifier = null)
|
IDbExceptionClassifier? classifier = null)
|
||||||
: base(context, logger, classifier: classifier)
|
: base(context, logger, classifier: classifier)
|
||||||
{
|
{
|
||||||
_context = context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup by vault entry key. The base Repository<> only exposes id-based queries, so this
|
// Lookup by vault entry key. The base Repository<> only exposes id-based queries, so this
|
||||||
// queries the DbSet directly. Returns null on miss (service wraps in ResultContainer).
|
// uses Query (soft-delete filtered) rather than the raw DbSet.
|
||||||
public async Task<TrackEntity?> GetByEntryKeyAsync(string entryKey)
|
public async Task<TrackEntity?> GetByEntryKeyAsync(string entryKey)
|
||||||
=> await _context.Tracks.FirstOrDefaultAsync(t => t.EntryKey == entryKey);
|
=> await Query.FirstOrDefaultAsync(t => t.EntryKey == entryKey);
|
||||||
|
|
||||||
// Picks one track uniformly at random. Two round-trips (count, then a single offset row)
|
// Picks one track uniformly at random. Two round-trips (count, then a single offset row)
|
||||||
// rather than ORDER BY random() so the database never sorts the whole table — the catalogue
|
// rather than ORDER BY random() so the database never sorts the whole table — the catalogue
|
||||||
// is small today but this keeps the cost flat as it grows. Returns null when empty so the
|
// is small today but this keeps the cost flat as it grows. Returns null when empty so the
|
||||||
// service surfaces a valid empty-library state, not an error. Queries the DbSet directly,
|
// service surfaces a valid empty-library state, not an error. Uses Query (soft-delete
|
||||||
// mirroring GetByEntryKeyAsync, since the base Repository<> exposes only id-based reads.
|
// filtered) so deleted tracks are never candidates.
|
||||||
public async Task<TrackEntity?> GetRandomAsync(CancellationToken cancellationToken = default)
|
public async Task<TrackEntity?> GetRandomAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var count = await _context.Tracks.CountAsync(cancellationToken);
|
var count = await Query.CountAsync(cancellationToken);
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var index = Random.Shared.Next(count);
|
var index = Random.Shared.Next(count);
|
||||||
return await _context.Tracks
|
return await Query
|
||||||
.OrderBy(t => t.Id)
|
.OrderBy(t => t.Id)
|
||||||
.Skip(index)
|
.Skip(index)
|
||||||
.Take(1)
|
.Take(1)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paged query with optional filter predicates. Built directly off the DbSet rather than the
|
// Paged query with optional filter predicates. Built off Query (soft-delete filtered) rather than the
|
||||||
// base GetPagedAsync(paging) overload, which takes no where-clause. The OrderBy expression and
|
// base GetPagedAsync(paging) overload, which takes no where-clause. The OrderBy expression and
|
||||||
// direction ride in on the PagingParameters the manager already built, so sort + filter +
|
// direction ride in on the PagingParameters the manager already built, so sort + filter +
|
||||||
// pagination compose. Filter predicates apply before sort and Skip/Take so TotalCount reflects
|
// pagination compose. Filter predicates apply before sort and Skip/Take so TotalCount reflects
|
||||||
@@ -56,7 +53,7 @@ public class TrackRepository : Repository<DeepDrftContext, TrackEntity>
|
|||||||
TrackFilter? filter,
|
TrackFilter? filter,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
IQueryable<TrackEntity> query = _context.Tracks;
|
IQueryable<TrackEntity> query = Query;
|
||||||
|
|
||||||
if (filter is not null)
|
if (filter is not null)
|
||||||
{
|
{
|
||||||
@@ -105,7 +102,7 @@ public class TrackRepository : Repository<DeepDrftContext, TrackEntity>
|
|||||||
// Distinct albums (non-null) with track counts and a representative cover key. The cover is the
|
// Distinct albums (non-null) with track counts and a representative cover key. The cover is the
|
||||||
// first non-null ImagePath in the group; GroupBy + projection keeps it a single round-trip.
|
// first non-null ImagePath in the group; GroupBy + projection keeps it a single round-trip.
|
||||||
public async Task<List<AlbumSummaryDto>> GetDistinctAlbumsAsync(CancellationToken ct = default)
|
public async Task<List<AlbumSummaryDto>> GetDistinctAlbumsAsync(CancellationToken ct = default)
|
||||||
=> await _context.Tracks
|
=> await Query
|
||||||
.Where(t => t.Album != null)
|
.Where(t => t.Album != null)
|
||||||
.GroupBy(t => t.Album!)
|
.GroupBy(t => t.Album!)
|
||||||
.Select(g => new AlbumSummaryDto
|
.Select(g => new AlbumSummaryDto
|
||||||
@@ -123,7 +120,7 @@ public class TrackRepository : Repository<DeepDrftContext, TrackEntity>
|
|||||||
|
|
||||||
// Distinct genres (non-null) with track counts.
|
// Distinct genres (non-null) with track counts.
|
||||||
public async Task<List<GenreSummaryDto>> GetDistinctGenresAsync(CancellationToken ct = default)
|
public async Task<List<GenreSummaryDto>> GetDistinctGenresAsync(CancellationToken ct = default)
|
||||||
=> await _context.Tracks
|
=> await Query
|
||||||
.Where(t => t.Genre != null)
|
.Where(t => t.Genre != null)
|
||||||
.GroupBy(t => t.Genre!)
|
.GroupBy(t => t.Genre!)
|
||||||
.Select(g => new GenreSummaryDto
|
.Select(g => new GenreSummaryDto
|
||||||
|
|||||||
Reference in New Issue
Block a user