Skip to content

System.InvalidCastException: Object must implement IConvertible. when passing List to Intersect method on primitive collection of non-List type with element converter type #3805

Description

@zlepper

Repro:

Repro

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.Logging;

await using var context = new ReproContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

var toFindAsList = new List<MyId>
{
    new MyId(42)
};

_ = await context.Users
    .Where(user => user.RelatedIds.Intersect(toFindAsList).Any())
    .ToListAsync();

public class ReproContext : DbContext {
    public DbSet<ReproUser> Users => Set<ReproUser>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
        optionsBuilder
            .UseNpgsql("Host=localhost;Username=postgres;Password=passw0rd;database=repro")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<ReproUser>().PrimitiveCollection(u => u.RelatedIds).ElementType().HasConversion<MyIdValueConverter>();
    }
}

public class ReproUser
{
    public ulong Id { get; set; }
    public MyId[] RelatedIds { get; } = [];
}

public readonly record struct MyId(long Value);

public class MyIdValueConverter : ValueConverter<MyId, long>
{
    public MyIdValueConverter() : base(v => v.Value, v => new MyId(v)){}
}

(Reproduction is based on the reproduction here #3286 (comment))

This gives the following output:

/home/rasmus/projects/EfCoreReproduction/EfCoreReproduction/bin/Debug/net10.0/EfCoreReproduction
warn: 4/8/2026 07:55:27.364 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure) 
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 4/8/2026 07:55:27.662 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (24ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 4/8/2026 07:55:27.734 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (22ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      DROP DATABASE repro WITH (FORCE);
info: 4/8/2026 07:55:27.801 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (44ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE DATABASE repro;
info: 4/8/2026 07:55:27.822 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (7ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 4/8/2026 07:55:27.930 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (10ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "Users" (
          "Id" numeric(20,0) NOT NULL,
          "RelatedIds" bigint[] NOT NULL,
          CONSTRAINT "PK_Users" PRIMARY KEY ("Id")
      );
fail: 4/8/2026 07:55:28.221 CoreEventId.QueryIterationFailed[10100] (Microsoft.EntityFrameworkCore.Query) 
      An exception occurred while iterating over the results of a query for context type 'ReproContext'.
      System.InvalidCastException: Object must implement IConvertible.
         at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
         at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.Sanitize[T](Object value)
         at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.<>c__DisplayClass2_0`2.<SanitizeConverter>b__1(Object v)
         at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapping.CreateParameter(DbCommand command, String name, Object value, Nullable`1 nullable, ParameterDirection direction)
         at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping.NpgsqlArrayTypeMapping`3.CreateParameter(DbCommand command, String name, Object value, Nullable`1 nullable, ParameterDirection direction)
         at Microsoft.EntityFrameworkCore.Storage.Internal.TypeMappedRelationalParameter.AddDbParameter(DbCommand command, Object value)
         at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalParameterBase.AddDbParameter(DbCommand command, IReadOnlyDictionary`2 parameterValues)
         at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.CreateDbCommand(RelationalCommandParameterObject parameterObject, Guid commandId, DbCommandMethod commandMethod)
         at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
         at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
Unhandled exception. System.InvalidCastException: Object must implement IConvertible.
   at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.Sanitize[T](Object value)
   at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.<>c__DisplayClass2_0`2.<SanitizeConverter>b__1(Object v)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapping.CreateParameter(DbCommand command, String name, Object value, Nullable`1 nullable, ParameterDirection direction)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping.NpgsqlArrayTypeMapping`3.CreateParameter(DbCommand command, String name, Object value, Nullable`1 nullable, ParameterDirection direction)
   at Microsoft.EntityFrameworkCore.Storage.Internal.TypeMappedRelationalParameter.AddDbParameter(DbCommand command, Object value)
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalParameterBase.AddDbParameter(DbCommand command, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.CreateDbCommand(RelationalCommandParameterObject parameterObject, Guid commandId, DbCommandMethod commandMethod)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in /home/rasmus/projects/EfCoreReproduction/EfCoreReproduction/Program.cs:line 15
   at Program.<Main>$(String[] args) in /home/rasmus/projects/EfCoreReproduction/EfCoreReproduction/Program.cs:line 15
   at Program.<Main>(String[] args)

Process finished with exit code 134.

It seems to be a cast from the List<MyId> to MyId[] that fails, based on what i traced with my debugger.

Switching the ToFindAsList type to be an array, like this:

var toFindAsArray = new MyId[]
{
     new MyId(42)
};

 _ = await context.Users
     .Where(user => user.RelatedIds.Intersect(toFindAsArray).Any())
     .ToListAsync();

Works without any exceptions, however I don't expect to remember to trace all of that everywhere.

For some background context:
I'm working on switching all our keys to be "strongly typed", and i recall this working fine before, when everything was just int rather than MyId.

Activity

  1. DPDmancul commented on Apr 14, 2026

    @DPDmancul

    Curiosly enough .Where(user => user.RelatedIds.Any(x => toFindAsList.Contains(x))) still works. So the maybe the problem is not intrinsic in the collection translation, but specifically in the .Intersect(...).Any() translation.

  2. added theissue type on Apr 14, 2026
  3. roji commented on Apr 14, 2026

    @roji
    Member

    AI Triage

    The below is an AI-generated analysis and may contain inaccuracies.

    Summary

    Confirmed bug. When a primitive collection property has a non-matching CLR type from the LINQ parameter (e.g. property is MyId[] but the parameter is List<MyId>) and the element type has a value converter, Intersect(...).Any() throws InvalidCastException: Object must implement IConvertible.

    Root Cause

    The issue is in NpgsqlArrayTypeMapping.CreateParameter(). When the Intersect().Any() pattern is matched, it's translated to the PostgreSQL && (overlap) operator, and the type mapping from the column (NpgsqlArrayTypeMapping<MyId[], MyId[], long>) is applied to the List<MyId> parameter.

    In CreateParameter, when there is a value converter (Converter is not null), the code skips materialization of the enumerable entirely. The List<MyId> is then passed to base.CreateParameter(), which invokes ValueConverter.Sanitize<MyId[]>(). Sanitize calls Convert.ChangeType(List<MyId>, typeof(MyId[])), which fails because List<MyId> doesn't implement IConvertible.

    Without a value converter, the code at line 228 proceeds to materialize the enumerable into a List<TElement>, which Npgsql ADO.NET handles natively. With a converter, this materialization is intentionally skipped (to preserve types like HashSet<T> that converters expect), but this causes the mismatch when the parameter's collection type differs from the property's.

    Reproduction

    • Confirmed with Npgsql.EntityFrameworkCore.PostgreSQL 9.0.4 on .NET 9
    • Also reproduced on 8.0. not a regression between 8.x and 9.x (the bug has existed since element-level value converters on primitive collections were introduced)11
    • Does not reproduce on SQL this is PostgreSQL-specific (SQL Server uses JSON-based primitive collections and doesn't have the same parameterization path)Server
    • Does not reproduce without value List<long> against long[] works fineconverter

    Workaround: use an array parameter (MyId[]) instead of List<MyId>, as the reporter noted.

    Minimal repro
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
    using Microsoft.Extensions.Logging;
    
    await using var context = new TestContext();
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();
    
    var toFindAsList = new List<MyId> { new MyId(42) };
    
    _ = await context.Users
        .Where(user => user.RelatedIds.Intersect(toFindAsList).Any())
        .ToListAsync();
    
    public class TestContext : DbContext
    {
        public DbSet<ReproUser> Users => Set<ReproUser>();
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder
                .UseNpgsql(Environment.GetEnvironmentVariable("Test__Npgsql__DefaultConnection"))
                .LogTo(Console.WriteLine, LogLevel.Information)
                .EnableSensitiveDataLogging();
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ReproUser>().PrimitiveCollection(u => u.RelatedIds).ElementType().HasConversion<MyIdValueConverter>();
        }
    }
    
    public class ReproUser
    {
        public int Id { get; set; }
        public MyId[] RelatedIds { get; set; } = [];
    }
    
    public readonly record struct MyId(long Value);
    
    public class MyIdValueConverter : ValueConverter<MyId, long>
    {
        public MyIdValueConverter() : base(v => v.Value, v => new MyId(v)) { }
    }

    Possible Duplicates

  4. DPDmancul commented on Apr 15, 2026

    @DPDmancul

    There is still a regression from 8.x to 9.x: with 8.x this doesn't fail if both the primitive collection and the list passed to Intersect implement IList<>.

    For example this worked with 8.x, but not with 9.x:
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
    using Microsoft.Extensions.Logging;
    using System.Collections.Immutable;
    
    await using var context = new ReproContext();
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();
    
    var toFindAsImmutableList = ImmutableList<MyId>.Empty.Add(new MyId(42));
    
    _ = await context.Users
        .Where(user => user.RelatedIds.Intersect(toFindAsImmutableList).Any())
        .ToListAsync();
    
    public class ReproContext : DbContext {
        public DbSet<ReproUser> Users => Set<ReproUser>();
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
            optionsBuilder
                .UseNpgsql("Host=localhost;Username=postgres;Password=passw0rd;database=repro")
                .LogTo(Console.WriteLine, LogLevel.Information)
                .EnableSensitiveDataLogging();
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<ReproUser>().PrimitiveCollection(u => u.RelatedIds).ElementType().HasConversion<MyIdValueConverter>();
        }
    }
    
    public class ReproUser
    {
        public ulong Id { get; set; }
        public List<MyId> RelatedIds { get; } = [];
    }
    
    public readonly record struct MyId(long Value);
    
    public class MyIdValueConverter : ValueConverter<MyId, long>
    {
        public MyIdValueConverter() : base(v => v.Value, v => new MyId(v)){}
    }
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions