I am completely new to AutoMapper, and I have a view that looks like this:
@using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" })) { @Html.ValidationSummary(true) <fieldset> <legend>Consultant</legend> <div class="editor-label"> @Html.LabelFor(model => model.FirstName) </div> <div class="editor-field"> @Html.EditorFor(model => model.FirstName) @Html.ValidationMessageFor(model => model.FirstName) </div> <div class="editor-label"> @Html.LabelFor(model => model.LastName) </div> <div class="editor-field"> @Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName) </div> <div class="editor-label"> @Html.LabelFor(model => model.Description) </div> <div class="editor-field"> @Html.TextAreaFor(model => model.Description) @Html.ValidationMessageFor(model => model.Description) </div> <div class="editor-label"> Program du behÀrskar: </div> <div> <table id="programEditorRows"> <tr> <th> Program </th> <th> NivÄ </th> </tr> @foreach (var item in Model.Programs) { Html.RenderPartial("ProgramEditorRow", item); } </table> <a href="#" id="addProgram">LÀgg till</a> </div> <div class="editor-label"> SprÄk du behÀrskar: </div> <div> <table id="languageEditorRows"> <tr> <th> SprÄk </th> <th> NivÄ </th> </tr> @foreach (var item in Model.Languages) { Html.RenderPartial("LanguageEditorRow", item); } </table> <a href="#" id="addLanguage">LÀgg till</a> </div> <div> <table id="educationEditorRows"> <tr> <th> Utbildning </th> <th> NivÄ </th> </tr> @foreach (var item in Model.Educations) { Html.RenderPartial("EducationEditorRow", item); } </table> <a href="#" id="addEducation">LÀgg till</a> </div> <div> <table id="workExperienceEditorRows"> <tr> <th> Arbetserfarenhet </th> <th> Startdatum </th> <th> Slutdatum </th> </tr> @foreach (var item in Model.WorkExperiences) { Html.RenderPartial("WorkExperienceEditorRow", item); } </table> <a href="#" id="addWorkExperience">LÀgg till</a> </div> <div> <table id="competenceAreaEditorRows"> <tr> <th> KompetensomrÄde </th> <th> NivÄ </th> </tr> @foreach (var item in Model.CompetenceAreas) { Html.RenderPartial("CompetenceAreaEditorRow", item); } </table> <a href="#" id="addCompetenceArea">LÀgg till</a> </div> <div> <input id="fileInput" name="FileInput" type="file" /> </div> <p> <input type="submit" value="Spara" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div>
Here's the GET Edit method:
public ActionResult Edit(int id) { Consultant consultant = _repository.GetConsultant(id); ConsultantViewModel vm = Mapper.Map<Consultant, ConsultantViewModel>(consultant); return View(vm); }
And the POST Edit method:
[HttpPost] [ValidateInput(false)] //To allow HTML in description box public ActionResult Edit(int id, ConsultantViewModel vm, FormCollection collection) { Consultant consultant = Mapper.Map<ConsultantViewModel, Consultant>(vm); _repository.Save(); return RedirectToAction("Index"); }
Now that AutoMapper creates the ViewModel, it seems to be working fine (using its simplest form, without permission or anything else, just matching the Consultant to the ViewModel consultant), including child collections and all. In addition, there is a UserName property. Now in the view, I do not have a field for UserName, because it is always automatically populated by the current user (User.Identity.Name). But when I go back to vm, the UserName property is null, apparently because the view did not have a field for it.
I suspect that some of the collections will cause the same error, even if I put a hidden field there for UserName, because the Consultant does not have to fill in the languages, etc. Therefore, although the ViewModel has an instance list for each of these child collections included in the view (with the number 0), they are returned with a null value.
How can i solve this? I do not want the user to enter values ââpopulated by all child collections. I mean, I could always create a Language object with an empty string for the Name property, but that would mean unnecessary extra code, and all I really want is to return the child collections (and UserName) to where they went View - with the filled in user name and with the created child collections, but with the number 0 if the user has not added any elements.
UPDATE:
I donât know, I think that I misunderstand AutoMapper ... I found that in fact child collections were not really a problem in relation to mappings, which worked fine with displaying it back to the "Consultant" object, However ... I it was also necessary to return the identifier to the "Consultant" object, because the ViewModel was not there. But even so, when I save to the store, it is not saved. This is where I think I misunderstand AutoMapper - I thought it would somehow populate the Consultant object with the values ââfrom the ViewModel, but I think it makes the consultant variable refer to another object in the Map () statement? Because none of this has survived ...
Here's the POST method changed (which doesn't work):
[HttpPost] [ValidateInput(false)] //To allow HTML in description box public ActionResult Edit(int id, ConsultantViewModel vm, FormCollection collection) { vm.UserName = User.Identity.Name; Consultant consultant = _repository.GetConsultant(id); consultant = Mapper.Map<ConsultantViewModel, Consultant>(vm); consultant.Id = id; _repository.Save(); return RedirectToAction("Index"); }
What am I doing wrong? How to get filled values ââin ViewModel back to the Consultant object and save it in the database ???
UPDATE 2:
Well, too confusing ... Starting with the bit: here's the map creation in Application_Start:
Mapper.CreateMap<ConsultantViewModel, Consultant>().ForMember("Id", opts => opts.Ignore()); Mapper.CreateMap<Consultant, ConsultantViewModel>();
Change methods:
// GET: /Consultant/Edit/5 public ActionResult Edit(int id) { Consultant consultant = _repository.GetConsultant(id); ConsultantViewModel vm = Mapper.Map<Consultant, ConsultantViewModel>(consultant); return View(vm); } // // POST: /Consultant/Edit/5 [HttpPost] [ValidateInput(false)] //To allow HTML in description box public ActionResult Edit(int id, ConsultantViewModel vm, FormCollection collection) { vm.UserName = User.Identity.Name; Consultant consultant = _repository.GetConsultant(id); consultant = Mapper.Map<ConsultantViewModel, Consultant>(vm, consultant); _repository.Save(); return RedirectToAction("Index"); }
This does not work either, but it seems to at least try to update the entity model, because now I get an exception:
EntityCollection cannot be initialized because the relationship manager for the object to which EntityCollection belongs is already bound to an ObjectContext. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of the object graph.
And an example YSOD error code:
Line 698: if ((value != null)) Line 699: { Line 700: ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Program>("ConsultantsModel.ConsultantProgram", "Program", value); Line 701: } Line 702: }
This is almost the same error I received when trying to use the entity object directly as a model, instead of AutoMapper creating a ViewModel. So what am I doing wrong? It drives me crazy...
UPDATE 3:
Well, an endless story ... I found information about using UseDestinationValue in the CreateMap method in AutoMapper. So I tried to do it, and it really put me in order. But ... now I get a new exception in SaveChanges () (in the EF model). An exception is the following: "The operation failed: the relation cannot be changed because one or more properties of the foreign key cannot be reset." This is similar to the exception that also occurs when trying to delete child objects in a one-to-many relationship if you donât have a set of cascading deletes, but thatâs not what I am trying to do here ...
The CreateMap methods are updated here:
Mapper.CreateMap<ConsultantViewModel, Consultant>().ForMember("Id", opts => opts.Ignore()).ForMember( x => x.Programs, opts => opts.UseDestinationValue()); Mapper.CreateMap<Consultant, ConsultantViewModel>();
Any ideas?