大約 2 年前,我問了這個問題,Steve Py 很好地解決了這個問題。
在使用子物件進行映射時,我現在遇到了類似但不同的問題。我遇到過幾次這個問題并解決了這個問題,但再次面臨這樣做,我不禁想到必須有一個更優雅的解決方案。我正在 Blazor Wasm 中撰寫會員系統,并希望通過 web-api 更新會員詳細資訊。都很正常。我有一個庫函式來更新成員資格:
public async Task<MembershipLTDTO> UpdateMembershipAsync(APDbContext context, MembershipLTDTO sentmembership)
{
Membership? foundmembership = context.Memberships.Where(x =>x.Id == sentmembership.Id)
.Include(x => x.MembershipTypes)
.FirstOrDefault();
if (foundmembership == null)
{
return new MembershipLTDTO { Status = new InfoBool(false, "Error: Membership not found", InfoBool.ReasonCode.Not_Found) };
}
try
{
_mapper.Map(sentmembership, foundmembership, typeof(MembershipLTDTO), typeof(Membership));
//context.Entry(foundmembership).State = EntityState.Modified; <-This was a 'try-out'
context.Memberships.Update(foundmembership);
await context.SaveChangesAsync();
sentmembership.Status = new InfoBool(true, "Membership successfully updated");
return sentmembership;
}
catch (Exception ex)
{
return new MembershipLTDTO { Status = new InfoBool(false, $"{ex.Message}", InfoBool.ReasonCode.Not_Found) };
}
}
Membership 物件是一個 EF DB 物件,并參考了 MembershipTypes 的多對多串列:
public class Membership
{
[Key]
public int Id { get; set; }
...more stuff...
public List<MembershipType>? MembershipTypes { get; set; } // The users membership can be several types. e.g. Employee Director etc..
}
MembershipLTDTO 是一個輕量級的 DTO,移除了一些重物。
執行代碼,我得到一個 EF 例外:
The instance of entity type 'MembershipType' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I think (from the previous question I asked some time ago) that I understand what is happening, and previously, I have worked around this by having a seperate function that would in this case update the membership types. Then, stripping it out of the 'found' and 'sent' objects to allow Mapper to do the rest.
In my mapping profile I have the mappings defines as follows for these object types:
CreateMap<Membership, MembershipLTDTO>();
CreateMap<MembershipLTDTO, Membership>();
CreateMap<MembershipTypeDTO, MembershipType>();
CreateMap<MembershipType, MembershipTypeDTO>();
As I was about to go and do that very thing again, I was wondering if I am missing a trick with my use of Mapper, or Entity Framework that would allow it to happen more seamlessly?
uj5u.com熱心網友回復:
我想到了幾件事。首先,context.Memberships.Update(foundmembership);只要您沒有在 DbContext 中禁用跟蹤,這里就不需要呼叫 to。呼叫將為嘗試覆寫物體的任何值更改(如果有)SaveChanges構建SQL 陳述句。UPDATEUpdate
您在處理參考文獻時可能遇到的問題很常見,因此我會推薦一種不同的方法。為了概述這一點,讓我們看一下成員資格型別。這些通常是我們想要與新成員和現有成員相關聯的已知串列。我們不會期望在創建或更新成員資格的操作中創建新的成員資格型別,只需添加或洗掉與現有成員資格的關聯即可。
使用 Automapper 的問題是當我們想在傳入的 DTO 中關聯另一個成員型別時。假設我們有一個與 Membership Type #1 關聯的成員資格的現有資料,并且我們想要添加 MemberShip Type #2。我們加載原始物體型別以復制值,急切加載成員型別,因此我們獲得成員和型別#1,到目前為止一切都很好。但是,當我們呼叫 Mapper.Map() 時,它會在 DTO 中看到 MemberShip Type #2,因此它會將 ID 為 #2 的新物體添加到我們加載的 Membership 的 Types 集合的集合中。從這里開始,可能會發生以下三種情況之一:
1) The DbContext was already tracking an instance with ID #2 and
will complain when Update tries to associate another entity reference
with ID #2.
2) The DbContext isn't tracking an instance, and attempts to add #2
as a new entity.
2.1) The database is set up for an Identity column, and the new
membership type gets inserted with the next available ID. (I.e. #16)
2.2) The database is not set up for an Identity column and the
`SaveChanges` raises a duplicate constraint error.
這里的問題是 Automapper 不知道應該從 DbContext 中檢索任何新的成員資格型別。
使用 Automapper 的Map方法可用于更新子集合,但它只能用于更新作為頂級物體的實際子項的參考。例如,如果您有一個客戶和一組聯系人,其中更新您想要更新、添加或洗掉聯系人詳細資訊記錄的客戶,因為這些子記錄歸他們的客戶所有,并與他們的客戶顯式關聯。Automapper 可以添加到集合中或從集合中洗掉,并更新現有專案。對于像多對多/多對一這樣的參考,我們不能依賴它,因為我們希望關聯現有物體,而不是添加/洗掉它們。
在這種情況下,建議告訴 Automapper 忽略 Membership Types 集合,然后再處理這些。
_mapper.Map(sentmembership, foundmembership, typeof(MembershipLTDTO), typeof(Membership));
var memberShipTypeIds = sentmembership.MembershipTypes.Select(x => x.MembershipTypeId).ToList();
var existingMembershipTypeIds = foundmembership.MembershipTypes.Select(x => x.MembershipTypeId).ToList();
var idsToAdd = membershipTypeIds.Except(existingMembershipTypeIds).ToList();
var idsToRemove = existingMembershipTypeIds.Except(membershipTypeIds).ToList();
if(idsToRemove.Any())
{
var membershipTypesToRemove = foundmembership.MembershipTypes.Where(x => idsToRemove.Contains(x.MembershipTypeId)).ToList();
foreach (var membershipType in membershipTypesToRemove)
foundmembership.MembershipTypes.Remove(membershipType;
}
if(idsToAdd.Any())
{
var membershipTypesToAdd = context.MembershipTypes.Where(x => idsToRemove.Contains(x.MembershipTypeId)).ToList();
foundmembership.MembershipTypes.AddRange(membershipTypesToAdd); // if declared as List, otherwise foreach and add them.
}
context.SaveChanges();
For items being removed, we find those entities in the loaded data state and remove them from the collection. For new items being added, we go to the context, fetch them all, and add them to the loaded data state's collection.
uj5u.com熱心網友回復:
盡管將 Steve Py 的解決方案標記為答案,因為它是一個有效的解決方案,盡管不像我希望的那樣“優雅”。
然而,我被
Lucian Bargaoanu 的評論指出了另一個方向,雖然有點神秘,但經過一些挖掘,我發現它可以作業。
為此,我必須將“AutoMapper.Collection”和“AutoMapper.Collection.EntityFrameworkCore”添加到我的解決方案中。將其設定為示例 [此處][2] 時有一些棘手的問題,與我的設定不匹配。我在我的 program.cs 中使用了這個:
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
mc.AddCollectionMappers();
});
我還必須修改物件的映射組態檔 - DTO 映射到此:
//Membership Types
CreateMap<MembershipTypeDTO, MembershipType>().EqualityComparison((mtdto, mt) => mtdto.Id == mt.Id);
用于告訴 AutoMapper 哪些欄位用于相等。
我按照 Steve Py 的建議取出了 context.Memberships.Update 并且它有效。
代表提問者發布
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/446716.html
