【问题标题】:Update a Many to Many relationship using Entity Framework?使用实体框架更新多对多关系?
【发布时间】:2015-12-26 15:52:51
【问题描述】:

我有一个多对多连接表,带有一个双键(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) 的选项,但这对我来说似乎效率低下。这真的是改变关系的唯一方法吗?

【问题讨论】:

  • 猜猜PersonIdRoleId 是钥匙吗?如果是,则 EF 无法修改密钥。只是可以使用原始 sql 命令来更新它们。
  • 您使用过 PersonsRoles 和 PeopleRoles。是错字吗?如果你展示你的模型会更好。
  • 是的,是一个错字。并包括模型
  • context.Database.ExecuteSqlCommand("UPDATE PersonsRoles SET RoleId=2 WHERE PersonId=1 AND RoleId=1"); 也许这会起作用。

标签: c# entity-framework entity-framework-core


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    您可以使用我制作的这个扩展来删除未选择的并将新选择的添加到列表中

        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);
    

    【讨论】:

    • 第一个参数应该是IList&lt;T&gt; currentItems而不是IEnumerable&lt;T&gt; currentItems,因为它不应该发出N+1请求
    猜你喜欢
    • 2014-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-14
    • 1970-01-01
    • 2017-01-28
    • 2011-12-17
    相关资源
    最近更新 更多