Correct way to edit and update complex viewmodel objects using asp.net-mvc2 and entity framework

Posted by jslatts on Stack Overflow See other posts from Stack Overflow or by jslatts
Published on 2010-04-02T22:12:16Z Indexed on 2010/04/02 22:13 UTC
Read the original article Hit count: 431

Filed under:
|
|

I have a table in my database with a one to many relationship to another table:

ParentObject

  • ID
  • Name
  • Description

ChildObject

  • ID
  • Name
  • Description
  • ParentObjectID
  • AnotherObjectID

The objects are mapped into Entity Framework and exposed through a data access class.

It seemed like ViewModels are recommended when the data to be displayed greatly differs from the domain object, so I created a ViewModel as follows:

public class ViewModel {
    public IList<ParentObject> ParentObjects { get; set; }
    public ParentObject selectedObject { get; set; }
    public IList<ChildObject> ChildObjects { get; set; }
}

I have a view that displays a list of ParentObjects and when clicked will allow a ChildObject to be modified saved.

<% using (Html.BeginForm()) { %> 
<table>
    <% foreach (var parent in Model.ParentObjects) { %>
    <tr>
        <td>
            ObjectID [<%= Html.Encode(parent.ID)%>]
        </td>
        <td>
            <%= Html.Encode(parent.Name)%>
        </td>
        <td>
            <%= Html.Encode(parent.Description)%>
        </td>
    </tr>
    <% } %>
</table>
<% if (Model.ParentObject != null) { %>
<div>
    Name:<br />
    <%= Html.TextBoxFor(model => model.ParentObject.Name) %>
    <%= Html.ValidationMessageFor(model => model.ParentObject.Name, "*")%>
</div>
<div>
    Description:<br />
    <%= Html.TextBoxFor(model => model.ParentObject.Description) %>
    <%= Html.ValidationMessageFor(model => model.ParentObject.Description, "*")%>
</div>
<div>
    Child Objects
</div>
<% for (int i = 0; i < Model.ParentObject.ChildObjects.Count(); i++) { %>
    <div>
        <%= Html.DisplayTextFor(sd => sd.ChildObjects[i].Name) %>
    </div>
    <div>
        <%= Html.HiddenFor(sd => sd.ChildObjects[i].ID )%>
        <%= Html.TextBoxFor( sd => sd.ChildObjects[i].Description) %>
        <%= Html.ValidationMessageFor(sd => sd.ChildObjects[i].Description, "*") %>
    </div>
    <% }
}
} %>  

This all works fine. My question is around the best way to update the EF objects and persist the changes back to the database. I initially tried:

[HttpPost]
    public ActionResult Edit(ViewModel viewModel) {
        ParentObject parent = myRepository.GetParentObjectByID(viewModel.SelectedObject.ID);

        if ((!ModelState.IsValid)
            || !TryUpdateModel(parent, "SelectedObject", new[] { "Name", "Description" })) {
            || !TryUpdateModel(parent.ChildObjects, "ChildObjects", new[] { "Name", "Description" })) {


            //Code to handle failure and return the current model snipped

            return View(viewModel);
        }

        myRepository.Save();

        return RedirectToAction("Edit");
    }

When I try to save a change to the child object, I get this exception: Entities in 'MyEntities.ChildObject' participate in the 'FK_ChildObject_AnotherObject' relationship. 0 related 'AnotherObject' were found. 1 'AnotherObject' is expected.

Investigation on StackOverflow and generally googling led me to this blog post that seems to describe my problem: TryUpdateModel() does not correctly handle nested collections. Apparently, (and stepping through the debugger confirms this) it creates a new ChildObject instead of associating with the EF objects from my instantiated context.

My hacky work around is this:

if (viewModel.ChildObjects.Count > 0) {
    foreach (ChildObject modelChildObject in viewModel.ChildObjects) {
        ChildObject childToUpdate = ParentObject.ChildObject.Where(a => a.ID == modelChildObject.ID).First();
        childToUpdate.Name = modelChildObject.Name;
    }
}

This seems to work fine. My question to you good folks: Is there correct way to do this? I tried following the suggestion for making a custom model binder per the blog link I posted above but it didn't work (there was an issue with reflection) and I needed to get something going ASAP.

PS - I tried to cleanup the code to hide specific information, so beware I may have hosed something up. I mainly just want to know if other people have solved this problem.

Thanks!

© Stack Overflow or respective owner

Related posts about c#

Related posts about asp.net-mvc2