Skip to content

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

@JustArchi

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions