Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/elixir/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何使用EF Core将JSON存储在实体字段中?_C#_Json.net_Entity Framework Core - Fatal编程技术网

C# 如何使用EF Core将JSON存储在实体字段中?

C# 如何使用EF Core将JSON存储在实体字段中?,c#,json.net,entity-framework-core,C#,Json.net,Entity Framework Core,我正在使用.NETCore(目标是.netstandard1.4)和EntityFrameworkCore(两者都是新的)创建一个可重用的库。我有一个实体类,看起来像: public class Campaign { [Key] public Guid Id { get; set; } [Required] [MaxLength(50)] public string Name { get; set; } public JObject Exten

我正在使用.NETCore(目标是.netstandard1.4)和EntityFrameworkCore(两者都是新的)创建一个可重用的库。我有一个实体类,看起来像:

public class Campaign
{
    [Key]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }

    public JObject ExtendedData { get; set; }
}
我有一个DbContext类,它定义了DbSet:

public DbSet<Campaign> Campaigns { get; set; }
公共数据库集活动{get;set;}
(我也将存储库模式用于DI,但我认为这与此无关。)

我的单元测试给出了以下错误:

System.InvalidOperationException:无法确定关系 由类型为的导航属性“JToken.Parent”表示 “JContainer”。手动配置关系或忽略 此属性来自模型


有没有办法表明这不是一个关系,而是应该存储为一个大字符串?

您能试试这样的方法吗

    [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);
        }
    }
[未映射]
私有作业对象外部数据;
[未映射]
公共工作项目外部数据
{
获取{return extraData;}
设置{extraData=value;}
}
[列(“额外数据”)]
公共字符串extradastr
{
得到
{
返回此.extraData.ToString();
}
设置
{
this.extraData=JsonConvert.DeserializeObject(值);
}
}
以下是迁移输出:

ExtraData = table.Column<string>(nullable: true),
ExtraData=table.Column(可空:true),

@Michael的回答让我走上了正轨,但我的实施方式有点不同。最后,我将该值作为字符串存储在私有属性中,并将其用作“备份字段”。然后,ExtendedData属性在set上将JObject转换为字符串,在get上将JObject转换为字符串:

public class Campaign
{
    // https://docs.microsoft.com/en-us/ef/core/modeling/backing-field
    private string _extendedData;

    [Key]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }

    [NotMapped]
    public JObject ExtendedData
    {
        get
        {
            return JsonConvert.DeserializeObject<JObject>(string.IsNullOrEmpty(_extendedData) ? "{}" : _extendedData);
        }
        set
        {
            _extendedData = value.ToString();
        }
    }
}

更新:Darren关于使用EF Core值转换的回答(EF Core 2.1的新版本-在回答此问题时不存在)似乎是目前最好的方法。

将以不同的方式回答此问题

理想情况下,域模型应该不知道数据是如何存储的。添加备份字段和额外的
[NotMapped]
属性实际上是将域模型耦合到基础结构

记住——你的领域才是王者,而不是数据库。数据库只是用来存储您的域的一部分

相反,您可以在
EntityTypeBuilder
对象上使用EF Core的
HasConversion()
方法在您的类型和JSON之间进行转换

考虑到这两种域模型:

公共类人物
{
公共int Id{get;set;}
[必需]
[MaxLength(50)]
公共字符串名{get;set;}
[必需]
[MaxLength(50)]
公共字符串LastName{get;set;}
[必需]
公共日期时间出生日期{get;set;}
公共IList地址{get;set;}
}
公共课堂演讲
{
公共字符串类型{get;set;}
公共字符串公司{get;set;}
公共字符串编号{get;set;}
公共字符串Street{get;set;}
公共字符串City{get;set;}
}
我只添加了域感兴趣的属性,而没有DB感兴趣的细节;即没有
[键]

My DbContext为
人员
提供了以下
IEntityTypeConfiguration

公共类PersonConfiguration:IEntityTypeConfiguration
{
公共void配置(EntityTypeBuilder)
{
//此转换器将执行Json与所需类型之间的转换
属性(e=>e.Addresses).HasConversion(
v=>JsonConvert.SerializeObject(v,新的JsonSerializerSettings{NullValueHandling=NullValueHandling.Ignore}),
v=>JsonConvert.DeserializeObject(v,新的JsonSerializerSettings{NullValueHandling=NullValueHandling.Ignore});
}
}

使用此方法,您可以将域与基础架构完全解耦。不需要所有支持字段和额外属性。

对于使用EF 2.1的用户,有一个很好的小NuGet包,使其非常简单

using Innofactor.EfCoreJsonValueConverter;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

public class Campaign
{
    [Key]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }

    public JObject ExtendedData { get; set; }
}

public class CampaignConfiguration : IEntityTypeConfiguration<Campaign> 
{
    public void Configure(EntityTypeBuilder<Campaign> builder) 
    {
        builder
            .Property(application => application.ExtendedData)
            .HasJsonValueConversion();
    }
}
使用Innofactor.EfCoreJsonValueConverter;
使用Microsoft.EntityFrameworkCore;
使用Microsoft.EntityFrameworkCore.Metadata.Builders;
公课运动
{
[关键]
公共Guid Id{get;set;}
[必需]
[MaxLength(50)]
公共字符串名称{get;set;}
public JObject ExtendedData{get;set;}
}
公共类活动配置:IEntityTypeConfiguration
{
公共void配置(EntityTypeBuilder)
{
建设者
.Property(application=>application.ExtendedData)
.HasJsonValueConversion();
}
}


注意此方法:EF Core仅当字段指定给时才将实体标记为已修改。因此,如果使用person.Addresses.Add,实体将不会标记为已更新;您需要调用属性设置程序person.Addresses=UpdateAddresses

这让我采取了一种不同的方法,所以这个事实是显而易见的:使用Getter和Setter方法,而不是属性

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;
}
但更清楚的是,这并不像你想象的那样™.


如果您首先使用数据库并使用某种类型的类自动生成器来生成EF,那么这些类通常会声明为
partial
,因此,您可以将这些内容添加到一个单独的文件中,下次从数据库更新类时不会被吹走。

正确实现更改跟踪功能的关键是实现ValueComparer和ValueConverter。以下是实现该功能的扩展:

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;
    }
}
public静态类ValueConversionExtensions
{
公共静态PropertyBuilder具有JSONversion(此PropertyBuilder PropertyBuilder),其中T:class,new()
{
ValueConverter converter=新的ValueConverter
(
v=>JsonConvert.SerializeObject(v),
v=>JsonConvert.DeserializeObject(v)??新的T()
);
ValueComparer comparer=新的ValueComparer
(
(l,r)=>JsonConve
campaign.GetExtendedData().Add(something);
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;
    }
}
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>>();
    }
}
public class FacilityModel 
{
    public string Name { get; set; } 
    public JObject Values { get; set; } 
}
[Table("facility", Schema = "public")]
public class Facility 
{
     public string Name { get; set; } 
     public Dictionary<string, string> Values { get; set; } = new Dictionary<string, string>();
}
this.CreateMap<Facility, FacilityModel>().ReverseMap();
base.OnModelCreating(builder); 
        builder.Entity<Facility>()
        .Property(b => b.Values)
        .HasColumnType("jsonb")
        .HasConversion(
        v => JsonConvert.SerializeObject(v),
        v => JsonConvert.DeserializeObject<Dictionary<string, string>>(v));
    public static PropertyBuilder<T> HasJsonConversion<T>(this PropertyBuilder<T> propertyBuilder) where T : class, new()
    {
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = true,
            AllowTrailingCommas = true,
            PropertyNameCaseInsensitive = true
        };

        ValueConverter<T, string> converter = new ValueConverter<T, string>
        (
            v => JsonSerializer.Serialize(v, options),
            v => JsonSerializer.Deserialize<T>(v, options) ?? new T()
        );

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

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

        return propertyBuilder;
    }