1

I fail to update a many-to-many relationship. When I debug "Response.Write(teacher.skills);", the values seem right but the database is not updating the object.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "id,lastname,firstname,image,campusId,skillIds")] Teacher teacher)
{
    if (ModelState.IsValid)
    {
        if (teacher.skillIds != null)
        {
            teacher.skills = (from t in db.Skills.ToList() where teacher.skillIds.Contains(t.id) select t).ToList();
        }

        Response.Write(teacher.skills);

        // 1st attempt -->
        //db.Teachers.Attach(teacher);
        //db.Entry(teacher).State = EntityState.Modified;

        // 2nd attempt -->
        UpdateModel(teacher);


        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(teacher);
}
4
  • You need to get the object (teacher) from the database first. Then, you update the fields you want and call db.Entry(teacher).State = EntityState.Modified, and SaveChanges. Commented Dec 22, 2015 at 11:15
  • Thx @jpgrassi, now I can add the items but I cannot remove them. I assume that i need to remove them manually from the skills array? Commented Dec 22, 2015 at 13:55
  • I didn't get the items part. Can you update your question with this? Commented Dec 22, 2015 at 14:00
  • I had to use oldTeacher.skills.Clear(); Now it works perfectly! Thx! Commented Dec 22, 2015 at 14:01

1 Answer 1

3

Don't clear the skills. What that's actually doing is telling Entity Framework to remove all existing relationships from the join table and then add entirely new relationships back.

When updating M2M relationships, you can't just set the property. You need to first remove any deselected items, and then add new items, while leaving existing items alone:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, TeacherViewModel model)
{
    var teacher = db.Teachers.Find(id);
    if (teacher == null)
    {
        return new HttpNotFoundResult();
    }

    if (ModelState.IsValid)
    {
        teacher.firstname = model.lastname;
        teacher.lastname = model.lastname;
        teacher.image = model.image;
        teacher.campusId = model.campusId;

        // Remove deselected skills
        teacher.skills.Where(m => !model.skillIds.Contains(m.Id))
            .ToList().ForEach(skill => teacher.skills.Remove(skill));

        // Add new skills
        var existingSkillIds = teacher.skills.Select(m => m.Id);
        db.Skills.Where(m => model.skillIds.Exclude(existingSkillIds).Contains(m.Id))
            .ToList().ForEach(skill => teacher.skills.Add(skill));

        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(model);
}

As you can see, I've added a view model, which you should always be using anytime you feel the need to add Bind. Just don't use Bind ever. I'm also passing the teach id as part of the URL. You should not be posting the id ever. Never include anything in your post body that you don't want to be potentially modified. I use that id to select the teacher fresh from the database. You should never directly save an entity that was posted. Then, there's the new code which removes deselected skills and adds newly selected skills.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.