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

前端 未结 10 435
死守一世寂寞
死守一世寂寞 2020-11-29 17:13

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

相关标签:
10条回答
  • 2020-11-29 18:16

    The comment by @Métoule:

    Be careful with this approach: EF Core marks an entity as modified only if the field is assigned to. So if you use person.Addresses.Add, the entity won't be flagged as updated; you'll need to call the property setter person.Addresses = updatedAddresses.

    made me take a different approach so that this fact is obvious: use Getter and Setter methods, rather than a property.

    public void SetExtendedData(JObject extendedData) {
        ExtendedData = JsonConvert.SerializeObject(extendedData);
        _deserializedExtendedData = extendedData;
    }
    
    //just to prevent deserializing more than once unnecessarily
    private JObject _deserializedExtendedData;
    
    public JObject GetExtendedData() {
        if (_extendedData != null) return _deserializedExtendedData;
        _deserializedExtendedData = string.IsNullOrEmpty(ExtendedData) ? null : JsonConvert.DeserializeObject<JObject>(ExtendedData);
        return _deserializedExtendedData;
    }
    

    You could theoretically do this:

    campaign.GetExtendedData().Add(something);
    

    But it's much more clear that That Doesn't Do What You Think It Does™.

    If you're using database-first and using some kind of class auto-generator for EF, then the classes will usually be declared as partial, so you can add this stuff in a separate file that won't get blown away the next time you update your classes from your database.

    0 讨论(0)
  • 2020-11-29 18:17

    Could you try something like this?

        [NotMapped]
        private JObject extraData;
    
        [NotMapped]
        public JObject ExtraData
        {
            get { return extraData; }
            set { extraData = value; }
        }
    
        [Column("ExtraData")]
        public string ExtraDataStr
        {
            get
            {
                return this.extraData.ToString();
            }
            set
            {
                this.extraData = JsonConvert.DeserializeObject<JObject>(value);
            }
        }
    

    here is the migration output:

    ExtraData = table.Column<string>(nullable: true),
    
    0 讨论(0)
  • 2020-11-29 18:18

    Here's something I used

    Model

    public class FacilityModel 
    {
        public string Name { get; set; } 
        public JObject Values { get; set; } 
    }
    

    Entity

    [Table("facility", Schema = "public")]
    public class Facility 
    {
         public string Name { get; set; } 
         public Dictionary<string, string> Values { get; set; } = new Dictionary<string, string>();
    }
    

    Mapping

    this.CreateMap<Facility, FacilityModel>().ReverseMap();
    

    DBContext

    base.OnModelCreating(builder); 
            builder.Entity<Facility>()
            .Property(b => b.Values)
            .HasColumnType("jsonb")
            .HasConversion(
            v => JsonConvert.SerializeObject(v),
            v => JsonConvert.DeserializeObject<Dictionary<string, string>>(v));
    
    0 讨论(0)
  • 2020-11-29 18:19

    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.

    0 讨论(0)
提交回复
热议问题