【问题标题】:Updating many-to-many links with direct relationships更新具有直接关系的多对多链接
【发布时间】:2021-03-11 23:33:26
【问题描述】:

我在 .NET 5.0 Web 应用程序中使用 Entity Framework Core 5。我有两个以多对多关系连接的类。如here 所述,我正在使用“直接”方法。这意味着我没有在代码中明确定义的连接表;相反,EF 已从以下架构推断出这种关系:

public class User
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }
    public ICollection<Group> Groups { get; set; }
}

public class Group
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }
    public ICollection<User> Users { get; set; }
}

我希望能够通过简单地向 EF 提供新对象来更新“组”或“用户”。例如,如果我为 EF 提供了一个与数据库中已有的组具有相同 ID 的“组”对象,但它现在在用户集合中有一个额外的“用户”,那么我希望 EF 更新“用户组” ' 表,它在幕后制作了一条新记录来表示这种关系。

这是我尝试过的:

1.

    public void Update(Group group)
    {
        Group oldGroup = _context.Groups
            .Include(g => g.Users)
            .First(g => g.ID == group.ID);

        _context.Entry(oldGroup).CurrentValues.SetValues(group);
        _context.SaveChanges();
    }

结果:保存对属于 Group 对象的任何道具的更改,但不影响任何关系。

2.

    public void Update(Group group)
    {
        Group oldGroup = _context.Groups
            .Include(g => g.Users)
            .First(g => g.ID == group.ID);

        oldGroup.Users = group.Users;

        _context.Entry(oldGroup).CurrentValues.SetValues(group);
        _context.SaveChanges();
    }

结果:异常。

System.InvalidOperationException: 'The instance of entity type 'User' 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. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'

我从here 为 Intersect 和 LeftComplementRight 添加了一些扩展方法,并尝试自己计算差异。

    public void Update(Group group)
    {
        Group oldGroup = _context.Groups
            .Include(g => g.Users)
            .First(g => g.ID == group.ID);

        var oldUsers = oldGroup.Users;
        var newUsers = group.Users;

        var toBeRemoved = oldUsers.LeftComplementRight(newUsers, x => x.ID);
        var toBeAdded = newUsers.LeftComplementRight(oldUsers, x => x.ID);
        var toBeUpdated = oldUsers.Intersect(newUsers, x => x.ID);

        foreach (var u in toBeAdded)
        {
            oldGroup.Users.Add(u);
        }

        foreach (var u in toBeRemoved)
        {
            oldGroup.Users.Remove(u);
        }

        _context.Entry(oldGroup).CurrentValues.SetValues(group);
        _context.SaveChanges();
    }

结果:与上述相同的异常。 此时我意识到组成“用户”集合的“用户”对象已实例化“组”对象并填充回组对象的自引用。我意识到这一点可能是混淆了 EF,所以我尝试了这个:

4.

    public void Update(Group group)
    {
        Group oldGroup = _context.Groups
            .Include(g => g.Users)
            .First(g => g.ID == group.ID);

        var oldUsers = oldGroup.Users;
        var newUsers = group.Users;

        var toBeRemoved = oldUsers.LeftComplementRight(newUsers, x => x.ID);
        var toBeAdded = newUsers.LeftComplementRight(oldUsers, x => x.ID);
        var toBeUpdated = oldUsers.Intersect(newUsers, x => x.ID);

        foreach (var u in toBeAdded)
        {
            u.Groups = null;
            oldGroup.Users.Add(u);
        }

        foreach (var u in toBeRemoved)
        {
            u.Groups = null;
            oldGroup.Users.Remove(u);
        }

        _context.Entry(oldGroup).CurrentValues.SetValues(group);
        _context.SaveChanges();
    }

这行得通,但似乎我做的工作太多了。我预计在整个项目中会有多个多对多关系,并且我不想在每个更新方法上都复制此代码。我想我可以创建一个扩展方法,但我觉得 EF 应该能够处理这个常见的用例。

我错过了什么吗?

【问题讨论】:

  • 字数太多,看不懂你的问题。您想向组中添加新用户还是从组中删除用户?没有其他选择
  • 添加或删除,或都不添加。看4下的代码。这是我想要实现的,但我正在寻找更好的方法来做到这一点。
  • 这种情况很少发生,因为我喜欢简单的决定。所以我通常只是从组中删除所有旧用户,然后从新组中添加所有用户。我不在乎某些用户是否可以相交,因为这是最可靠和最简单的方法。

标签: c# entity-framework asp.net-core entity-framework-core


【解决方案1】:

你可以试试这个。基本上只是用当前跟踪的实例替换实体(具有匹配 ID 的实体)。列表中的新实体会在您保存时自动保留。并且原列表中存在但新列表中不存在的实体将被自动删除。

public void Update(Group updatedGroup)
{
    Group group = _context.Groups
        .Include(g => g.Users)
        .First(g => g.ID == updatedGroup.ID);

    group.Users = updatedGroup.Users
        .Select(u => group.Users.FirstOrDefault(ou => ou.ID == u.ID) ?? u)
        .ToList();

    // Do other changes on group as needed.

    _context.SaveChanges();
}

是的,没有一种简单的方法可以以这种方式替换实体列表,这有点荒谬。虽然如果您考虑到通常我们甚至不会在类似更新的方法中获取实体实例,它会变得不那么荒谬,因为通常我们通过 DTO 获取更改,所以我们不能只替换列表开始吧。

如果它不起作用,请告诉我(这里已经很晚了)。 :)

【讨论】:

  • 谢谢,这行得通。我仍然必须迭代地将“updatedGroup”中所有嵌套用户对象的“Groups”属性设置为 null,但我意识到这是我如何获取更新组的问题,而不是 EF。
  • 我很高兴它有效。虽然我当然测试过。 :) 首先,我尝试使用 RemoveRange/UpdateRange 方法,但没有成功。
  • 也许ChangeTracker.TrackGraph 会更容易使用。更新组,附加所有用户...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-05
  • 1970-01-01
  • 1970-01-01
  • 2015-08-25
  • 2021-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多