大约在2 years+之前,我问过这个question,这个问题被Steve善意地解决了。
在与子对象映射时,我现在有一个类似但不同的问题。我已经处理过几次这个问题了,但是面对这个问题,我不禁想,一定会有一个更优雅的解决方案。我正在编写一个记忆系统的Blazor,并希望通过一个web更新成员的详细信息。都很正常。我有一个库功能来更新成员:
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) };
}
}
成员资格对象是EF对象,它引用了许多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异常:
实体类型'MembershipType‘的实例无法跟踪,因为另一个具有{'Id'}键值相同的实例已经被跟踪。在附加现有实体时,请确保只附加一个具有给定键值的实体实例。
我认为(从前一段时间我提出的问题)我理解正在发生的事情,以前,我通过一个单独的函数来解决这个问题,在这种情况下,它将更新成员类型。然后,把它从“发现”和“发送”对象中去掉,让Mapper做剩下的事情。
在我的映射概要文件中,为这些对象类型定义了如下映射:
CreateMap<Membership, MembershipLTDTO>();
CreateMap<MembershipLTDTO, Membership>();
CreateMap<MembershipTypeDTO, MembershipType>();
CreateMap<MembershipType, MembershipTypeDTO>();
当我准备再次做这件事的时候,我想知道我是否错过了使用Mapper或者实体框架( Entity )来让它更无缝地发生的窍门?
发布于 2022-03-14 23:05:09
我想到了几件事。第一件事是,只要您没有在context.Memberships.Update(foundmembership);
中禁用跟踪,这里就不需要调用DbContext。调用SaveChanges
将为任何值更改(如果有的话)构建一个UPDATE
SQL语句,Update
将试图覆盖实体(Ies)。
在处理引用时,您可能遇到的问题很常见,因此我建议采用不同的方法。要概述这一点,让我们来看看成员类型。这些通常是我们希望与新成员和现有成员相关联的已知列表。我们永远不会期望创建一个新的成员类型,作为创建或更新成员资格的操作的一部分,只需添加或删除现有成员资格的关联即可。
为此使用Automapper的问题是,当我们希望在DTO传递中关联另一个成员类型时。假设我们有与成员关系类型#1相关联的现有数据,我们希望添加membership类型2。我们加载原始实体类型来复制值,急切地加载成员类型,从而获得成员资格和类型1,到目前为止还不错。但是,当我们调用Mapper.Map()时,它会在DTO中看到一个MemberShip类型2,因此它将向加载的成员类型集合的集合中添加一个ID #2的新实体。在这里,有三件事是可以发生的:
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忽略成员类型集合,然后处理这些集合。
_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();
对于要删除的项,我们将在加载的数据状态中找到这些实体,并将它们从集合中删除。对于添加的新项,我们转到上下文中,将它们全部获取,并将它们添加到加载的数据状态的集合中。
https://stackoverflow.com/questions/71466047
复制相似问题