MVC 中的 UpdateModel 创建子列表的 duplicate/orphaned 条记录

UpdateModel in MVC creates duplicate/orphaned records of child list

我使用 MVC 5 和 Entity Framework Code First。

我有一个 Class(学校 class)对象,它附有一个学生列表,我正在尝试更新 class and/or 个学生在 class 中以及我在上下文中调用 SaveChanges() 时得到的是 class 中学生的重复记录。基本上,旧学生列表在数据库中得到 "orphaned",并且一组全新的学生被附加到数据库中编辑的 class。

所以我现在有 20 个学生记录,而不是数据库中的 10 个更正学生记录。 10 个原始(未更正)和 10 个新(更正)的。原来的 10 个已删除 classid 外键,因此它们不再是 class 的一部分。

对 Class 对象的任何更新都可以很好地实现,而无需复制 Class 记录。

我看到一个答案表明,上下文可能不知道学生不是新项目,因此它添加了它们……并从数据库中获取学生,以便上下文了解他们 -但是如果你从数据库中拉取 class 对象的时候学生们也来了,这和直接拉取对象不一样吗?

  public class Class
  {
    [Key]
    public Guid ClassId { get; set; }
    [Display(Name="Class Name")]
    public string ClassName { get; set; }

    public virtual List<Student> Students { get; set; }
    public virtual Teacher Teacher { get; set; }

    public Class()
    {
        ClassId = Guid.NewGuid();
        Students = new List<Student>();
    }

   }

    [HttpPost]
    public ActionResult Edit(Guid id, FormCollection collection)
    {                   
        Class selectedclass = db.Classes.Find(id);

        try
        {                                                
            UpdateModel(selectedclass, collection.ToValueProvider());
            db.SaveChanges();
            return RedirectToAction("Details", new { id = id });
        }
        catch
        {
            return View(selectedclass);
        }
    }

我究竟做错了什么?

我唯一能想到的就是在保存更改之前删除数据库中附加到 class 的所有学生记录,但必须有比那。

db.Students.RemoveRange(db.Classes.Find(id).Students);

所以我尝试将列表中的学生附加到上下文中,甚至将他们的状态更改为已修改,例如:

 selectedclass.Students.ForEach(s => db.Students.Attach(s));
 selectedclass.Students.ForEach(s => db.Entry(s).State = EntityState.Modified);

但运气不好,仍然得到重复项和孤儿项。

尝试直接从数据库中获取学生,但没有成功:

 var ids = selectedclass.Students.Select(s => s.StudentId).ToList();           

 selectedclass.Students = db.Students.Where(s => ids.Contains(s.StudentId)).ToList()

HTML 编辑为学生:

  @for (int i = 0; i < Model.Students.Count; i++)
  {
      <div class="col-md-8">
          @Html.EditorFor(m => m.Students[i], new { htmlAttributes = new { @class = "form-control" }})
      </div>
  }

学生的编辑器模板:

<div class="form-group">
@Html.LabelFor(m => m.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-xs-6 col-md-4">
    @Html.EditorFor(m => m.FirstName, new { htmlAttributes = new { @class = "form-control" }})
</div>
@Html.LabelFor(m => m.LastName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-xs-6 col-md-4">
    @Html.EditorFor(m => m.LastName, new { htmlAttributes = new { @class = "form-control" }})
</div>
</div>

<div class="form-group">
@Html.LabelFor(m => m.UserName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-xs-6 col-md-4">
    @Html.EditorFor(m => m.UserName, new { htmlAttributes = new { @class = "form-control" } })
</div>
@Html.LabelFor(m => m.Password, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-xs-6 col-md-4">
    @Html.EditorFor(m => m.Password, new { htmlAttributes = new { @class = "form-control" }})
</div>
</div>

我认为发生的事情是您正在使用 Class selectedclass = db.Classes.Find(id); 加载实体,但这 不会 自动加载学生集合。

我怀疑正在发生的事情是,由于未加载 Students 集合,很可能 UpdateModel 只是调用 Students setter,替换集合。

由于Student个对象没有被上下文加载,Entity Framework认为他们是新生,并相应地插入他们。参见 Data Points - Why Does Entity Framework Reinsert Existing Objects into My Database?

对此有几个解决方案。您可以尝试在调用 UpdateModel 之前显式加载 Students 集合。或者您可以明确地告诉 entity framework 学生记录存在,方法是明确地将它们附加到上下文。

但是,一般来说,我尽量避免处理断开连接的实体 - 在 Entity Framework 断开连接的实体处理中有很多 "gotchas"。

此外,直接绑定到 Entity Framework 映射 类 时要小心,因为这很容易导致 overposting/underposting 安全漏洞。参见 ASP.NET MVC – Think Before You Bind

更新 我开始怀疑问题在于 html 如何指定表单字段。参见 Model Binding To A List

您的另一个选择当然是而不是以这种方式使用UpdateModel。相反,只需加载实体,然后 "manually" 将编辑应用于加载的实体。从安全角度来看,这不太可能导致问题。在这种情况下,我可能会建议转向强类型视图,这样您就不必使用字典键搜索表单集合。