簡體   English   中英

將數據保存到數據庫 .NET 5.0 時出現數據上下文錯誤

[英]Data Context error when saving data to database .NET 5.0

我在數據庫中保存數據時遇到了一點問題。 在給定的方法中,我向 API 發出請求,對數據進行序列化,然后嘗試保存到數據庫中,如圖所示,但最終在引用應用程序的 DataContext 時出現錯誤,說:

System.InvalidOperationException:無法跟蹤實體類型“Launch”的實例,因為已經在跟蹤具有相同鍵值 {'id'} 的另一個實例。 附加現有實體時,請確保僅附加一個具有給定鍵值的實體實例。 考慮使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”來查看沖突的鍵值。

有關如何解決此問題的任何提示?

保存數據庫方法

using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RestSharp;
using SpaceFlightApiChallenge.Data.Context;
using SpaceFlightApiChallenge.Domain.Entities;

namespace SpaceFlightApiChallenge.Data
{
    public class DatabaseImport
    {
        private readonly SpaceFlightContext _context;

        public DatabaseImport(SpaceFlightContext context)
        {
            _context = context;
        }

        public async Task ImportData()
        {
            string url = "https://api.spaceflightnewsapi.net/v3/";
            var client = new RestClient(url);
            var apiRequest = new RestRequest("articles", Method.Get);
            apiRequest.AddHeader("Accept", "application/json");

            var response = await client.ExecuteAsync(apiRequest);
            var content = response.Content;
            var articles = JsonConvert.DeserializeObject<List<Article>>(content);
            
            _context.Articles.AddRange(articles);
            await _context.SaveChangesAsync();
        }
    }
}

文章 Class

using System.Collections.Generic;

namespace SpaceFlightApiChallenge.Domain.Entities
{
    public class Article
    {
        public int id { get; set; }
        public bool featured { get; set; }
        public string title { get; set; }
        public string url { get; set; }
        public string imageUrl { get; set; }
        public string newsSite { get; set; }
        public string summary { get; set; }
        public string publishedAt { get; set; }
        public List<Launch> launches { get; set; }
        public List<Event> events { get; set; }
    }
}

發射 Class

namespace SpaceFlightApiChallenge.Domain.Entities
{
    public class Launch
    {
        public string id { get; set; }
        public string provider { get; set; }
    }
}

事件 Class

namespace SpaceFlightApiChallenge.Domain.Entities
{
    public class Event
    {
        public string id { get; set; }
        public string provider { get; set; }
    }
}

編輯:我相信整個問題出在身份證上。 外部 API 返回給我的所有對象(文章、事件、啟動)都有自己的 id,但是當它們進入數據庫時,EF Core 想要分配一個新的 id,因為 identity 屬性。 我不需要更改id,數據必須保存在數據庫中,因為它來自API,以便以后可以查閱。 這是我從中獲取數據的 API 鏈接: https://api.spaceflightnewsapi.net/v3/documentation

我可以在您的文章中看到 class

[DatabaseGenerated(DatabaseGeneratedOption.None)] 
public int id { get; set; } 

嘗試改變它

[Key]
public int id { get; set; } 

我會說這是因為外部服務給你重復的 ID,而你的 JSON 反序列化例程只是制作具有相同 ID 的新對象並將它們附加到 object 圖。 這意味着 EF 最終會收到以下內容:

{ 
  id: 13479,
  title: Engineers taking more time ...
  events: [ { id: 482 } ]                 //object at memory address 1234
},
{ 
  id: 13477,
  title: NASA takes break in JWST deployment ...
  events: [ { id: 482 } ]                 //object at memory address 4567
}

具有相同 ID 的兩個不同 Event 對象; EF 期望那些是相同的 object 實例

您是否可以獲得 JSON deser 例程來解決此問題(例如,通過保留基於 ID 的前見對象字典),而無需自定義 deser,我不知道 *..

..但應該可以修復對象,以便圖形實體是唯一的:

var events = articles.SelectMany(a => a.Events).ToLookup(e => e.Id);
var launches = articles.SelectMany(a => a.Launches).ToLookup(l=> l.Id);

