Skip to content

Querying enum-member of property with JsonDocument conversion infers wrong type when using enum-string representation. #3851

@Ta1sty

Description

@Ta1sty

Npgsql.EntityFrameworkCore.PostgreSQL 10.0.2
DotNet 10, C# 14

Hello,

I was experimenting a bit with polymorphism. Having read the open issue about complex-type polymorphism led me to map my 'Complex' property as a SimpleProperty with a conversion to JsonDocument. This works fine so far, write and read to store works.
Since there are cases where I also want to filter by the descriminator I tried to filter my results server side.
The discriminator is an Enum (with string representation!).
However when I query it with

var newComplexA2 = context.TableA.Single(x => x.ComplexType.Type == ComplexType.ComplexTypeA);

Translates to:

SELECT t."Id", t."ComplexType", t."ComplexTypeEnum"
FROM "TableA" AS t
WHERE CAST(t."ComplexType" ->> 'Type' AS integer) = 0
LIMIT 2

I get why EF-Core might think that it needs to convert the constant to an int and cast the left side to an integer. But no matter what i try, i can't seem to find a way to make it work. Except the HasComputedColumn workaround.

Is there any way I can tell EF that the enum in this JsonDocument is string?

Best Regards

--- Code

using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

var entityA = new EntityA
{
    ChildClass = new ChildClassA
    {
        Title = "Title"
    }
};

var entityB = new EntityA
{
    ChildClass = new ChildClassB
    {
        Description = "Description"
    }
};

var context = new Context();

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

context.TableA.Add(entityA);

await context.SaveChangesAsync();

context.TableA.Add(entityB);

await context.SaveChangesAsync();

context.ChangeTracker.Clear();

// works - left side is a mapped property with varchar(32), the correct type for the right side is inferred
var newComplexA1 = context.TableA.Single(x => x.ChildType == ChildTypeEnum.ChildTypeA);

// doesn't work - left side is 'unknown' JSON.
// Since the CLR-Type is an enum, it falls back to integer (even though I configured Enums to have string representation)
// Setting a JSON converter also doesn't work or explicitly setting a type via [Column(TypeName = "varchar(32)"]
var newComplexA2 = context.TableA.Single(x => x.ChildClass.Type == ChildTypeEnum.ChildTypeA);

// also doesn't work
var newComplexA3 = context.TableA.Single(x => x.ChildClass.Type.ToString() == nameof(ChildTypeEnum.ChildTypeA));

// doesn't work either
var newComplexA4 = context.TableA.Single(x => ((string) (object) x.ChildClass.Type) == nameof(ChildTypeEnum.ChildTypeA));

var enumValue = context.TableA.Select(x => x.ChildClass.Type).First();

Console.WriteLine("Hello World!");

public class Context : DbContext
{
    public DbSet<EntityA> TableA => Set<EntityA>();
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connectionString = "Host=localhost;Port=13205;Database=complex_poly_demo;Username=postgres;Password=postgres";
        
        optionsBuilder.UseNpgsql(connectionString)
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableDetailedErrors()
            .EnableSensitiveDataLogging();
    }

    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder.Properties<Enum>().HaveConversion<string>().HaveMaxLength(32);
        configurationBuilder.Properties<Enum?>().HaveConversion<string>().HaveMaxLength(32);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<EntityA>(entity =>
        {
            entity.Property(x => x.ChildType).HasComputedColumnSql("\"ChildClass\"->>'Type'", stored: true);
            
            entity.Property<IChildClass>(x => x.ChildClass)
                .HasConversion(
                    x => JsonSerializer.SerializeToDocument(x),
                    x => x.Deserialize<IChildClass>()!
                );
        });
    }
}

public class EntityA
{
    public int Id { get; set; }
    
    public ChildTypeEnum ChildType { get=> ChildClass.Type; private set { } }
    public required IChildClass ChildClass { get; set; }
}

[JsonConverter(typeof(JsonStringEnumConverter<ChildTypeEnum>))]
public enum ChildTypeEnum
{
    ChildTypeA,
    ChildTypeB,
}

[JsonPolymorphic(TypeDiscriminatorPropertyName = nameof(Type))]
[JsonDerivedType(typeof(ChildClassA), nameof(ChildTypeEnum.ChildTypeA))]
[JsonDerivedType(typeof(ChildClassB), nameof(ChildTypeEnum.ChildTypeB))]
public interface IChildClass
{
    [Column(TypeName = "varchar(32)")]
    [JsonConverter(typeof(JsonStringEnumConverter<ChildTypeEnum>))]
    public ChildTypeEnum Type { get; }
}

public class ChildClassA : IChildClass
{
    [JsonIgnore]
    public ChildTypeEnum Type => ChildTypeEnum.ChildTypeA;

    public required string Title { get; set; }
}

public class ChildClassB : IChildClass
{
    [JsonIgnore]
    public ChildTypeEnum Type => ChildTypeEnum.ChildTypeB;

    public required string Description { get; set; }
}
fail: 28.05.2026 14:18:33.702 RelationalEventId.CommandError[20102] (Microsoft.EntityFrameworkCore.Database.Command) 
      Failed executing DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT t."Id", t."ChildClass", t."ChildType"
      FROM "TableA" AS t
      WHERE CAST(t."ChildClass" ->> 'Type' AS integer) = 0
      LIMIT 2
fail: 28.05.2026 14:18:33.708 CoreEventId.QueryIterationFailed[10100] (Microsoft.EntityFrameworkCore.Query) 
      An exception occurred while iterating over the results of a query for context type 'Context'.
      Npgsql.PostgresException (0x80004005): 22P02: invalid input syntax for type integer: "ChildTypeA"
         at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
         at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
         at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
         at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
         at Npgsql.NpgsqlDataReader.NextResult()
         at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
         at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
         at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior)
         at Npgsql.NpgsqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
         at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.InitializeReader(Enumerator enumerator)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.<>c.<MoveNext>b__21_0(DbContext _, Enumerator enumerator)
         at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
        Exception data:
          Severity: ERROR
          SqlState: 22P02
          MessageText: invalid input syntax for type integer: "ChildTypeA"
          File: numutils.c
          Line: 615
          Routine: pg_strtoint32_safe

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions