10

I switched form .NET 6 to .NET 7 to make use of the polymorphic serialization with System.Text.Json.

In WebApi, serializing and deserializing works like a charm using these types:

[JsonDerivedType(typeof(FileMessageData), typeDiscriminator: nameof(FileMessageData))]
public class FileMessageData : MessageData
{
    public string FileName { get; set; }
}

[JsonPolymorphic]
[JsonDerivedType(typeof(FileMessageData), typeDiscriminator: nameof(FileMessageData))]
public class MessageData
{
    public string InCommon { get; set; }
}

But when it comes to EF Core 7, I wanted to use the Json Column feature. In an entity called MessageEntity I added a property named Data with the base type MessageData and this configuration:

builder.OwnsOne(e => e.Data, ownedNavigationBuilder =>
{
    ownedNavigationBuilder.ToJson();
});

Now my expectation is, when I assign a FileMessageData object to the Data property that the saved value will be

{
   "$type": "FileMessageData",
   "inCommon": "test",
   "fileName": "hellokitty.png"
}

But unfortunately it is always an empty object:

{}

When I change the type of the Data property on the entity to FileMessageData, the value in the database is:

{
   "inCommon": "test",
   "fileName": "hellokitty.png"
}

No type discriminator

As a workaround, I'm doing this:

var options = new JsonSerializerOptions(JsonSerializerDefaults.General);
builder
    .Property(e => e.Data)
    .HasColumnType("nvarchar")
    .HasMaxLength(4000)
    .HasConversion(
        v => JsonSerializer.Serialize(v, options),
        s => JsonSerializer.Deserialize<MessageData>(s, options)!,
        ValueComparer.CreateDefault(typeof(MessageData), true)
    );

...but this doesn't allow me to query on the "InCommon" field with server evaluation because this can't obviously be translated to a query.

System.InvalidOperationException: 'The LINQ expression 'DbSet<MessageEntity>()
    .Where(m => m.Data.InCommon == "HoHo")' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'

How can I tell EF Core 7 to serialize/deserialize the object using the polymorphic JSON feature?

martinoss
  • 5,268
  • 2
  • 45
  • 53

1 Answers1

0

Using JsonProperty.EFCore can solve your problem. It allows you to use polymorphic json field, editable as IDictionary<string,object> or IList<object> as an entity model property. You can install it as a nuget package.

Usage example:

using JsonProperty.EFCore;

//Create product
var product = new Product() { Name = "Car" };

//AddRange
product.Attributes.AddRange(new Dictionary<string, object>() {
    //You can add values ​​of different types if the base type is object 
    {"MaxSpeed",300},{ "Engine capacity",3.5}, { "ElectroCar",false }
});

//Add
product.Attributes.Add("Color", "Red");

//Edit
product.Attributes.Edit(attrs=>{
    attrs["Color"] = "Blue";
    return attrs;
});

//Entity model
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    //Json container property. Same type as JsonDictionary<string,object>
    public JsonDictionary Attributes{ get; set; } = new();
}