Skip to content

fix(search): use a translatable predicate in the three list searches … - #52

Open
wilderg88 wants to merge 1 commit into
suxrobGM:mainfrom
wilderg88:fix-untranslatable-search-predicates
Open

fix(search): use a translatable predicate in the three list searches …#52
wilderg88 wants to merge 1 commit into
suxrobGM:mainfrom
wilderg88:fix-untranslatable-search-predicates

Conversation

@wilderg88

Copy link
Copy Markdown
Contributor

string.Contains(value, StringComparison) has no SQL translation in EF Core, so a non-empty search term on three lists throws InvalidOperationException at materialisation instead of filtering:

  • GetExpensesHandler.cs:53-54 (throws at the CountAsync on the next line)
  • GetPortalLoadsHandler.cs:25
  • GetPortalInvoicesHandler.cs:26
    All three already compute var search = req.Search.ToLower() and then compare against a column that was never lowered, so the intent was case-insensitive matching and only the overload was wrong.

The fix uses the idiom this repo already uses for the same job in eight places, including GetAdminInvitationsHandler.cs:23 and GetInvitationsHandler.cs:37, which have the identical shape. Behaviour is unchanged for every input that previously worked; it now runs in SQL instead of throwing, and translates on any provider rather than one.

Adds SearchPredicateQueryTranslationTests following the existing TenantQuotaUsageQueryTranslationTests pattern in the same project - ToQueryString(), no database needed - covering all three predicates plus a control asserting the old shape still fails to translate.

One thing worth flagging beyond this fix: the existing handler tests for these queries use MockQueryable, i.e. LINQ to Objects, where the broken overload works fine. They cannot detect an EF translation failure by construction, which is probably why one shape reached three handlers.

Full suite: 1,263 passed, 4 skipped, 0 failed.

…that throw

`string.Contains(value, StringComparison)` has no SQL translation in EF Core,
so any non-empty search term on these three lists throws
InvalidOperationException at materialisation rather than filtering:

  - Modules/Financial/Expenses/Queries/GetExpenses/GetExpensesHandler.cs:53-54
    (throws at the CountAsync on the next line)
  - Modules/Platform/Portal/Queries/GetPortalLoads/GetPortalLoadsHandler.cs:25
  - Modules/Platform/Portal/Queries/GetPortalInvoices/GetPortalInvoicesHandler.cs:26

All three already compute `var search = req.Search.ToLower()` and then compare
against a column that was never lowered, so the intent was plainly
case-insensitive matching - only the overload was wrong.

The fix is this codebase's own established idiom, not a new one. `ToLower()
.Contains(term)` is already used for exactly this purpose in eight places,
including two handlers with the identical "filter a list by a search box"
shape: GetAdminInvitationsHandler.cs:23 and GetInvitationsHandler.cs:37. These
three handlers were the outliers; this brings them back in line, and it
translates to `lower(col) LIKE '%term%'` on any provider rather than only on
one.

Behaviour is unchanged for every input that previously worked: the term was
already lowercased, so lowering the column produces the same case-insensitive
match - except that it now runs in SQL instead of throwing.

Adds SearchPredicateQueryTranslationTests, following the existing
TenantQuotaUsageQueryTranslationTests pattern in the same project (force
translation with ToQueryString, no database required). It covers all three
predicates, plus a control test asserting the OLD shape still fails to
translate - without that control the other three would pass just as happily if
ToQueryString quietly stopped forcing translation.

The control is also the direct evidence for this fix: run on the real
Npgsql-configured TenantDbContext, `Contains(value, StringComparison)` throws
"could not be translated", which is the whole bug.

Worth flagging separately: the existing handler tests for these queries build
data with MockQueryable, which is LINQ to Objects. The broken overload works
perfectly in memory and throws against PostgreSQL, so those tests cannot tell
a working search from a broken one by construction. That is how one shape
reached three handlers. The new tests are in the Persistence project precisely
so they run against the real provider's translation.

Full suite: 1,263 passed, 4 skipped, 0 failed across all 10 test projects.
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

All contributors have signed the CLA.
Posted by the CLA Assistant Lite bot.

@wilderg88

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@wilderg88

Copy link
Copy Markdown
Contributor Author

recheck

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant