5

I am currently working on a database table, that shall contain some base properties of the data and one property that is a jsonb column in the database that can contain different objects that are derived from one base class.

I am using Entity Framework Core 9.0.2 and Npgsql 9.0.3.

Here is an example for the entity:

public class DatabaseEntity
{
        public int Id { get; set; }

        // This is the jsonb column
        public BaseClass DerivedClassProperty { get; set; }
}

And this is an example for the class hierarchy that shall be stored in the jsonb column of the entity:

[JsonPolymorphic]
[JsonDerivedType(typeof(A))]
[JsonDerivedType(typeof(B))]
public class BaseClass
{
        public int BaseProperty { get; set; }
}

public class A : BaseClass
{
        public int AdditionalProperty { get; set; }
}

public class B : BaseClass
{
        public string AnotherProperty { get; set; }
}

In EF I added the entity by defining it this way:

modelBuilder.Entity<DatabaseEntity>().ToTable("DatabaseEntity")
        .OwnsOne(b => b.Properties, onb => onb.ToJson());

The migrations (we are using code first) have been generated, the database table is exactly what I expect, however, if I try to save the entity with e.g. class "A" in DerivedClassProperty, it only persists the properties of the BaseClass and not the ones of the derived class.

As far as I understand, this is the correct way on how to handle POCOs for jsonb tables in the latest version of Npsql and EF Core 9 (Npsql documentation).

I already tried to use the legacy way, which is described in the link to the Npgsql documentation above, but this also did not work.

Has anybody experienced something like this and knows how to set it up, so that the derived classes get persisted correctly?

1 Answer 1

0

If you add the type on the derived class then it should work.

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$t")]
[JsonDerivedType(typeof(A), nameof(A))]
public class A : BaseClass

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$t")] may not be necessary. I know in efcore version 8 Microsoft didn't respect the JSON standard and expected the $type attribute to be first (which Postgres doesn't guarantee). I don't know if they've fixed that in the later versions.

The type discriminator in the nameof() argument is needed.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your answer. In EF Core 9 they changed the behavior of to use "$type" as a type discriminator, so it is not necessary to use it anymore (at least according to their documentation). The attributes for the polymorphism were set according to the System.Text.Json documentation of the .NET framework, so they only need to be set on the base class. However, I tried to do this in my scenario and got the same result unfortunately.
Sorry, did you try to add the the annotations to the derived type as well as the base type? I know the documentation states that It's only necessary on the base class but I ran into the same issue as you when I did it a few months ago. Adding the properties above fixed it. Bear in mind that Microsoft docs use System.Text whereas npgsql use Newtonsoft so the docs won't work exactly the same as your scenario
The using of "$type" as a discriminator wasn't the issue that I was referring to. When you read from the database, the json polymorphic expected the "$type" property to be the first key it finds in the object. If it wasn't, then it would crash. E.G. stackoverflow.com/questions/77669675/…
Yes, I tried it both ways. I also know that the $type property was needed at the beginning of the JSON, however, in .NET 9 it is possible to use out of order metadata reads (devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-9/…), which is also supported by Npgsql (npgsql.org/doc/types/…), but this does not work. The problem already exists when writing the data to the database and not by reading it back.
I've made an example with the approach I mentioned: github.com/scott-david-walker/polymorphic-test In the example, it works as I'd expect it to. The code to save is in the TestController. Necessary entities are in Core/Entities/Poly.cs. Sorry about the unnecessary other classes, I used a template to make the setup quicker.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.