Custom type with automatic serialization/deserialization in EF Core

Storing a complex entity as JSON in a single database column turns out to be pretty easy with the Value Conversions which were added in EF Core 2.1.

[NotMapped] not needed

public class AvailabilityRule: ApplicationEntity
{
   ...
    // [NotMapped]
    public CalendarEvent Event { get; set; }
}

Add the following extension for PropertyBuilder:

public static class PropertyBuilderExtensions
{
    public static PropertyBuilder<T> HasJsonConversion<T>(this PropertyBuilder<T> propertyBuilder) where T : class, new()
    {
        ValueConverter<T, string> converter = new ValueConverter<T, string>
        (
            v => JsonSerializer.Serialize(v, null),
            v => JsonSerializer.Deserialize<T>(v, null) ?? new T()
        );

        ValueComparer<T> comparer = new ValueComparer<T>
        (
            (l, r) => JsonSerializer.Serialize(l, null) == JsonSerializer.Serialize(r, null),
            v => v == null ? 0 : JsonSerializer.Serialize(v, null).GetHashCode(),
            v => JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(v, null), null)
        );

        propertyBuilder.HasConversion(converter);
        propertyBuilder.Metadata.SetValueConverter(converter);
        propertyBuilder.Metadata.SetValueComparer(comparer);
        propertyBuilder.HasColumnType("jsonb");

        return propertyBuilder;
    }
}

In the OnModelCreating method of the database context need call HasJsonConversion, which does the serialization, deserialization and track changes to your object:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<AvailabilityRule>()
        .Property(b => b.Event )
        .HasJsonConversion();
}