我最近切換到 ContextFactory 因為 second operation was started on this context ...
于是我注冊了DbContextFactory:
builder.Services.AddDbContextFactory<CharacterSheetDbContext>(
options => {
options.UseMySql(
builder.Configuration.GetConnectionString("DefaultConnection"),
new MySqlServerVersion(new Version(8, 0, 27))
);
options.EnableSensitiveDataLogging();
}
);
然后我有我的服務層:
在ctor中注入工廠:
public ARepository(IDbContextFactory<CharacterSheetDbContext> contextFactory) {
_contextFactory = contextFactory;
}
和更新方法(我創建我的背景關系):
public async Task UpdateAsync(TEntity entity) {
await using var context = await _contextFactory.CreateDbContextAsync();
var set = context.Set<TEntity>();
//_context.ChangeTracker.Clear(); <-- (tried with and without this one)
set.Update(entity);
await context.SaveChangesAsync();
}
但是當我在物體上呼叫 Update 時,它??會拋出以下錯誤:
An exception occurred in the database while saving changes for context type 'Model.Configurations.CharacterSheetDbContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> MySqlConnector.MySqlException (0x80004005): Duplicate entry '3-9' for key 'characters_has_personalities.PRIMARY'
所以它會嘗試再次插入這些東西,但我只是希望它在不更改的情況下不更新。
我應該知道什么?先感謝您!
uj5u.com熱心網友回復:
自從切換到 DbContext 工廠后,您收到該例外的原因是因為您正在獲取一個物體并告訴工廠創建的全新 DbContext 實體來持久化它,以及與之關聯的任何物體。
您最肯定看到的問題不在于您嘗試更新的物體,而是該物體擁有的子物體或參考物體。在您的情況下,如果您嘗試持久化一個角色,并且該角色具有一組 Personalities,那么由于 DBContext 沒有跟蹤 Personalities 的這些實體,它會將它們視為新物體并嘗試插入它們。
坦率地說,與分離的物體一起作業是一種痛苦。將物體傳遞給新的 DbContext 實體并呼叫 可能看起來很簡單Update,但除了最簡單的場景之外,它很少那么簡單。嘗試將資料訪問層構建為通用實作只會增加復雜性。
在最基本的層面上,在處理具有關系的物體時,您需要在持久化時將這些相關物體關聯到 DbContext。但是,首先您需要檢查有問題的 DbContext 實體是否已經在跟蹤匹配的物體,如果是,則替換參考。
public async Task UpdateCharacterAsync(Character character)
{
await using var context = await _contextFactory.CreateDbContextAsync();
var set = context.Set<Character>();
foreach(var personality in character.Personalities)
{
context.Attach(personality);
}
set.Update(entity);
await context.SaveChangesAsync();
}
這至少可以讓你朝著正確的方向前進。因為我們需要了解 Character 可能參考的任何其他內容,并確保 DbContext 正在跟蹤這些內容,所以這種型別的方法在不訴諸大量復雜性和反思的情況下不可能真正成為 Generic。即使這樣,這種方法也不是萬無一失的,也不是有效的。如果同一相關物體有可能在一組關系中出現多次,并且傳入的對該物體的參考不是同一個參考,則在第二次嘗試附加該物體時可能會遇到錯誤。這在處理反序列化的物體圖時尤其重要,例如使用 MVC 或 Ajax 呼叫,其中物體和相關資料是從 JSON 反序列化的。例如,如果 Character 和 Personality 參考了 CreatedBy User 物體,則需要附加這些物體。假設您有 2 個對用戶 ID #1 的參考,它們是 2 個不同的物件,而不是對同一物件的 2 個參考。當您附加第一個實體時,一切都很好,但嘗試附加第二個實體會導致背景關系已經在跟蹤具有相同 ID 的實體的錯誤。
使用分離的物體可能會很痛苦。使用分離的物體圖(關系)很容易成為一場噩夢,因為這樣的錯誤是情境性的。
使用它也效率不高,Update因為這將導致 UPDATE 陳述句更新表中的所有列,無論是否發生更改。如果您不使用快照和行版本控制之類的東西,這也會產生需要考慮過時資料覆寫的情況。Update雖然傳遞物體并呼叫以避免重新加載物體似乎更好,但這有幾個缺點,只有在SaveChanges()呼叫時才會有些模糊地顯示出來。在執行更新時,我的建議是使用跨方法讀取和復制,因為這允許您隨時驗證資料狀態,并確保 DB UPDATE 陳述句僅在值更改時運行,以及哪些列實際更改。
理想情況下,這與將 DTO 發送到僅包含可能更改的欄位的 Update 呼叫相結合,并且在參考的情況下,只需要為這些參考傳遞 ID 或 ID 集合。這使從客戶端到服務器的有效負載大小盡可能緊湊,而不是傳遞整個物體結構。
Automapper 的Map(source, destination)方法可以幫助將更新的值從 DTO 復制到加載的物體,并且 EF 的更改跟蹤從那里接管以確定是否確實需要執行 UPDATE 陳述句。
該方法最終看起來更像:
public async Task UpdateAsync(UpdateCharacterDTO characterDto)
{
if (characterDto == null) throw new ArgumentNullException("characterDto");
await using var context = await _contextFactory.CreateDbContextAsync();
var character = context.Characters
.Include(x => x.Personalities)
.Single(x => x.CharacterId == characterDTO.CharacterId);
// Here you could check a row version on the DB vs. DTO to see if the DB had changed since the data used to build the DTO was read.
_mapper.Map(characterDto, character);
//If personalities can be added or removed...
var existingPersonalityIds = character.Personalities.Select(x => x.PersonalityId);
var personalityIdsToAdd = characterDto.PersonalityIds.Except(existingPersonalityIds).ToList();
var personalityIdsToRemove = existingPersonalityIds.Except(characterDto.PersonalityIds).ToList();
var personalitiesToAdd = await context.Personalities.Where(x => personalityIdsToAdd.Contains(x.PersonalityId).ToListAsync();
var personalitiesToRemove = character.Personalities.Where(x => personalityIdsToRemove.Contains(x.PersonalityId)).ToList();
foreach(var personality in personalitiesToRemove)
character.Personalities.Remove(personality);
foreach(var personality in personalitiesToAdd)
character.Personalities.Add(personality);
await context.SaveChangesAsync();
}
其中 CharacterDTO 是一個資料容器,其中包含可能會更新的 ID 和欄位,以及個性 ID 的集合。(用戶可能已添加或洗掉專案)
它會產生更多代碼,但應該很容易理解它在做什么,并且對于 EF 預期跟蹤/參考的內容沒有任何混淆。理想情況下,您可以通過使操作更加原子化來避免很多復雜性,例如可以更簡單地呼叫 Add 和 Remove 個性和其他相關元素,而不是嘗試在一個頂級 Update 方法中更新整個物件圖。像這樣的更新方法作業得很好,但前提是所涉及的物體從開始到結束都由同一個 DbContext 實體跟蹤。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/414966.html
標籤:
上一篇:為什么物體框架不在這里拋出例外?
