简体   繁体   中英

Entity Framework Core - update related collection

I'm trying to update collection of ProjectEmployees inside ProjectModel. I want to remove all old values and set new.

My models:

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

It's typical many-to-many relationship.

My controller action:

    [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();
    }

When pro.ProjectEmployees is empty all records from dbProject.ProjectEmployees were removed correctly, also if dbProject.ProjectEmployees is empty new records from model were added, but when dbProject.ProjectEmployees is not empty I can't set new records:

ERROR:

"The instance of entity type 'ProjectEmployee' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (ie if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context."

I tried to repair this action in a hundreds way but always sth is wrong.

linked to another SO question

I can answer it here with your classes.

Then use this extension i made to remove the unselected and add the newly selected to the list

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

using it looks like this

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

It's far from perfect, but the only way I was able to make it work is to remove the items from the corresponding DbSet and call SaveChanges before adding the new ones:

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

I think that the old objects are not really removed from the database. You are only calling Clear() which is not enough. Try doing this:

[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();
}

Here I assume that you have the proper set in your context (_context.ProjectEmployee). If you don't have this Set explicitly in your context just add it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM