Skip to content

fix(metadata): restore author/series catalog cache on SQLite#649

Open
kevinheneveld wants to merge 1 commit into
Listenarrs:canaryfrom
kevinheneveld:fix/catalog-cache-sqlite-coalesce
Open

fix(metadata): restore author/series catalog cache on SQLite#649
kevinheneveld wants to merge 1 commit into
Listenarrs:canaryfrom
kevinheneveld:fix/catalog-cache-sqlite-coalesce

Conversation

@kevinheneveld

Copy link
Copy Markdown
Contributor

Summary

The persisted author and series metadata cache never returns a hit on SQLite, so every author and series page re-fetches the full catalog from Audible on each load. The cache read orders by a Nullable<DateTime>.GetValueOrDefault(...) expression the SQLite EF Core provider can't translate; the query throws, the caller's best-effort catch swallows it as a cache miss, and the cache is effectively dead. This switches the ordering to a translatable COALESCE and adds regression coverage that runs against the real SQLite provider.

Why this matters

Open any author or series page twice. The catalog should come from the persisted cache the second time, but instead each visit hits Audible again — slower pages, and avoidable load on the upstream Audible endpoints (and more exposure to their rate limits). On a library with many authors/series, ordinary browsing keeps re-fetching catalogs that were already cached minutes ago.

The cache looks like it works in tests because the EF InMemory provider happily evaluates the same LINQ that SQLite rejects — so the failure only appears against a real database.

Implementation notes

  • Surface: listenarr.infrastructure/Persistence/Repositories/AudiobookRepository.cs — four cache-read methods (GetCachedAuthorByNameAsync, GetCachedAuthorByAsinAsync, GetCachedSeriesByNameAsync, GetCachedSeriesByAsinAsync).
  • Change: OrderByDescending(e => e.LastFetchedAt.GetValueOrDefault(e.UpdatedAt))OrderByDescending(e => e.LastFetchedAt ?? e.UpdatedAt). The null-coalescing form maps to SQL COALESCE(LastFetchedAt, UpdatedAt); identical "freshest-first" ordering semantics, but translatable. An inline comment at each site records why, so the pattern doesn't regress.
  • No behavior change beyond the cache now actually being read: same ordering, same rows, same return shape.
  • Testing approach: a new test class deliberately uses the real SQLite provider (Microsoft.Data.Sqlite :memory:) rather than the EF InMemory provider, because InMemory can't reproduce the translation failure. The new tests fail against the current code with Translation of method 'System.DateTime?.GetValueOrDefault' failed and pass with the fix.

What this PR does NOT include

  • No changes to the cache write paths or the cache schema.
  • No unrelated catalog/series UI features — this is the bug fix in isolation.

Test plan

  • dotnet test tests/Listenarr.Tests.csproj --filter FullyQualifiedName~AudiobookRepository_CatalogCacheReadTests — 3/3 passing (new).
  • Verified the new tests fail on the current canary with the GetValueOrDefault translation error — they guard the actual regression.
  • dotnet test tests/Listenarr.Tests.csproj — full suite 685/685 passing.
  • Frontend untouched.

@kevinheneveld kevinheneveld marked this pull request as ready for review June 4, 2026 17:02
@kevinheneveld kevinheneveld requested a review from a team June 4, 2026 17:02
The four catalog-cache read paths in AudiobookRepository ordered results
by LastFetchedAt.GetValueOrDefault(UpdatedAt), which the SQLite EF Core
provider cannot translate. The query threw at execution time and each
caller's best-effort try/catch swallowed it as a cache miss, so the
persisted author/series catalog cache never returned a hit — every author
and series page re-fetched the full catalog from Audible on each load.

Use null-coalescing (entry.LastFetchedAt ?? entry.UpdatedAt), which maps
to SQL COALESCE with identical freshest-first ordering but is translatable.

Add regression coverage that exercises these reads against the real SQLite
provider; the EF InMemory provider tolerates the untranslatable LINQ and so
could not catch this.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@therobbiedavis therobbiedavis force-pushed the fix/catalog-cache-sqlite-coalesce branch from 1832d8f to a88bbf0 Compare June 9, 2026 13:41
@therobbiedavis therobbiedavis added the patch patch version bump - backward compatible bug fixes label Jun 9, 2026

@therobbiedavis therobbiedavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinheneveld This looks good to me. Please delete the changelog and update the PR description to match the template.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch patch version bump - backward compatible bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants