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" 将编辑应用于加载的实体。从安全角度来看,这不太可能导致问题。在这种情况下,我可能会建议转向强类型视图,这样您就不必使用字典键搜索表单集合。
我使用 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" 将编辑应用于加载的实体。从安全角度来看,这不太可能导致问题。在这种情况下,我可能会建议转向强类型视图,这样您就不必使用字典键搜索表单集合。