foreach(var a in articles){
  a.Events = a.Events.Select(e => events[e.Id].First()).ToList();
  a.Launches = a.Launches.Select(l => launches[l.Id].First()).ToList();
}

將事件轉換為對idLookup (類似於Dictionary<int, List<Event>> )會將所有不同的事件對象分組為可通過 id 訪問的枚舉。 然后可以使用它來重新創建一個事件列表,其中的所有事件都引用一個公共事件。 如果我們枚舉事件列表,從我們在列表中找到的事件中提取id並使用它來查找查找中的第一個事件,它將所有內容都指向該 id 的相同事件實例

這意味着您有一個名為events的查找:

events -> lookup of [ { id:482 /*mem address 1234*/ }, {id: 482 /*mem address 4567*/ } ]

如果您曾經有一篇文章 13477,其事件有一個事件(ID 為 482,在 memory 地址 4567),則該列表將替換為事件再次為 482 的事件列表,但這次指向位於 ZCD69B4957F06CD818D7BF3D23980E291Z 地址 1 的事件

{ 
  id: 13479,
  title: Engineers taking more time ...
  events: [ { id: 482 } ]                 //object at memory address 1234: first element in lookup 482
},
{ 
  id: 13477,
  title: NASA takes break in JWST deployment ...
  events: [ { id: 482 } ]                 //object at memory address 1234: first element in lookup 482
}

現在.. 我希望這些事件不會在下載調用中重復(即下周將有一篇同樣具有 id 482 的文章),因為如果這樣做,您可能會遇到主鍵違規。

該問題的解決方案可能是擴展修補 object 圖的循環,以便它首先在數據庫中查找 482 的事件,然后將其放入文章中(然后回退到新的如果數據庫不知道事件 482)


筆記; 您可以使用[JsonProperty("json name here")]屬性來聲明 json 中的名稱與 C# 中的名稱,因此您不必讓 C# 違反常規 PasscalC 約定

Here's a bunch of code generated from http://quicktype.io (no affiliaiton) that is C# naming convention square, and deser's your JSON. 如果需要,您可以將其與您的實體混合:


// <auto-generated />
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
//    using SpaceX;
//
//    var article = Article.FromJson(jsonString);

namespace SpaceX
{
    using System;
    using System.Collections.Generic;

    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;

    public partial class Article
    {
        [JsonProperty("id")]
        public long Id { get; set; }

        [JsonProperty("title")]
        public string Title { get; set; }

        [JsonProperty("url")]
        public Uri Url { get; set; }

        [JsonProperty("imageUrl")]
        public Uri ImageUrl { get; set; }

        [JsonProperty("newsSite")]
        public string NewsSite { get; set; }

        [JsonProperty("summary")]
        public string Summary { get; set; }

        [JsonProperty("publishedAt")]
        public DateTimeOffset PublishedAt { get; set; }

        [JsonProperty("updatedAt")]
        public DateTimeOffset UpdatedAt { get; set; }

        [JsonProperty("featured")]
        public bool Featured { get; set; }

        [JsonProperty("launches")]
        public List<Launch> Launches { get; set; }

        [JsonProperty("events")]
        public List<Event> Events { get; set; }
    }

    public partial class Event
    {
        [JsonProperty("id")]
        public long Id { get; set; }

        [JsonProperty("provider")]
        public string Provider { get; set; }
    }

    public partial class Launch
    {
        [JsonProperty("id")]
        public Guid Id { get; set; }

        [JsonProperty("provider")]
        public string Provider { get; set; }
    }

    public partial class Article
    {
        public static List<Article> FromJson(string json) => JsonConvert.DeserializeObject<List<Article>>(json, SpaceX.Converter.Settings);
    }

    public static class Serialize
    {
        public static string ToJson(this List<Article> self) => JsonConvert.SerializeObject(self, SpaceX.Converter.Settings);
    }

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters =
            {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }
}

* 我希望這是可能的,但我從來沒有直接使用數據庫實體(我也不建議這樣做;我通常有一組用於 API 穿梭的 Dtos 和另一組用於數據庫工作的實體)

也許您的列表包含重復項。 EF 不允許跟蹤具有相同密鑰的實體。 如果您有該錯誤,請嘗試

_context.Entry(<entity>).State = EntityState.Detached;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM