Search code examples
c#.netentity-framework-coreef-core-8.0

Mapping ICollection<string> to JSON column in EF Core 8


I'm using EF Core 8, with the Postgres adapter.

I need this entity property as JSON:

public ICollection<string> Messages { get; } = [];

I tried this configuration:

builder.OwnsOne(x => x.Messages, x => x.ToJson());

That didn't work as it saved "{ Capacity: 4 }" in the database.

I tried this configuration:

builder.Property(x => x.Messages).HasColumnType("jsonb");

That throws a NotSupportedException.

I tried various other combinations and workarounds.

For years now, although JSON mapping has been improved, there are always bugs or shortcomings.

I know that .NET 9 was released yesterday, possibly with some improvements for collections of primitives and strings, but we can't upgrade now.

What works in .NET 8?

(Docs are here and here.)


Solution

  • I found the answer in the docs for value converters. It serialises to json despite not using HasColumnType("jsonb") or ToJson().

    modelBuilder
      .Entity<Customer>()
      .Property(x => x.Messages)
      //.HasColumnType("jsonb")      // unnecessary
      .HasConversion(
        c => JsonSerializer.Serialize(c, JsonSerializerOptions.Default),
        s => JsonSerializer.Deserialize<ICollection<string>>(s, JsonSerializerOptions.Default)!,
        new ValueComparer<ICollection<string>>(
          (c1, c2) => c1!.SequenceEqual(c2!),
          c => c.Aggregate(0, (acc, val) => HashCode.Combine(acc, val.GetHashCode(StringComparison.Ordinal))),
          c => new Collection<string>(c.ToList()))
      );
    

    Example:

    customer.Messages.Add("foo");
    customer.Messages.Add("bar");
    customer.Messages.Add("baz");
    

    The db record will have:

    ["foo","bar","baz"]