[英]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();
}
將事件轉換為對id
的Lookup
(類似於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.