将反序列化的 JSON 对象保存到具有重复子实体的数据库

Posted

技术标签:

【中文标题】将反序列化的 JSON 对象保存到具有重复子实体的数据库【英文标题】:Saving deserialized JSON objects to database with duplicate child entities 【发布时间】:2020-09-28 09:26:11 【问题描述】:

我正在从 API 调用中检索一些 JSON 并将其反序列化为它的组件对象。一切正常,直到我开始保存到数据库。原因是,存在具有重复键的子对象(就数据而言这是绝对正确的)但是当我保存***对象时,它会在子对象上引发主键冲突错误。

这是我的 JSON 示例(我知道它不完整);


"count": 149,
"filters": ,
"competitions": [
    
        "id": 2006,
        "area": 
            "id": 2001,
            "name": "Africa",
            "countryCode": "AFR",
            "ensignUrl": null
        ,
        "name": "WC Qualification",
        "code": null,
        "emblemUrl": null,
        "plan": "TIER_FOUR",
        "currentSeason": 
            "id": 555,
            "startDate": "2019-09-04",
            "endDate": "2021-11-16",
            "currentMatchday": null,
            "winner": null
        ,
        "numberOfAvailableSeasons": 2,
        "lastUpdated": "2018-06-04T23:54:04Z"
    ,
    
        "id": 2025,
        "area": 
            "id": 2011,
            "name": "Argentina",
            "countryCode": "ARG",
            "ensignUrl": null
        ,
        "name": "Supercopa Argentina",
        "code": null,
        "emblemUrl": null,
        "plan": "TIER_FOUR",
        "currentSeason": 
            "id": 430,
            "startDate": "2019-04-04",
            "endDate": "2019-04-04",
            "currentMatchday": null,
            "winner": null
        ,
        "numberOfAvailableSeasons": 2,
        "lastUpdated": "2019-05-03T05:08:18Z"
    ,
    
        "id": 2023,
        "area": 
            "id": 2011,
            "name": "Argentina",
            "countryCode": "ARG",
            "ensignUrl": null
        ,
        "name": "Primera B Nacional",
        "code": null,
        "emblemUrl": null,
        "plan": "TIER_FOUR",
        "currentSeason": 
            "id": 547,
            "startDate": "2019-08-16",
            "endDate": "2020-06-14",
            "currentMatchday": 30,
            "winner": null
        ,
        "numberOfAvailableSeasons": 3,
        "lastUpdated": "2020-05-15T00:00:02Z"
    ,

目前我只是保存***对象,我希望/希望所有子对象也保存。如果我关闭子对象上的主键(使它们成为相同的列而不是它们的实际值),这一切都可以正常工作,并且所有子对象都可以完美保存。正如您从 JSON 中看到的,“区域”2011 是重复的,有两个具有相同区域的比赛,因此数据明智的是正确的,但是打开“区域”的正确主键时,它会跳闸因为它试图插入重复的记录。

所以我完全理解发生了什么以及为什么会出错,我想知道的是,是否有一种简单的方法可以告诉 EF 忽略重复的关键错误。我无法添加 try catch 来保存***对象,因为它在遇到错误时不会保存任何内容。

我尝试保存单个子对象,在保存之前测试它们是否存在,但是当它尝试保存父级对象时,它也尝试保存子对象,给我留下了同样的问题。

这是我保存***对象的代码(为简单起见,删减);

public class Area

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int id  get; set; 
    public string name  get; set; 
    public string countryCode  get; set; 
    public string ensignUrl  get; set; 


public class Winner

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int id  get; set; 
    public string name  get; set; 
    public string shortName  get; set; 
    public string tla  get; set; 
    public string crestUrl  get; set; 


public class CurrentSeason

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int id  get; set; 
    public string startDate  get; set; 
    public string endDate  get; set; 
    public int? currentMatchday  get; set; 
    public Winner winner  get; set; 


public class Competition

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int id  get; set; 
    public Area area  get; set; 
    public string name  get; set; 
    public string code  get; set; 
    public string emblemUrl  get; set; 
    public string plan  get; set; 
    public CurrentSeason currentSeason  get; set; 
    public int numberOfAvailableSeasons  get; set; 
    public DateTime lastUpdated  get; set; 


public class Example

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int count  get; set; 
    public IList<Competition> competitions  get; set; 



static void Main(string[] args)
    

        string json = GET(@"http://my.url.com/api/stuff");

        Example example = JsonConvert.DeserializeObject<Example>(json);

        using(var db = new ExampleContext())
        
            db.Examples.Add(example);
            db.SaveChanges();
        
    

感谢期待。

【问题讨论】:

什么版本的 EF?什么数据库? @DavidBrowne-Microsoft EF 6.4.4 SQL Server 2019 如果只是将 db.Examples.Add(example); 更改为 db.Examples.Update(example); 会怎样? (没试过,只是想到了) @ArmanEbrahimpour - 感谢您的建议,但我得到了相同的主键违规异常。 【参考方案1】:

很遗憾,没有任何直接的方法可以解决您的问题。

EF Change Tracker 通过引用跟踪实体,解决问题的唯一方法是为所有相同的 areas 创建相同的对象。

为此,您有两个选择:

1- 在此行之后循环 example

Example example = JsonConvert.DeserializeObject<Example>(json);

找到所有相同的areas 并用其中一个替换所有。

2- 使用 NewtonSoft 的PreserveReferencesHandling 功能。但它需要同时应用于 Serialize 和 Deserialize 方面:

服务器(Api)端:

string json = JsonConvert.SerializeObject(data, Formatting.Indented,
   new JsonSerializerSettings  PreserveReferencesHandling = PreserveReferencesHandling.Objects );

客户端:

var example = JsonConvert.DeserializeObject<Example>(json,
   new JsonSerializerSettings  PreserveReferencesHandling = PreserveReferencesHandling.Objects );

【讨论】:

这太令人失望了!!!!感谢您的反馈,非常感谢您的帮助。

以上是关于将反序列化的 JSON 对象保存到具有重复子实体的数据库的主要内容,如果未能解决你的问题,请参考以下文章

如何将反序列化JSON中的列表传递给视图到选择列表

如何将具有嵌套属性的 JSON 对象反序列化为 Symfony 实体?

如何从实体框架中存在数据模型的json中反序列化对象?

将具有嵌套对象的对象保存到文件的最佳方法?

在将实体框架对象图序列化为 Json 时防止 ***Exception

保存子实体的重复键值