實體框架核心 - 更新相關集合

asp.net-core c# entity-framework entity-framework-core

我正在嘗試更新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為空,則添加了來自model的新記錄,但是當dbProject.ProjectEmployees不為空時,我無法設置新記錄:

錯誤:

“無法跟踪實體類型'ProjectEmployee'的實例,因為已經跟踪了具有相同密鑰的此類型的另一個實例。添加新實體時,對於大多數密鑰類型,如果未設置密鑰,將創建唯一的臨時密鑰值(即如果為key屬性分配了其類型的默認值。如果您明確設置新實體的鍵值,請確保它們不會與現有實體或為其他新實體生成的臨時值發生衝突。附加現有實體時,請確保只有一個具有給定鍵值的實體實例附加到上下文。“

我嘗試以數百種方式修復此操作,但總是錯誤的。

一般承認的答案

它遠非完美,但我能夠使其工作的唯一方法是從相應的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();

熱門答案

另一個SO問題相關聯

我可以在這里和你的課程一起回答。

然後使用此擴展我刪除未選中並將新選擇添加到列表中

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


Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow