Firstly, to save yourself a lot of headaches, do not pass Entity objects to your view. Yes, a lot of examples will be out there demonstrating it, but it exposes data you don't want client users to know, it results in more data getting loaded from the database and going across the wire than is needed, and it promotes lazy, non-validated saving. (I.e. accept an entity back, attach it to a context, set state to Modified, and SaveChanges) Trust absolutely nothing coming back from the client without checking it thoroughly first.
You will probably need to split your question up and clarify points with examples of what you've tried vs. what you expect because depending on what you want to do, there are various approaches to take. For instance, you can "add" a child entity (create a new child and associate it to the entity) or you can associate an existing entity with another. For example: For a Movie I might want to "Add" a review which will involve letting the user enter some fields and save against a movie. Alternatively I might have created a reservation and want to "Associate" a movie. This involves finding the appropriate movie instance as opposed to creating it.
At a high level, with web applications, the lifecycle for editing an entity would follow something like this:
- Load entity from context.
- Map to a view model exposing only the data needed for the view.
- If this is something like a reservation that has movies, then it would include a collection of MovieViewModels which might contain the MovieId, Name, and any other details that might be displayed.
- Return view model to view.
for association scenarios, such as when we create a new reservation and want to associate movies, the view will also have calls via Fetch/Ajax etc. to search for matches or list available records. Again, these calls would follow steps like the above. The client application would associate the selected movies etc. to the reservation, this could include adding or removing associations to movies. When saving we would be breaking the operation down into specific actions such as UpdateReservation vs. CancelReservation for example.
Updating a reservation would accept the view model (or another purpose-built view model for the update) and then:
- Verify the view model applies to the user currently logged in.
- Validate the data passed for correctness.
- If anything is wrong with the data, log details and terminate the session. (Suspected user tampering/hacking)
- Load the entity from the Context via ID, including child references that will be potentially updated. (fast)
- Verify the data isn't stale (check a timestamp/row version recorded in the view model from when the entity is read to see that it matches the current entity timestamp. If it doesn't it means someone else modified this record since this session read it so you may want to inform the user to review and re-apply the change)
To update child collections you can take the view model collection of children or child IDs to get a list of IDs to remain associated. This may include new references, and may represent removing existing references. So we split off those scenarios:
var childIds = viewModel.Children.Select(x => x.ChildId).ToList(); // we only care about the IDs.
var existingChildIds = entity.Children.Select(x => x.ChildId).ToList();
var childIdsToAdd = childIds.Except(existingChildIds);
var childEntitiesToAdd = context.Children.Where(x => childIdsToAdd.Contains(x.ChildId).ToList();
var childEntitiesToRemove = entity.Children.Where(x => !childIds.Contains(x.ChildId));
foreach(var child in childEntitiesToRemove)
entity.Children.Remove(child);
entity.Children.AddRange(childEntitiesToAdd);
When it comes to adding and removing new child entities, we can treat those as separate actions. Often the act of creating a new entity involves recording more data than we usually view at the parent level, so this is typically done on a separate view such as a pop-up dialog or a different page. This can use a separate action on the view to create the new child and associate it to the parent by ID. The original view model for the parent can be updated or refreshed from the server when the operation is completed.
So a controller action to do something like record a review might look like:
[Post]
public async Task<JsonResult> AddMovieReview(int movieId, string comment, int rating)
{
// validate movie exists, and rating is valid, i.e. 1-5
// create new Review entity using comment and rating.
// load movie from context using movie ID.
// add review to the movie.
// save changes.
// return a view model with a result (success/failure) and perhaps the new avg. rating for the movie.
}
Hopefully this gives you some ideas to research and try out, but otherwise create a question with some code on what you've tried and what you'd expect to see to work through specifics.