Skip to content

Complex property stored as json will be marked non-nullable even in TPH class hierarchy #37404

@tpoiesz

Description

@tpoiesz

Bug description

When two subclasses are stored in the same table using TPH, all columns mapped from properties that are exclusive to one of the subclasses will be marked as nullable, becuase if the row is an instance of the 'other' subclass that column will not have a value. Except for complex properties stored as json. This leads to exceptions when storing data because the database expect a value where there is none. This issue does not exist when using owned entities instead of complex entities.

Workaround is to explicitly specifiy IsRequired(false) but that doesn't seem the intended solution.

Your code

using System.Diagnostics;
using Microsoft.EntityFrameworkCore;

const string connectionString = "Server=localhost;Database=complex-issues;Port=5432;User Id=postgres;Password=postgres;Include Error Detail=true";
var options = new DbContextOptionsBuilder<AppDbContext>()
  .UseNpgsql(connectionString)
  .Options;
var dbContext = new AppDbContext(options);
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();

var entity = new EntityWithoutComplexJson();
dbContext.Entities.Add(entity);
await dbContext.SaveChangesAsync();

public abstract class Entity
{
  public Guid Id { get; init; }
}

public class EntityWithoutComplexJson : Entity
{
  
}

public class EntityWithComplexJson : Entity
{
  public required ComplexEntity ComplexEntity { get; init; }
}

public class ComplexEntity
{
  public required string Value { get; init; }
}

public class AppDbContext(DbContextOptions options) : DbContext(options)
{
  public DbSet<Entity> Entities { get; init; }
  
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<EntityWithoutComplexJson>();
    modelBuilder.Entity<EntityWithComplexJson>().ComplexProperty(entity => entity.ComplexEntity).ToJson();
  }

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    base.OnConfiguring(optionsBuilder);
    optionsBuilder.LogTo(message =>
    {
      Debug.WriteLine(message);
    });
  }
}

Stack traces

---> Npgsql.PostgresException (0x80004005): 23502: null value in column "ComplexEntity" of relation "Entities" violates not-null constraint

DETAIL: Failing row contains (019b373e-f53a-7786-ac07-94575699cf3c, EntityWithoutComplexJson, null).
   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.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
  Exception data:
    Severity: ERROR
    SqlState: 23502
    MessageText: null value in column "ComplexEntity" of relation "Entities" violates not-null constraint
    Detail: Failing row contains (019b373e-f53a-7786-ac07-94575699cf3c, EntityWithoutComplexJson, null).
    SchemaName: public
    TableName: Entities
    ColumnName: ComplexEntity
    File: execMain.c
    Line: 1947
    Routine: ExecConstraints
   --- End of inner exception stack trace ---

Verbose output


EF Core version

10.0.1

Database provider

Npgsql.EntityFrameworkCore.PostgreSQL

Target framework

.NET 10.0

Operating system

MacOS

IDE

Rider

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions