Skip to content

Fix Contains on an object collection over a value-converted column - #3961

Open
sanjaysingh wants to merge 2 commits into
npgsql:mainfrom
sanjaysingh:fix/3916-object-collection-contains
Open

sanjaysingh wants to merge 2 commits into
npgsql:mainfrom
sanjaysingh:fix/3916-object-collection-contains

Conversation

@sanjaysingh

Copy link
Copy Markdown
Contributor

Contains on an object[] or List<object> failed to compile when the column had a value converter. The collection element is object, but the converter expects the underlying type, such as int. Each element is now cast into the converter and back out, and object is accepted as that wider model element type.

Fixes #3916

Test plan

  • Unit tests for converting object[] and List<object>, and for still rejecting a real type mismatch
  • PostgreSQL tests: both collections return the matching rows, with = ANY

@roji roji left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation: keep the direction, but revise before merging. The reported scenario works with this PR, but manual validation found two ordinary variants that still fail; see the inline comments.

Problem

#3916 is a genuine provider bug. Given an int property converted to long, a typed collection works but an object-typed collection fails while building the array converter:

modelBuilder.Entity<Blog>().Property(b => b.Id)
    .HasConversion(v => (long)v, v => checked((int)v));

object[] ids = [1, 2];
await context.Blogs.Where(b => ids.Contains(b.Id)).ToListAsync();

Expected approach

Adapt the element converter's argument and result types once, before constructing null handling and either collection loop; allow object as the wider model element type; adapt the collection comparer and JSON reader consistently. This should remain a small PostgreSQL-provider change with focused regression coverage, not a new abstraction or broader EF change. No documentation change is needed for this bug fix.

Assessment

The comparer composition and JSON mapping changes are relevant, and retaining rejection of unrelated model-element types is good. The new unit tests check both conversion directions, and the functional tests verify actual results and ANY SQL for arrays and lists. However, four loop-site calls plus the new conversion helper are less direct than normalizing the element lambda centrally. Main already has that centralized normalization, so I would reuse it rather than add another conversion layer and a one-use matching helper.

Gaps

  1. String-backed converters still throw during construction. Changing the example's converter to v => v.ToString(), v => int.Parse(v) reproduces the original object/int expression error with both object arrays and lists. A string-to-string converter fails similarly. The existing nullable-wrapper code invokes the incompatible lambda before the new helper is reached.
  2. Null elements fail at parameter conversion. Keeping the original int-to-long converter but changing the input to object[] ids = [null!, 2] throws NullReferenceException; List<object> does too. An unconverted integer column handles that input, and a typed nullable collection handles a converted nullable property correctly. The widened input needs a null-preserving provider representation, not unconditional unboxing into int and storage in long[].

These are incomplete parts of the proposed object-collection support, not demonstrated regressions in previously working typed-collection queries. Add focused string-provider and null-element cases rather than substantially expanding the test infrastructure; the existing primitive-collection fixture already has converted-element query tests.

Manual validation

I built exact commit afbe3ef648b6659cc4930aa12d9f3f84676fa2bc with its pinned .NET 11 SDK and ran a separate console application against PostgreSQL 18.4. Both reported int-to-long queries returned IDs 1,2 through ANY. The string-backed and null-element failures above were reproduced through the actual provider API, including query execution for the successful cases and null controls. All seven converter tests, both new PostgreSQL tests, and the existing HashSet/Dictionary-values converter tests also passed, which demonstrates the coverage gaps rather than a failure of the supplied tests.

Posted by copilot

Comment thread src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs Outdated
Comment thread src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs Outdated
…ns can compile.

An object[] or List<object> was passed straight into an int converter, so the query failed to compile.
@sanjaysingh
sanjaysingh force-pushed the fix/3916-object-collection-contains branch from afbe3ef to 68beb15 Compare October 15, 2026 16:10
@sanjaysingh

Copy link
Copy Markdown
Contributor Author

Rebased onto main and removed the per-loop conversion helper. The element lambda is already normalized before the null wrapper, so an object collection with a string converter now builds.

A null in that collection is preserved by using a nullable provider element. An int-to-long converter stores the value in long?[] instead of unboxing it into long[]. Typed collections still use ToNullableComparer.

Added tests for a string converter and for null elements, including a match on a nullable converted property. The existing nullable-struct Contains test still passes.

This branch has not been deployed

No deployments
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.

object[]/List<object> Contains over a value-converted column throws in NpgsqlArrayConverter

2 participants