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

Ski*_*e63 5 c# json entity-framework ef-code-first

我正在从 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"
    },
Run Code Online (Sandbox Code Playgroud)

目前我只是保存顶级对象,并且我期望/希望所有子对象也保存。如果我关闭子对象上的主键(使它们成为同一列而不是其实际值),那么这一切都可以正常工作,并且所有子对象都可以完美保存。正如您从 JSON 中看到的,“area”2011 是重复的,有两个竞赛具有相同的面积,因此数据方面它是正确的,但在打开“area”的正确主键时,它会出错因为它试图插入重复的记录。

所以我完全理解发生了什么以及为什么会出错,我想知道的是,是否有一种简单的方法可以告诉 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

谢谢期待。

Arm*_*our 3

不幸的是,没有任何直接的方法可以解决您的问题。

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

为此,您有两种选择:

example1-在该行之后循环

Example example = JsonConvert.DeserializeObject<Example>(json);
Run Code Online (Sandbox Code Playgroud)

并找到所有相同的areas并将所有替换为其中之一。

2- 使用NewtonSoft 的PreserveReferencesHandling功能。但它需要同时应用于序列化和反序列化端:

服务器(Api)端:

string json = JsonConvert.SerializeObject(data, Formatting.Indented,
   new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
Run Code Online (Sandbox Code Playgroud)

客户端:

var example = JsonConvert.DeserializeObject<Example>(json,
   new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
Run Code Online (Sandbox Code Playgroud)