【问题标题】:Entity Framework Core - update related collectionEntity Framework Core - 更新相关集合
【发布时间】:2017-02-11 16:08:54
【问题描述】:

我正在尝试更新 ProjectModel 中的 ProjectEmployees 集合。 我想删除所有旧值并设置新值。

我的模型:

public class Project
{
    ... 
    public ICollection<ProjectEmployee> ProjectEmployees { get; set; }
}

public class ProjectEmployee
{
    public int ProjectId { get; set; }
    public virtual Project Project { get; set; }
    public int UserId { get; set; }
    public virtual Employee Employee { get; set; }
}

public class Employee
{
    public int UserId { get; set; }
    public User User { get; set; }
    ...
}
public class ProjectGroupModel //ViewModel
{
    public int ProjectId { get; set; }
    public ICollection<Employee> ProjectEmployees { get; set; }
}

这是典型的多对多关系。

我的控制器操作:

    [HttpPost("group")]
    public async Task<IActionResult> CreateGroup([FromBody] ProjectGroupModel pro)
    {
            var dbProject = await _context.Project
                .Include(p=>p.ProjectEmployees)
                .FirstAsync(p => p.ProjectId == pro.ProjectId);
            dbProject.ProjectEmployees.Clear();

            foreach (var emp in pro.ProjectEmployees)
            {
                dbProject.ProjectEmployees.Add(new ProjectEmployee()
                {
                    UserId = emp.UserId
                });
            }

            await _context.SaveChangesAsync();

            return Ok();
    }

当 pro.ProjectEmployees 为空时,来自 dbProject.ProjectEmployees 的所有记录都已正确删除,如果 dbProject.ProjectEmployees 为空,则添加了模型中的新记录,但当 dbProject.ProjectEmployees 不为空时,我无法设置新记录:

错误:

"无法跟踪实体类型'ProjectEmployee'的实例 因为另一个具有相同键的这种类型的实例已经 被跟踪。添加新实体时,对于大多数键类型,一个唯一的 如果没有设置键(即如果键 属性被赋予其类型的默认值)。如果你是 为新实体显式设置键值,确保它们不会 与现有实体或为其他生成的临时值发生冲突 新实体。附加现有实体时,确保只有一个 具有给定键值的实体实例附加到上下文。"

我试图用数百种方式修复这个动作,但总是……是错误的。

【问题讨论】:

  • 应该使用 ToList 方法将 dbProject 转换为列表。然后尝试更新它
  • 在我看来像是主键冲突。当您创建新的 ProjectEmployee() 时,您只需设置 UserId。你不应该也设置ProjectId吗?
  • ProjectEmployee 的 PK 是什么?
  • @MuhammadNasir 我正在获取 Project 的一条记录,我应该在哪里使用 ToList() ?
  • @JakubRusilko 我不这么认为,正如我所说,当 dbProject.ProjectEmployees 为空时,会正确添加新记录。

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


【解决方案1】:

它远非完美,但我能够使其工作的唯一方法是从相应的 DbSet 中删除项目并在添加新项目之前调用 SaveChanges

    var dbProject = await _context.Project
        .Include(p=>p.ProjectEmployees)
        .FirstAsync(p => p.ProjectId == pro.ProjectId);

    if (dbProject.ProjectEmployees.Any())
    {
        _context.ProjectEmployee.RemoveRange(dbProject.ProjectEmployees);
        await _context.SaveChangesAsync();
    }

    foreach (var emp in pro.ProjectEmployees)
    {
        dbProject.ProjectEmployees.Add(new ProjectEmployee()
        {
            UserId = emp.UserId
        });
    }
    
    await _context.SaveChangesAsync();

【讨论】:

  • 目前我不在乎它是否完美,因为它有效!非常感谢。不值得寻找理想的解决方案。我为此浪费了很多时间。有时最简单的方法是最好的;)
  • 什么是最佳解决方案?我有一种保存的感觉,肯定应该有更好的东西。
【解决方案2】:

链接到another SO question

我可以在这里用你的课来回答。

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

    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.Employees
             .Include(x => x.ProjectEmployees)
             .FirstOrDefault(x => x.EmployeeId == employee.EmployeeId);

 db.TryUpdateManyToMany(model.ProjectEmployees, listOfNewProjectIds
 .Select(x => new ProjectEmployee
 {
     ProjectId = x,
     EmployeeId = employee.EmployeeId
 }), x => x.ProjectId );

【讨论】:

    【解决方案3】:

    我认为旧对象并没有真正从数据库中删除。您只调用 Clear() 是不够的。尝试这样做:

    [HttpPost("group")]
    public async Task<IActionResult> CreateGroup([FromBody] ProjectGroupModel pro)
    {
        var dbProject = await _context.Project
            .Include(p=>p.ProjectEmployees)
            .FirstAsync(p => p.ProjectId == pro.ProjectId);
    
        foreach (var old in dbProject.ProjectEmployees)
            {
                _context.ProjectEmployee.Remove(old);
            }
    
        dbProject.ProjectEmployees.Clear();
    
        foreach (var emp in pro.ProjectEmployees)
            {
                dbProject.ProjectEmployees.Add(new ProjectEmployee()
                {
                    UserId = emp.UserId
                });
            }
    
        await _context.SaveChangesAsync();
    
        return Ok();
    }
    

    在这里,我假设您在上下文 (_context.ProjectEmployee) 中有正确的设置。如果您的上下文中没有明确地设置此 Set,请添加它。

    【讨论】:

    • 我试过这个。不幸的是,它返回相同的错误。我确信当 pro.ProjectEmployee 为空时,Clear() 工作正常,数据库集合被清除。
    • 内存中的集合被清除。但是,请检查记录是否真的从数据库中删除。你能确认一下吗?
    • 他们是。我认为正如 IvanStoev 所说,这是 ef 更改跟踪的问题。即使行被删除,它也不知道
    • 更改要删除对象的状态
    猜你喜欢
    • 2017-08-01
    • 2023-04-05
    • 2016-08-04
    • 2018-11-24
    • 2021-12-03
    • 2018-02-27
    • 1970-01-01
    • 2020-04-28
    • 1970-01-01
    相关资源
    最近更新 更多