Skip to content

Properly support non-array/list collections/enumerables in value-converted arrays #3286

Description

@JustArchi

Minimum repro extracted from real application:

await using var reproContext = new ReproContext();

await reproContext.Database.EnsureDeletedAsync();
await reproContext.Database.EnsureCreatedAsync();

HashSet<EReproRarity> uniqueRarities = [EReproRarity.Common];

List<ReproUser> users = await reproContext.Users.AsNoTracking().Include(user => user.Assets.Where(asset => uniqueRarities.Contains(asset.Rarity))).Where(user => user.Assets.Any(asset => uniqueRarities.Contains(asset.Rarity))).ToListAsync().ConfigureAwait(false);

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

	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
		optionsBuilder
			.UseNpgsql("Host=/var/run/postgresql")
			.EnableSensitiveDataLogging();

	protected override void OnModelCreating(ModelBuilder modelBuilder) {
		ArgumentNullException.ThrowIfNull(modelBuilder);

		base.OnModelCreating(modelBuilder);

		modelBuilder.HasDefaultSchema("repro");
	}
}

[Table("users", Schema = "repro")]
public class ReproUser {
	[JsonInclude]
	public ICollection<ReproAsset> Assets { get; } = new HashSet<ReproAsset>();

	[Column]
	[Key]
	public ulong SteamID { get; set; }
}

[Table("assets", Schema = "repro")]
[PrimaryKey(nameof(SteamID), nameof(AssetID))]
public class ReproAsset {
	[Column]
	[Key]
	public ulong AssetID { get; set; }

	[Column]
	public EReproRarity Rarity { get; set; }

	[Column]
	[ForeignKey(nameof(SteamID))]
	public ReproUser User { get; set; } = null!;

	[Column]
	[Key]
	public ulong SteamID { get; set; }
}

public enum EReproRarity : byte {
	Unknown,
	Common,
	Uncommon,
	Rare
}

Worked fine in last 8.0.4 release, now throws:

2024-09-21 02:56:12|dotnet-155123|ERROR|Microsoft.EntityFrameworkCore.Query|An exception occurred while iterating over the results of a query for context type 'ArchiSteamFarmBackend.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__DisplayClass7_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() 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__DisplayClass7_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()

It seems the problem is strictly related with enum, as changing Rarity to int and uniqueRarities accordingly to HashSet<int> no longer crashes the application.

Removing : byte from the enum still crashes the app.

Thanks in advance for looking into this issue.

Activity

  1. roji commented on Sep 21, 2024

    @roji
    Member

    Thanks, confirmed regression from 8.0.0 to 9.0.0-rc.1.

    Simplified repro
    await using var context = new ReproContext();
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();
    
    HashSet<EReproRarity> uniqueRarities = [EReproRarity.Common];
    
    _ = await context.Users
        .Where(user => user.Assets.Any(asset => uniqueRarities.Contains(asset.Rarity)))
        .ToListAsync();
    
    public class ReproContext : DbContext {
        public DbSet<ReproUser> Users => Set<ReproUser>();
        public DbSet<ReproAsset> Assets => Set<ReproAsset>();
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
            optionsBuilder
                .UseNpgsql("Host=localhost;Username=test;Password=test")
                .LogTo(Console.WriteLine, LogLevel.Information)
                .EnableSensitiveDataLogging();
    }
    
    public class ReproUser
    {
        public ulong Id { get; set; }
        public ICollection<ReproAsset> Assets { get; } = new HashSet<ReproAsset>();
    }
    
    public class ReproAsset
    {
        public ulong Id { get; set; }
        public EReproRarity Rarity { get; set; }
        public ReproUser User { get; set; } = null!;
    }
    
    public enum EReproRarity : byte
    {
        Unknown,
        Common,
        Uncommon,
        Rare
    }
  2. added 3 commits that reference this issue on Sep 21, 2024
    e89b6ce
    23af21d
    1a22ed2
  3. changed the title [-]Query stopped working in 9.0.0-rc.1 - `Object must implement IConvertible` (enum in collection)[/-] [+]Properly support non-array/list collections/enumerables in value-converted arrays[/+] on Sep 21, 2024
  4. self-assigned this
    on Sep 21, 2024
  5. added this to the 9.0.0 milestone on Sep 21, 2024
  6. added a commit that references this issue on Sep 23, 2024
    8a0fca5
  7. added a commit that references this issue on Dec 18, 2024
    a5c7622
  8. added theissue type on Jun 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions