Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a table in my database with a one to many relationship to another table, which has a relationship to a third table:

ParentObject

  • ID
  • Name
  • Description

ChildObject

  • ID
  • Name
  • Description
  • ParentObjectID
  • AnotherObjectID

AnotherObject

  • ID
  • Name

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 general 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 a 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, the code expected certain properties to exist) 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.

like image 725
jslatts Avatar asked Apr 02 '10 22:04

jslatts


1 Answers

Just briefly looking at the error mentioning FK_ChildObject_AnotherObject... are you sure everything is wired up correctly in your EF data model concerning AnotherObject?

In your question you only list two tables, but this error is indicating that the ChildObject is participating in a 1 to * relationship with AnotherObject and there is not one present in the entity on save causing an error.

Try removing AnotherObject from the situation in order to test any assumed problems between ParentObject and ChildObject, or try changing the FK_ChildObject_AnotherObject relationship to 0..1 to * briefly for testing.

like image 104
user308105 Avatar answered Sep 29 '22 20:09

user308105