【问题标题】:Trying to use AutoMapper for model with child collections, getting null error in Asp.Net MVC 3尝试将 AutoMapper 用于具有子集合的模型,在 Asp.Net MVC 3 中出现空错误
【发布时间】:2011-02-27 17:05:45
【问题描述】:

我是 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 编辑方法:

    public ActionResult Edit(int id)
    {
        Consultant consultant = _repository.GetConsultant(id);
        ConsultantViewModel vm = Mapper.Map<Consultant, ConsultantViewModel>(consultant);
        return View(vm);
    }

还有 POST 编辑方法:

    [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 时,它似乎工作正常(使用其最简单的形式,没有解析器或任何东西,只是将 Consultant 映射到 ConsultantViewModel),包括子集合和所有。此外,还有 UserName 属性。现在,在视图中我没有 UserName 字段,因为它总是由当前用户 (User.Identity.Name) 自动填写。但是当我取回 vm 时,UserName 属性为空,大概是因为视图中没有它的字段。

我怀疑即使我在其中为 UserName 放置了一个隐藏字段,某些集合也会导致相同的错误,因为顾问可以选择填写语言等...所以即使 ViewModel 有一个实例化列表对于进入视图的每个子集合(计数为 0),它们返回一个空值。

我该如何解决这个问题?我不想强制用户在所有子集合中填写值。我的意思是,我总是可以为 Name 属性创建一个带有空字符串的 Language 对象,但这意味着不必要的额外代码,而我真正想要的只是让子集合(和 UserName)以它们进入的方式返回视图 - 填写了用户名,并实例化了子集合,但如果用户没有添加任何项目,则计数为 0。

更新:

我不知道,我想我以某种方式误解了 AutoMapper...我发现实际上子集合在映射方面并不是真正的问题,它可以很好地将其映射回 Consultant 对象.但是...我还需要将 id 放回 Consultant 对象中,因为 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 中的填充值返回到 Consultant 对象并将其保存到数据库中???

更新 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:

嗯,没完没了的故事...我在 AutoMapper 的 CreateMap 方法中找到了一些有关使用 UseDestinationValue 的信息。所以我尝试了,嗯,这实际上让我更进一步。但是......现在我在 SaveChanges() (在 EF 模型中)上遇到了一个新异常。现在的例外是:“操作失败:无法更改关系,因为一个或多个外键属性不可为空。”这似乎是一个例外,如果您没有设置级联删除集,则在尝试以一对多关系删除子对象时也会发生这种情况,但这不是我在这里想要做的......

这是更新后的 CreateMap 方法:

Mapper.CreateMap<ConsultantViewModel, Consultant>().ForMember("Id", opts => opts.Ignore()).ForMember(
                x => x.Programs, opts => opts.UseDestinationValue());
            Mapper.CreateMap<Consultant, ConsultantViewModel>();

有什么想法吗?

【问题讨论】:

    标签: asp.net collections asp.net-mvc-3 null automapper


    【解决方案1】:

    还没有得到任何答案,我实际上找到了一种方法来让它发挥作用。感觉还是不太好,因为代码有点冗长……所以如果有人有更好的想法,请提出来!

    我对其进行了更改,以便我现在为子集合中的每种类型都有一个 DTO 对象(在使用 AutoMapper 时可能应该从该对象开始)。例如,我现在有一个 ProgramDTO 类型来映射 Program。

    我尝试简单地使用 Consultant 对象进行映射,希望嵌套集合能够自行解决,但再次出现“EntityCollection 已被初始化”错误。所以凭直觉我尝试了这种方法:

        private Consultant CreateConsultant(ConsultantViewModel vm, Consultant consultant) //Parameter Consultant needed because an object may already exist from Edit method.
        {
    
            Mapper.Map(vm, consultant);
    //To do this I had to add an Ignore in the mapping configuration:
    //Mapper.CreateMap<ConsultantViewModel, Consultant>().ForMember(x => x.Programs, opts => opts.Ignore());
    
            //Delete items "marked for deletion" by removing with jQuery in the View:
            var programs = consultant.Programs.Except(consultant.Programs.Join(vm.Programs, p => p.Id, d => d.Id, (p, d) => p)).ToList();
            Delete(programs);
    
            foreach (var programDto in vm.Programs)
            {
                Program program = consultant.Programs.SingleOrDefault(x => x.Id == programDto.Id);
                if (program == null)
                {
                    program = new Program();
                    consultant.Programs.Add(program);
                }
                program = Mapper.Map(programDto, program);
            }
    
            _repository.Save();
    
            return consultant;
        }
    

    不同之处在于我通过 UpdateModel() 填充了 Consultant 的简单属性,然后遍历 ProgramDTO 的集合并映射每个 Program。

    这行得通,虽然我不太喜欢代码...在我完成此操作后它也打击了我,我还需要能够删除用户“标记为删除”的任何项目可以这么说在视图中。我正在使用一个视图,您可以在其中通过 jQuery 添加和删除文本框等,遵循 Steven Sanderson 的教程。但这使代码更加复杂......无论如何,这是我能想到的最好的,所以如果你可以改进的话,请再次提供任何其他想法!我特别希望有一个解决方案,我不必在 POST 上手动循环遍历集合,而是让 AutoMapper 自己处理嵌套集合,而不会出现上述错误!

    【讨论】:

    • 我同意我不喜欢该解决方案,但它有效,并且不需要我现在完全重做我的数据模型来解决我的问题。我也想出了这个解决方案;来到这里希望有更好的方法......
    猜你喜欢
    • 2012-04-24
    • 2017-06-12
    • 2014-08-17
    • 1970-01-01
    • 1970-01-01
    • 2020-04-02
    • 2022-07-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多