Entity Framework 更新多对多关系的中间层 table

Entity Framework update the intermediate table of a many-to-many relation

我在 userproject 之间建立了多对多关系。喜欢:

class User 
{
    public ICollection<Project> Projects { get; set; }
}

class Project 
{
    public ICollection<User> Users { get; set; }
}

Entity Framework自动生成中间table.

问题是我想更新用户以及整个项目列表。可以以任何方式修改此列表,可以添加和删除项目。在更新用户对象之前的同一时间。

我总是遇到同样的错误,即 Entity Framework 试图在中间 table 添加重复条目。

我尝试了很多事情都没有成功(下面列出了一些)。

var tmp = Context.Entry(user); // user being the updated object.
tmp.State = EntityState.Modified;
tmp.Collection(e => e.Projects).IsModified = true;
        
Context.Users.Update(user);
Context.SaveChanges();

var tmp = Context.Users.SingleOrDefault(u => u.Id == user.Id);

if (tmp == null) 
     return null;

Context.Entry(tmp).CurrentValues.SetValues(user);
Context.SaveChanges();

return user;

或者只是简单的旧更新:

Context.Users.Update(user);
Context.SaveChanges();

但是其中 none 有效。

这个问题听起来像是您有一个分离的用户实体和一组项目,您想要将其传递到一个方法中,与 DbContext 关联以保留更改。

您在加倍记录时遇到问题,因为当您将用户附加到 DbContext 时,它会将与用户关联的每个项目实体视为新实例,因为它们本身不引用跟踪实例。

使用关联更新分离的实体是相当复杂的,尤其是当您希望可能在操作中添加或删除关联时。

推荐的方法是从数据库加载当前用户 项目,然后利用 Automapper 来保护您可以从分离的实体复制哪些值,然后通过add/remove 任何已更改的项目引用的关联。如果可以创建一个全新的项目作为此操作的一部分与用户关联,您也需要处理它。

var existingUser = Context.Users.Include(x => x.Projects).Single(x => x.UserId == user.UserId);
Mapper.Map(user, existingUser); 
// Where Automapper is configured with a User to User mapping with allowed 
// values to copy over, ignoring anything that cannot legally be changed.

var newProjectIds = user.Projects.Select(x => x.ProjectId).ToList();
var existingProjectIds = existingUser.Projects.Select(x => x.ProjectId).ToList();
var projectIdsToAdd = newProjectIds.Except(existingProjectIds).ToList();
var projectIdsToRemove = existingProjectIds.Except(newProjectIds).ToList();

var projectsToAdd = Context.Projects.Where(x => projectIdsToAdd.Contains(x.ProjectId)).ToList();
var projectsToRemove = existingUser.Projects.Where(x => projectIdsToRemove.Contains(x.ProjectId)).ToList();

foreach(var project in projectsToRemove)
    existigUser.Projects.Remove(project);
foreach(var project in projectsToAdd)
    existingUser.Projects.Add(project);

Context.SaveChanges();

... 这个例子不包括全新项目的可能性。如果更新后的用户可以包含一个全新的项目,那么您需要在查找 projectsToAdd 时检测这些项目,以从传入的项目列表中添加任何项目,其中 ID 在新项目 ID 中但未在数据库中找到。这些分离的引用可以添加到从 DbContext 加载的用户中,但是您确实需要处理每个项目可能必须避免重复的任何导航属性,将每个属性替换为对跟踪实体的引用,包括任何 bi-directional 引用返回给用户(如果存在)。

一般来说,处理分离的实体有多种注意事项,您需要牢记并慎重处理。通常最好避免传递分离的实体,而是旨在传递您想要关联的数据的最小表示,然后加载并调整它 server-side。通常使用分离实体的理由是避免必须再次加载数据,但是这会在尝试同步这些分离实例时导致更多代码,并且忽略了数据状态在分离实例被采用后可能已经改变的事实。例如,上面的代码还应该查看分离实体和加载状态之间的实体版本控制,以检测是否有其他人可能进行了更改,因为在用户进程开始时读取了分离副本以进行更改。