Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store JSON in an entity field with EF Core?

I am creating a reusable library using .NET Core (targeting .NETStandard 1.4) and I am using Entity Framework Core (and new to both). I have an entity class that looks like:

public class Campaign {     [Key]     public Guid Id { get; set; }      [Required]     [MaxLength(50)]     public string Name { get; set; }      public JObject ExtendedData { get; set; } } 

and I have a DbContext class that defines the DbSet:

public DbSet<Campaign> Campaigns { get; set; } 

(I am also using the Repository pattern with DI, but I don't think that is relevant.)

My unit tests give me this error:

System.InvalidOperationException: Unable to determine the relationship represented by navigation property 'JToken.Parent' of type 'JContainer'. Either manually configure the relationship, or ignore this property from the model..

Is there a way to indicate that this is not a relationship but should be stored as a big string?

like image 891
Alex Avatar asked Jun 29 '17 15:06

Alex


People also ask

Does EF core support JSON?

With string mapping, the EF Core provider will save and load properties to database JSON columns, but will not do any further serialization or parsing - it's the developer's responsibility to handle the JSON contents, possibly using System. Text.

Can we use EF6 in .NET core?

You can't put an EF6 context in an ASP.NET Core project because . NET Core projects don't support all of the functionality that EF6 commands such as Enable-Migrations require.

Is EF core faster than EF6?

EF Core 6.0 itself is 31% faster executing queries. Heap allocations have been reduced by 43%.


Video Answer


2 Answers

Going to answer this one differently.

Ideally the domain model should have no idea how data is stored. Adding backing fields and extra [NotMapped] properties is actually coupling your domain model to your infrastructure.

Remember - your domain is king, and not the database. The database is just being used to store parts of your domain.

Instead you can use EF Core's HasConversion() method on the EntityTypeBuilder object to convert between your type and JSON.

Given these 2 domain models:

public class Person {     public int Id { get; set; }      [Required]     [MaxLength(50)]     public string FirstName { get; set; }      [Required]     [MaxLength(50)]     public string LastName { get; set; }      [Required]     public DateTime DateOfBirth { get; set; }      public IList<Address> Addresses { get; set; }       }  public class Address {     public string Type { get; set; }     public string Company { get; set; }     public string Number { get; set; }     public string Street { get; set; }     public string City { get; set; } } 

I have only added attributes that the domain is interested in - and not details that the DB would be interested in; I.E there is no [Key].

My DbContext has the following IEntityTypeConfiguration for the Person:

public class PersonsConfiguration : IEntityTypeConfiguration<Person> {     public void Configure(EntityTypeBuilder<Person> builder)     {         // This Converter will perform the conversion to and from Json to the desired type         builder.Property(e => e.Addresses).HasConversion(             v => JsonConvert.SerializeObject(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }),             v => JsonConvert.DeserializeObject<IList<Address>>(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));     } } 

With this method you can completely decouple your domain from your infrastructure. No need for all the backing field & extra properties.

like image 191
Darren Wainwright Avatar answered Sep 19 '22 04:09

Darren Wainwright


The key to making the the Change Tracker function correctly is to implement a ValueComparer as well as a ValueConverter. Below is an extension to implement such:

public static class ValueConversionExtensions {     public static PropertyBuilder<T> HasJsonConversion<T>(this PropertyBuilder<T> propertyBuilder) where T : class, new()     {         ValueConverter<T, string> converter = new ValueConverter<T, string>         (             v => JsonConvert.SerializeObject(v),             v => JsonConvert.DeserializeObject<T>(v) ?? new T()         );          ValueComparer<T> comparer = new ValueComparer<T>         (             (l, r) => JsonConvert.SerializeObject(l) == JsonConvert.SerializeObject(r),             v => v == null ? 0 : JsonConvert.SerializeObject(v).GetHashCode(),             v => JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(v))         );          propertyBuilder.HasConversion(converter);         propertyBuilder.Metadata.SetValueConverter(converter);         propertyBuilder.Metadata.SetValueComparer(comparer);         propertyBuilder.HasColumnType("jsonb");          return propertyBuilder;     } } 

Example of how this works.

public class Person {     public int Id { get; set; }      [Required]     [MaxLength(50)]     public string FirstName { get; set; }      [Required]     [MaxLength(50)]     public string LastName { get; set; }      [Required]     public DateTime DateOfBirth { get; set; }      public List<Address> Addresses { get; set; }       }  public class Address {     public string Type { get; set; }     public string Company { get; set; }     public string Number { get; set; }     public string Street { get; set; }     public string City { get; set; } }  public class PersonsConfiguration : IEntityTypeConfiguration<Person> {     public void Configure(EntityTypeBuilder<Person> builder)     {         // This Converter will perform the conversion to and from Json to the desired type         builder.Property(e => e.Addresses).HasJsonConversion<IList<Address>>();     } } 

This will make the ChangeTracker function correctly.

like image 27
Robert Raboud Avatar answered Sep 21 '22 04:09

Robert Raboud