Darren Brook

Darren Brook

  • NA
  • 30
  • 13.2k

Updating Master-Detail / Parent-Child data in MVC

Apr 30 2018 5:40 AM
I have a view with a master-detail style view for an invoice. Master is invoice, detail is invoice lines. I'm trying to get detail data items to save on upon an edit post but the detail data is lost upon reaching the post Edit on the controller. So the master data saves fine but the detail is obviously not saved.

Invoice Class:
  1. public class Invoice  
  2. {  
  3. public Invoice()  
  4. {  
  5.   
  6. }  
  7.   
  8. [Required]  
  9. [Key]  
  10. public int InvoiceID { getset; }  
  11.   
  12. [Required]  
  13. [StringLength(30)]  
  14. [DisplayName("Invoice Number")]  
  15. public string InvoiceNumber { getset; }  
  16.   
  17. [Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]  
  18. [DataType(DataType.Date)]  
  19. [Column(TypeName = "Date")]  
  20.   
  21. [DisplayName("Invoice Date")]  
  22. public DateTime InvoiceDate { getset; }  
  23.   
  24. public List InvoiceLines { getset; }  
  25.   
  26. [ForeignKey("Client")]  
  27. public int OwnerClientIDFK { getset; }  
  28.   
  29. [DisplayName("Client")]  
  30. public Client Client { getset; }  

Invoice Line class:
  1. public class InvoiceLine  
  2. {  
  3. public InvoiceLine()  
  4. {  
  5.   
  6. }  
  7.   
  8. [Key]  
  9. [Required]  
  10. public int InvoiceLineId { getset; }  
  11.   
  12. [Required]  
  13. [StringLength(255)]  
  14. [DisplayName("Item")]  
  15. public string ItemName { getset; }  
  16.   
  17. [DisplayName("Description")]  
  18. public string ItemDescription { getset; }  
  19.   
  20. [Required]  
  21. public int Quantity { getset; }  
  22.   
  23. [Required]  
  24. [DisplayFormat(DataFormatString = "{0:C}", ApplyFormatInEditMode = true)]  
  25. public decimal Value { getset; }  
  26.   
  27. [ForeignKey("ParentInvoice")]  
  28. public int InvoiceID { getset; }  
  29.   
  30. public Invoice ParentInvoice { getset; }  
  31.   
  32. }  
Controller Edit (get):
  1. public ActionResult Edit(int? id)  
  2. {  
  3. if (id == null)  
  4. {  
  5. return new HttpStatusCodeResult(HttpStatusCode.BadRequest);  
  6. }  
  7. // Invoice invoice = db.Invoices.Find(id);  
  8. Invoice invoice = db.Invoices.Include(i => i.InvoiceLines)  
  9. .Include(i => i.Client)  
  10. .Where(c => c.InvoiceID == id).FirstOrDefault();  
  11.   
  12. if (invoice == null)  
  13. {  
  14. return HttpNotFound();  
  15. }  
  16. ViewBag.OwnerClientIDFK = new SelectList(db.Clients, "ClientId""CompanyName", invoice.OwnerClientIDFK);  
  17. return View(invoice);  
  18. }  
Controller Edit (post):
  1. public ActionResult Edit([Bind(Include = "InvoiceID,InvoiceNumber,InvoiceDate,OwnerClientIDFK")] Invoice invoice)  
  2. {  
  3. if (ModelState.IsValid)  
  4. {  
  5. db.Entry(invoice).State = EntityState.Modified;  
  6.   
  7. foreach (var invLine in invoice.InvoiceLines)  
  8. {  
  9. db.Entry(invLine).State = EntityState.Modified;  
  10. }  
  11.   
  12. db.SaveChanges();  
  13. return RedirectToAction("Index");  
  14. }  
  15. ViewBag.OwnerClientIDFK = new SelectList(db.Clients, "ClientId""CompanyName", invoice.OwnerClientIDFK);  
  16. return View(invoice);  
  17. }  
So in the above, when it reaches the foreach, it throws an exception because InvoiceLines is null.

View:
  1. @model DemoApp.Entities.Invoice    
  2.  @{    
  3. ViewBag.Title = "Edit";    
  4. }    
  5.  <h2>Edit</h2>    
  6. @using (Html.BeginForm())    
  7. {    
  8. @Html.AntiForgeryToken()    
  9.     
  10. <div class="form-horizontal">    
  11. <h4>Invoice</h4>    
  12. <hr />    
  13. @Html.ValidationSummary(true, "", new { @class = "text-danger" })    
  14. @Html.HiddenFor(model => model.InvoiceID)    
  15.     
  16. <div class="form-group">    
  17. @Html.LabelFor(model => model.InvoiceNumber, htmlAttributes: new { @class = "control-label col-md-2" })    
  18. <div class="col-md-10">    
  19. @Html.EditorFor(model => model.InvoiceNumber, new { htmlAttributes = new { @class = "form-control" } })    
  20. @Html.ValidationMessageFor(model => model.InvoiceNumber, "", new { @class = "text-danger" })    
  21. </div>    
  22. </div>    
  23.  <div class="form-group">    
  24. @Html.LabelFor(model => model.InvoiceDate, htmlAttributes: new { @class = "control-label col-md-2" })    
  25. <div class="col-md-10">    
  26. @Html.EditorFor(model => model.InvoiceDate, new { htmlAttributes = new { @class = "form-control" } })    
  27. @Html.ValidationMessageFor(model => model.InvoiceDate, "", new { @class = "text-danger" })    
  28. </div>    
  29. </div>    
  30.  <div class="form-group">    
  31. @Html.LabelFor(model => model.OwnerClientIDFK, "OwnerClientIDFK", htmlAttributes: new { @class = "control-label col-md-2" })    
  32. <div class="col-md-10">    
  33. @Html.DropDownList("OwnerClientIDFK", null, htmlAttributes: new { @class = "form-control" })    
  34. @Html.ValidationMessageFor(model => model.OwnerClientIDFK, "", new { @class = "text-danger" })    
  35. </div>    
  36. </div>    
  37.     
  38. <div class="form-group">    
  39. <h2>Invoice Lines</h2>    
  40. <hr />    
  41. @Html.ValidationSummary(true, "", new { @class = "text-danger" })    
  42.     
  43. <div class="row">    
  44. <div class="col-md-8">    
  45. <table class="table">    
  46. <thead>    
  47. <tr>    
  48. <th>Item</th>    
  49. <th>Description</th>    
  50. <th>Qty</th>    
  51. <th>Unit Value</th>    
  52. </tr>    
  53. </thead>    
  54. <tbody>    
  55. @for (int i = 0; i < Model.InvoiceLines.Count; i++)    
  56. {    
  57. <tr>    
  58. <td>@Html.EditorFor(x => x.InvoiceLines[i].ItemName, new { htmlAttributes = new { @class = "form-control" } })</td>    
  59. </tr>    
  60. }    
  61. </tbody>    
  62.  </table>    
  63. </div>    
  64. </div>    
  65.  </div>    
  66.  <div class="form-group">    
  67. <div class="col-md-offset-2 col-md-10">    
  68. <input type="submit" value="Save" class="btn btn-default" />    
  69. </div>    
  70. </div>    
  71.     
  72. </div>    
  73. }    
  74.  <div>    
  75. @Html.ActionLink("Back to List", "Index")    
  76. </div>    
  77.  @section Scripts {    
  78. @Scripts.Render("~/bundles/jqueryval")    
  79. }    
Any help appreciated. Thanks in advance.

Answers (5)