Я совершенно новичок в AutoMapper, и у меня есть вид, который выглядит следующим образом:
@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>
Вот метод GET Edit:
public ActionResult Edit(int id)
{
Consultant consultant = _repository.GetConsultant(id);
ConsultantViewModel vm = Mapper.Map<Consultant, ConsultantViewModel>(consultant);
return View(vm);
}
И метод POST Edit:
[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");
}
Теперь, когда AutoMapper создает ViewModel, кажется, что он работает нормально (используя простейшую форму, без распознавателя или чего-либо еще, просто сопоставляя Консультанта с ConsultantViewModel), включая дочерние коллекции и все. Также есть свойство UserName. Теперь в представлении у меня нет поля для UserName, потому что оно всегда автоматически заполняется текущим пользователем (User.Identity.Name). Но когда я возвращаю vm, свойство UserName имеет значение null, предположительно потому, что в представлении для него не было поля.
Я подозреваю, что некоторые коллекции вызовут ту же ошибку, даже если я добавлю туда скрытое поле для UserName, потому что Консультант необязательно заполнять языки и т. Д. Так что, хотя ViewModel имеет созданный экземпляр списка для каждой из этих дочерних коллекций, поступающих в представление (со счетчиком 0), вместо этого они возвращаются с нулевым значением.
Как мне решить это? Я не хочу, чтобы пользователь заполнял значения во всех дочерних коллекциях. Я имею в виду, что я всегда мог создать объект Language с пустой строкой для свойства Name, но это означало бы ненужный дополнительный код, и все, что я действительно хочу, это вернуть дочерние коллекции (и UserName) обратно так, как они вошли в Представление - с заполненным именем пользователя и экземплярами дочерних коллекций, но со счетчиком 0, если пользователь не добавил ни одного элемента.
UPDATE:
Не знаю, мне кажется, что я как-то неправильно понимаю AutoMapper ... Я обнаружил, что на самом деле дочерние коллекции на самом деле не были проблемой с отображением, это прекрасно работало, отображая его обратно в объект Консультанта. , Однако ... Мне также нужно было вернуть идентификатор обратно в объект Консультанта, потому что у ViewModel его не было. Но даже в этом случае, когда я сохраняю данные в хранилище, они не сохраняются. Здесь я думаю, что неправильно понимаю AutoMapper - я думал, что это каким-то образом заполнит объект Консультанта значениями из ViewModel, но я предполагаю, что это заставит переменную консультанта ссылаться на другой объект в выражении Map ()? Потому что ничего из этого не сохранилось ...
Вот модифицированный метод POST (который не работает):
[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");
}
Что я делаю не так? Как мне вернуть заполненные значения в ViewModel обратно в объект Консультанта и сохранить его в базе данных ???
ОБНОВЛЕНИЕ 2:
Ладно, в замешательстве ... Начнем сначала: создание карты в Application_Start:
Mapper.CreateMap<ConsultantViewModel, Consultant>().ForMember("Id", opts => opts.Ignore());
Mapper.CreateMap<Consultant, ConsultantViewModel>();
Методы редактирования:
// 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");
}
Это тоже не работает, но, по крайней мере, пытается обновить модель сущности, потому что теперь я получаю исключение:
EntityCollection не удалось инициализировать, поскольку диспетчер отношений для объекта, которому принадлежит EntityCollection, уже присоединен к ObjectContext. Метод InitializeRelatedCollection следует вызывать только для инициализации новой коллекции EntityCollection во время десериализации графа объектов.
И пример кода ошибки YSOD:
Line 698: if ((value != null))
Line 699: {
Line 700: ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<Program>("ConsultantsModel.ConsultantProgram", "Program", value);
Line 701: }
Line 702: }
Это почти та же ошибка, что и при попытке использовать объект-сущность непосредственно в качестве модели, вместо того, чтобы AutoMapper создавал ViewModel. Так что я делаю не так? Это сводит меня с ума ...
ОБНОВЛЕНИЕ 3:
Ну, бесконечная история ... Я нашел некоторую информацию об использовании UseDestinationValue для метода CreateMap в AutoMapper.Так что я попробовал это, и это действительно помогло мне продвинуться дальше.Но ... теперь я получаю новое исключение для SaveChanges () (в модели EF).Теперь исключение: «Операция завершилась неудачно: отношение не может быть изменено, поскольку одно или несколько свойств внешнего ключа не могут иметь значение NULL».Похоже, это исключение, которое также возникает при попытке удалить дочерние объекты в отношении «один ко многим», если у вас не установлено каскадное удаление, но я здесь не пытаюсь это сделать ...
Вот обновленные методы CreateMap:
Mapper.CreateMap<ConsultantViewModel, Consultant>().ForMember("Id", opts => opts.Ignore()).ForMember(
x => x.Programs, opts => opts.UseDestinationValue());
Mapper.CreateMap<Consultant, ConsultantViewModel>();
Есть идеи?