Question:
Help to understand the problem.
I am trying to implement using entity framework code first to update a record in a table that is linked to another table with a many-to-one relationship.
Here are the data models
public class Defect
{
public int id { get; set; }
public string index { get; set; }
public string specification { get; set; }
public int amountOfBalls { get; set; }
public virtual DefectCategory defectCategory { get; set; }
}
public class DefectCategory
{
public int id { get; set; }
public string index { get; set; }
public string specification { get; set; }
public virtual ICollection<Defect> defects { get; set; }
}
Here is the controller's GET method
public ActionResult DefectEdit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Defect defect = db.Defects.Find(id);
if (defect == null)
{
return HttpNotFound();
}
ViewBag.defectCategory_id = new SelectList(db.DefectCategories, "id", "specification", defect.defectCategory.id);
return View(defect);
}
Here is the controller's POST method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult DefectEdit(Defect defect, int defectCategory_id)
{
if (ModelState.IsValid)
{
defect.defectCategory = db.DefectCategories.Find(defectCategory_id);
db.Entry(defect).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("ListDefects");
}
return View(defect);
}
Here is a view
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
<h4>Відредагувати існуючий дефект</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.id)
<div class="form-group">
@Html.LabelFor(model => model.index, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.index, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.index, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.defectCategory, "defectCategory_id", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("defectCategory_id", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.defectCategory, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.specification, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.specification, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.specification, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.amountOfBalls, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.amountOfBalls, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.amountOfBalls, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Зберегти" class="btn btn-default" />
</div>
</div>
</div>
}
The problem is that all changed fields are updated in the database except for the defectCategory_id which is generated by the framework and I don't have access to it directly (or do I have it?) In one word, help me figure out how to correctly implement a one-to-many relationship in the controller and view
Answer:
public class Defect
{
public int Id { get; set; }
public string Index { get; set; }
public string Specification { get; set; }
public int AmountOfBalls { get; set; }
//Предпочитаю создавать поля сам
public int DefectCategoryId { get; set; }
[ForeignKey("DefectCategoryId")] // указываю какое поле будет использоваться для связи
public virtual DefectCategory DefectCategory { get; set; }
}
public class DefectCategory
{
public int Id { get; set; }
public string Index { get; set; }
public string Specification { get; set; }
public virtual ICollection<Defect> Defects { get; set; }
}
the Get and Post methods remain the same. Below is the full controller code:
public class HomeController : Controller
{
public List<DefectCategory> DefectCategories = new List<DefectCategory>()
{
new DefectCategory(){Id = 1,Index="A", Specification = "дефекти паперу"},
new DefectCategory(){Id = 2, Index="B", Specification="дефекти тексту"}
};
public List<Defect> Defects = new List<Defect>
{
new Defect(){Id=1, Index="A1", Specification="тест", AmountOfBalls = 10, DefectCategoryId = 1}
};
public ActionResult ListDefects()
{
return View(Defects);
}
public ActionResult DefectEdit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var defect = Defects.Find(x=>x.Id==id);
if (defect == null)
{
return HttpNotFound();
}
ViewBag.DefectCategories = new SelectList(DefectCategories, "id", "specification");
return View(defect);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult DefectEdit(Defect defect)
{
if (ModelState.IsValid)
{
//defect.defectCategory = db.DefectCategories.Find(defectCategory_id);
return RedirectToAction("ListDefects");
}
return View(defect);
}
edit view
@model WebApplication1.Controllers.Defect
@{
ViewBag.Title = "DefectEdit";
}
<h2>DefectEdit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Defect</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.Id)
<div class="form-group">
@Html.LabelFor(model => model.Index, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Index, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Index, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Specification, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Specification, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Specification, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.AmountOfBalls, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.AmountOfBalls, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.AmountOfBalls, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.DefectCategoryId, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(model => model.DefectCategoryId, (SelectList)ViewBag.DefectCategories, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.DefectCategoryId, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
if you put a breakpoint in the post method, then we will see that defect.DefectCategoryId contains the key value that We selected