Skip to content

Json field with value converter when converter converts nulls isn't converted #37983

@JoasE

Description

@JoasE

Bug description

When you have a complex or owned type stored as json, with a scalar property that has a converter that can convert null values, the converter isn't ran when the value of the scalar is null. This is both during serialization aswell as deserialization
For serialization, this causes null to be stored in the field
For deserialization, this causes null values to be retrieved
Both could cause unexpected errors.

Your code

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Diagnostics;

var options = new DbContextOptionsBuilder<AAMyDbContext>()
    .EnableSensitiveDataLogging()
    .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=AAMyDbContext;Trusted_Connection=True;")
    .LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information)
    .Options;

using (var context = new AAMyDbContext(options))
{
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();
    var entity = new EntityType
    {
        Id = Guid.NewGuid(),
        ComplexType = new ComplexType 
        { 
            Int = null,  // (Fail) Should be stored as "<null>", but is stored as null. Converter hasn't ran
            String = "<null>" // Correctly stored as null
        } 
    };
    context.Add(entity);
    await context.SaveChangesAsync();
    context.ChangeTracker.Clear();

    var retrievedEntity = await context.EntityTypes.FirstAsync();
    Debug.Assert(retrievedEntity.ComplexType.String == "<null>"); // Fails, retrievedEntity.ComplexType.String is null, converter hasn't ran
}

internal class AAMyDbContext(DbContextOptions<AAMyDbContext> options) : DbContext(options)
{
    public DbSet<EntityType> EntityTypes => Set<EntityType>();


    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        var type = modelBuilder.Entity<EntityType>();
        modelBuilder.Entity<EntityType>().ComplexProperty(x => x.ComplexType, cfg =>
        {
            cfg.ToJson();
            cfg.Property(x => x.Int).HasConversion(new NullIntToNonNullStringConverter()).IsRequired(false);
            cfg.Property(x => x.String).HasConversion(new NonNullStringToNullStringConverter())
            .IsRequired(false);
        });
    }
}

public class NullIntToNonNullStringConverter() : ValueConverter<int?, string>(
        v => v == null ? "<null>" : v.ToString()!, v =>
        v == "<null>" ? null : int.Parse(v))
{
    public override bool ConvertsNulls => true;
}

public class NonNullStringToNullStringConverter() : ValueConverter<string, string?>(
        v => v == "<null>" ? null : v, v => v ?? "<null>")
{
    public override bool ConvertsNulls => true;
}

public class EntityType
{
    public Guid Id { get; set;  }

    public ComplexType ComplexType { get; set; } = null!;
}

public class ComplexType
{
    public int? Int { get; set; } = null!;

    public string String { get; set; } = "<null>";
}

Stack traces


Verbose output


EF Core version

10.0.5

Database provider

Microsoft.EntityFrameworkCore.SqlServer

Target framework

.NET 10

Operating system

W11

IDE

VS2026

Metadata

Metadata

Assignees

Type

No fields configured for Bug.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions