Open roji opened 2 years ago
Copied from my original issue
Support in EF7 and upcoming work in EF8 for JSON is coming along nicely, and I was wondering if there might be room to continue expanding on it.
This specific idea is to support of interfaces or otherwise abstract types in JSON columns, through the use of a type hint that gets embedded with the data.
I've accomplished this in the past by writing my own .HasConversion
, accompanied by some custom System.Text.Json
[de]serialization voodoo. This ended up being quite useful for working with the data after it was retrieved, but obviously didn't fully integrate with EF in terms of being able to filter on properties defined by the contracts.
An example of how this could be useful is for storing arrays/collections as part of a known JSON type:
{
"configurations": [
{
"_discriminator": "configuration-type-1",
"specificToAll": "All types would have this.",
"specificToOne": "Only type one would have this."
},
{
"_discriminator": "configuration-type-2",
"specificToAll": "All types would have this.",
"specificToTwo": "Only type two would have this."
}
],
"singleConfiguration": {
"_discriminator": "configuration-type-3",
"specificToAll": "All types would have this.",
"specificToThree": "Only type three would have this."
}
}
This could be deserialized into something like:
public class ApplicationSettings
{
public IList<Configuration> Configurations { get; set; } = new();
public Configuration SingleConfiguration { get; set; }
}
public interface Configuration
{
public string SpecificToAll { get; }
}
public class ConfigurationTypeOne : Configuration
{
public string SpecificToAll { get; set; }
public string SpecificToOne { get; set; }
}
public class ConfigurationTypeTwo : Configuration
{
public string SpecificToAll { get; set; }
public string SpecificToTwo { get; set; }
}
public class ConfigurationTypeThree : Configuration
{
public string SpecificToAll { get; set; }
public string SpecificToThree { get; set; }
}
New EF-based JSON filtering would only allow filtering on the fields it has reason to expect, so in this case, SpecificToAll
. But that alone would be quite powerful as this technique allows for a lot of dynamism in the schema without requiring migrations.
This would be extremely useful.
Currently anything other than simple "complex" structures aren't supported by EF Owned Types (either multi-column-mapped or JSON-column-mapped), limiting their use and having to fall back to string columns and managing our own JSON.
Note for team: should we have a separate issue for this in Cosmos?
@ajcvickers I think so... Though we need to more clearly agree on what this means - at this point I think it refers to TPH-style inheritance for complex types.
I've done this myself using the technique mentioned above of having a custom converter. One major issue I ran into is that System.Text.Json requires that the first property be the $type
property (docs), however postgres (and I would assume others) didn't round trip the sql where the $type
property was first, they don't guarantee the order of fields at all usually since it shouldn't matter. I hacked around this, but it still caused problems and would need to be solved for this to work. Not sure why System.Text.Json has that requirement but removing that requirement would probably make this simpler to implement.
One major issue I ran into is that System.Text.Json requires that the first property be the $type property [...]
This limitation has already been removed for .NET 9.0 (issue).
Note #28592, which is also about mapping multiple types to the same JSON document (or sub-document), but where the types aren't in a hierarchy - also via the use of a discriminator ($type
). We may want to implement these two features together.
I need this, although with my model the discriminator not only needs to have a custom name (not $type
) but is also a regular property of my type hierarchy used in our code base.
@atrauzzi @hahn-kev do you have any example conversion code that you could share?
Yeah this is what I did. Super simple https://github.com/sillsdev/harmony/blob/main/src%2FSIL.Harmony%2FDb%2FEntityConfig%2FSnapshotEntityConfig.cs#L20-L25
@chrisc-ona -- https://gist.github.com/atrauzzi/dde4f5e92fb783cb6847ff2b1c2d6710
Hopefully this is of some use. I'll repeat the usual disclaimer when I share it: It works, it seems to work well, but I'm sure there are optimizations or deeper integrations with EF that can be done to make it much better overall.
@hahn-kev @atrauzzi thanks. Unfortunately I'm not sure how well either of these approaches would work for my particular use case though since where polymorphism applies in our model is not at the column/top level document level, but a few levels deep. It looks like at least for now I'll have to continue treating the column as a string and explicitly convert to/from JSON.
System.Text.Json has polymorphic deserialization in 7.0. We should implement a similar scheme here with
$type
, so that JSON documents we produce are compatible with that.Originally requested in https://github.com/npgsql/efcore.pg/issues/2321