1

I am creating an application where a Note can be created and one to many Parts can be added to the note. (The application is for a tractor salvage yard where customers call for tractor parts). I know similar questions have been asked before. But I couldn't find anything very relevant to my situation with EF and all.

I am having a lot of difficulty with creating/editing a Note with its Parts in one view. I want to focus on editing for this question, though.

I have two simple CLR classes with a relation.

public class Note
    {
        public int ID { get; set; }
        public string CustomerName { get; set; }
        public string CustomerPhone { get; set; }
        public DateTime DateCreated { get; set; }
        public DateTime DateUpdated { get; set; }
        public string CreatedBy { get; set; }
        public string AssignedTo { get; set; }
        public virtual ICollection<Part> Parts { get; set; }
    }
public class Part
    {
         
         public int PartID { get; set; }
         public string PartNumber { get; set; }
         public string Description { get; set; }
         public int NoteID { get; set; }
         public virtual Note Note { get; set; }
    }

And the DbContext:

public class CallNoteContext : DbContext
    {
        public CallNoteContext() { }
        public DbSet<Note> Notes { get; set; }
        public DbSet<Part> Parts { get; set; }
    }

My problem is binding the data from both entities to the edit view, accessing the data in the view for editing and saving the note and and multiple parts to the database in the httppost action.

I have tried a lot of things, but after reading a lot of articles, I keep coming back to this for the controller and view. To me it seems like this should work. But obviously I am missing something.

Here is the edit and post actions from my controller.

private CallNoteContext db = new CallNoteContext();

        // GET: Note/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Note note = db.Notes.Find(id);
            var model = new Note()
            {
                CustomerName = note.CustomerName,
                CustomerPhone = note.CustomerPhone,
                DateCreated = note.DateCreated,
                DateUpdated = note.DateUpdated,
                CreatedBy = note.CreatedBy,
                AssignedTo = note.AssignedTo,
                Parts = note.Parts 
            };
            if (note == null)
            {
                return HttpNotFound();
            }
            return View(model);
        }

        // POST: Note/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "ID,CustomerName,CustomerPhone,DateCreated,DateUpdated,CreatedBy,AssignedTo,Parts")] Note note)
        {
            if (ModelState.IsValid)
            {
                foreach(var p in note.Parts)
                {
                    db.Entry(p).State = EntityState.Modified;
                    db.SaveChanges();
                }
                db.Entry(note).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(note);
        }

When I try to make editors for p.PartNumber and p.Description in my view below, it breaks with the exception that it can't find these properties. I have a feeling that I am doing something wrong in the "get" action of the controller. But I am having a hard time figuring out what is wrong.

By the way, IntelliSense is saying No Issues Found for the controller.

Here is my Edit view.

@model CallNote.Models.Note

<head>
    <script src="~/Scripts/jquery-3.4.1.js" type="text/javascript"></script>
</head>

<h2>Edit</h2>

@using (Html.BeginForm())
{
@Html.HiddenFor(model => model.ID)

<div class="form-group">
        @Html.LabelFor(model => model.CustomerName, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.CustomerName, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.CustomerPhone, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.CustomerPhone, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.CustomerPhone, "", new { @class = "text-danger" })
        </div>
    </div>
@*There are editors here for all of the properties, but I didn't list them to save space.*@



@*The app always breaks when it gets to this foreach because it says it can't find p.PartNumber. What is wrong?

 @foreach (var p in Model.Parts)
    {
        <div>
@*I also tried just using p.PartNumber, but it says p doesn't exist in current context.
            @Html.EditorFor(p => p.PartNumber)
            @Html.EditorFor(p => p.Description)
        </div>
    }

    <div id="partInfo" style="display:none">
        @Html.EditorFor(p => p.PartNumber)
        @Html.EditorFor(p => p.Description)
    </div>

    <div id="btnWrapper">
        <input id="btnAddPart" type="button" value="Add Part" />
    </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>
}
@*The script below works, allowing you to add part editors*@
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
<script>
    $(document).ready(function () {
        $("#btnAddPart").click(function () {
            var partinfo = $("#partInfo").html();
            $("#partInfo").append(partinfo);
        });
    });
</script>

Also I am unsure if the httppost action will work. I have not been able to try it yet as I cannot get the Edit view to even load yet. So if you have any suggestions for that too, let me know.

I am just getting started with MVC, so a detailed answer would be super!

2
  • why you have not tried to use MODEL class instead of entity ? Commented Aug 19, 2021 at 17:30
  • Can you explain? I have 2 classes in my model. It is linked to the database with entity. Is there other options? Commented Aug 19, 2021 at 19:31

1 Answer 1

1

you have to include Parts in the Note

....
  Note note = db.Notes
                .Include(i=> i.Parts)
                .FirstOrDefault(i=>i.ID==id);
 if (note == null)
   {
     return HttpNotFound();
    }
.....

and since you are using editor, replace foreach loop by for loop

@if( Model.Parts!=null && Model.Parts.Count >0)
{
@for (var i=0; i< Model.Parts.Count; i++)
{

 <div id="partInfo" style="display:none">
        @Html.EditorFor(model => model.Parts[i].PartNumber)
        @Html.EditorFor(model => model.Parts[i].Description)
    </div>
   ...... and so on for all properties
}
}

and remove bind from the action

[HttpPost]
[ValidateAntiForgeryToken]
 public ActionResult Edit(Note note)
 {
  if (!ModelState.IsValid)  return View(note);
            
var existedNote = db.Notes
  .Include(i=> i.Parts)
    .FirstOrDefault(i=>i.ID==note.ID);

  if(existedNote!=null) 
 {
  db.Entry(existedNote).CurrentValues.SetValues(note);
 

if(note.Parts!=null && note.Parts.Count > 0)
{
   foreach( var part in note.Parts)
   {
     var existingPart = existingNote.Parts.FirstOrDefault(p => p.PartID == part.PartID);

            if (existingPart == null)
            {
                existingNote.Parts.Add(part);
            }
            else
            {
                context.Entry(existingPart).CurrentValues.SetValues(part);
            }
     }
  }
     
    db.SaveChanges();
 }
    return RedirectToAction("Index");
 }
 return View(note);
}
````
Sign up to request clarification or add additional context in comments.

14 Comments

Thank you for answering @Serge! Now it breaks with the exception: Cannot apply indexing with [] to an expression of type 'ICollection<Part>'. Would it work to make it an IList instead of ICollection?
@cytech04 You need List<> for forloop. Make Parts as List<Part>
The edit view works! But now there is a problem posting.
I get this error: 'A referential integrity constraint violation occurred: The property value(s) of 'Note.ID' on one end of a relationship do not match the property value(s) of 'Part.NoteID' on the other end.'
Thanks. It saves everything for the Note. But all of the part info just goes back to what it was before. Do I need to do a db entry for the Part objects?
|

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.