Skip to content

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

Description

@Dreamescaper

Contains over an object[]/List<object> parameter against a column with an element value converter throws at query compilation:

System.ArgumentException: Expression of type 'System.Object' cannot be used for parameter of type 'System.Int32' (Parameter 'arg0')
   at System.Linq.Expressions.Expression.Invoke(Expression expression, Expression arg0)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.ValueConversion.NpgsqlArrayConverter`3.ArrayConversionExpression[TInput,TOutput,TConcreteOutput](LambdaExpression elementConversionExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.ValueConversion.NpgsqlArrayConverter`3..ctor(ValueConverter elementConverter)
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Constructor(...)

(TargetInvocationException wrapping the ArgumentException when the converter is constructed reflectively.)

Repro

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

var ids = new object[] { 1, 2 };          // also List<object> { 1, 2 }
using var ctx = new Ctx();
ctx.Blogs.Where(b => ids.Contains(b.Id)).ToQueryString();

sealed class Blog { public int Id { get; set; } }

sealed class Ctx : DbContext
{
    public DbSet<Blog> Blogs => Set<Blog>();
    protected override void OnConfiguring(DbContextOptionsBuilder b)
        => b.UseNpgsql("Host=localhost;Database=x;Username=u");
    protected override void OnModelCreating(ModelBuilder b)
        => b.Entity<Blog>().Property(x => x.Id)
            .HasConversion(new ValueConverter<int, long>(v => v, v => checked((int)v)))
            .HasColumnType("bigint");
}

Results with 10.0.3:

object[]       TargetInvocationException: ArgumentException: Expression of type 'System.Object' cannot be used for parameter of type 'System.Int32' (Parameter 'arg0')
List<object>   TargetInvocationException: ArgumentException: ...
int[]          OK
List<int>      OK

So only collections whose element type (object) differs from the converter's model type (int) fail; typed collections work.

Analysis

NpgsqlArrayTypeMapping.CreateParameters sees the element mapping's converter (int → long) and builds:

NpgsqlArrayConverter<object[], object[], long[]>(elementConverter: ValueConverter<int, long>)

NpgsqlArrayConverter.ArrayConversionExpression then does, in the indexer/for-loop path:

Invoke(elementConversionExpression, indexer(counter))

where indexer(counter) has the collection's element type (object), while the lambda's parameter is the converter's model type (int) — so Expression.Invoke rejects it. The nullable guard above only handles Nullable<T> input/output element types; the object (non-nullable, non-matching) case falls through. It would need to Convert/Unbox the element to elementConversionExpression.Parameters[0].Type before invoking (and the model-type validation in the ctor would need to tolerate the wider collection element type).

This looks like the same family as #2189 / #3050, but for an element type that is wider than the converter's model type rather than nullable.

Environment

  • Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 (also reproduces on 9.x per the closely-related reports)
  • EF Core 10.0.12, net10.0

Workaround: use a typed collection (int[] / List<int>).

(Found while building an EF Core provider for Aurora DSQL, which widens int identity keys to bigint with exactly this converter; the four failing tests are upstream of that provider.)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions