使用实体框架更新多对多关系

本文关键字:关系 更新 框架 实体 | 更新日期: 2023-09-27 18:28:03

我有一个多对多联接表,带有一个双关键字(PersonId,RoleId)。为了简单起见,我在Person表中只有一个PersonId。此外,使用EF7,它还不支持EF6的很多优点(例如通过导航属性连接表的隐含性)。

虽然我可以在SQLite中运行这个查询,并且它可以毫无问题地运行:update PersonRole set RoleId = 2 where PersonId = 1,但我不能在EF:中做同样的事情

var du = context.PersonsRoles.Where(p => p.PersonId == 1).First();
du.RoleId = 2;
context.PersonsRoles.Update(du);
context.SaveChanges(); //get an error here

错误是:"发生了未处理的异常:实体类型"PersonRole"的属性"RoleId"是键的一部分,因此无法修改或标记为已修改。"

(ETA根据下面的评论)-我的模型是:

public class PersonRole
    {
        public virtual int PersonId { get; set; }
        public virtual int RoleId { get; set; }
    }

我找到了一个答案,其中包括删除原始行(1,1)然后重新插入(1,2)的选项,但这对我来说似乎效率很低。这真的是修改关系的唯一方法吗?

使用实体框架更新多对多关系

您试图在一侧修改多对多关系的键。多对多关系在数据库中用一个表表示,该表包含关系中双方的外键。

您试图做的是尝试更改对象的键,但外键表中仍保留引用,导致违反约束,因为N-N表中的值尚未更新。

EF7中不允许进行此更改。您应该使用SQL命令来执行此操作,而不是考虑多对多表更新。

您可以使用我所做的扩展来删除未选择的,并将新选择的添加到列表中

    public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
    {
        db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
        db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
    }
    public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
    {
        return items
            .GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
            .SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
            .Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
            .Select(t => t.t.item);
    }

使用它看起来像这个

 var model = db.Persons
             .Include(x => x.PersonsRoles)
             .FirstOrDefault(x => x.PersonId == person.PersonId );
 db.TryUpdateManyToMany(model.PersonsRoles, listOfNewRoleIds
 .Select(x => new PersonRole
 {
     RoleId = x,
     PersonId = person.PersonId
 }), x => x.PersonId);