为什么DbContext.Entry(IEnumerable <medicalproduct>)。State在我的代码中产生ArguementNullException?</medicalproduct>

时间:2013-10-01 18:01:21

标签: c# asp.net-mvc entity-framework asp.net-mvc-4 razor

在我的MedicalProductController中,我尝试让我的Edit操作能够在一个页面上编辑多个对象。为此,我计划使用HTTPPOST编辑操作方法接收IEnumerable<MedicalProduct>,而不是脚手架为我设置的MedicalProduct

当我点击“保存”提交一些更改时,我在行ArguementNullException上未处理_db.Entry(productList).State = EntityState.Modified;,我不明白为什么它为空。

MedicalProductController:

public class MedicalProductController : Controller
{
    private MvcMedicalStoreDb _db = new MvcMedicalStoreDb();

    // some code omitted for brevity

    public ActionResult Edit(int id = 0)
    {
        MedicalProduct product = _db.Products.Find(id);
        if (product == null)
        {
            return HttpNotFound();
        }
        var productList = new List<MedicalProduct> { product }; 
        var viewModel = GetMedicalProductViewModelList(productList);
        return View(viewModel);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(IEnumerable<MedicalProduct> productList)
    {
        if (ModelState.IsValid)
        {
            _db.Entry(productList).State = EntityState.Modified;
            _db.SaveChanges();
            return RedirectToAction("Index");
        }

        //var productList = new List<MedicalProduct> { product };
        var viewModel = GetMedicalProductViewModelList(productList);
        return View(viewModel);
    }

}

Edit.cshtml:

@model IEnumerable<MvcMedicalStore.Models.MedicalProductViewModel>

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>MedicalProduct</legend>

        @foreach (var modelItem in Model)
        {
            @Html.HiddenFor(item => modelItem.ID)

            <div class="editor-label">
                @Html.LabelFor(item => modelItem.Name)
            </div>
            <div class="editor-field">
                @Html.EditorFor(item => modelItem.Name)
                @Html.ValidationMessageFor(item => modelItem.Name)
            </div>

            <div class="editor-label">
                @Html.LabelFor(item => modelItem.Price)
            </div>
            <div class="editor-field">
                @Html.EditorFor(item => modelItem.Price)
                @Html.ValidationMessageFor(item => modelItem.Price)
            </div>


            <div class="editor-label">
                @Html.LabelFor(item => modelItem.BrandName)
            </div>
            <div class="editor-field">
                @Html.DropDownListFor(item => modelItem.BrandName, modelItem.BrandSelectListItem)
                @Html.ValidationMessageFor(item => modelItem.BrandName)
            </div>
        }
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

1 个答案:

答案 0 :(得分:2)

在我看来,模型绑定器无法绑定到您的集合,这将导致它null。它之所以这样做是因为你没有为每个元素指定索引。这意味着MVC无法确定如何正确绑定它们。

修改

我已经弄明白为什么这个答案的最后一次修订不起作用。首先,IEnumerable<T>没有直接索引器。相反,您可以使用Model.ElementAt(i).ID来访问ID属性。但是,这实际上无法解决模型绑定问题,因为由于某些原因,这不会为生成的name字段生成<input>属性的正确索引。 (更多内容见下文。)

有两种方法可以解决这个问题。第一种方法是将List传递给视图,而不是IEnumerable,然后访问前面显示的字段。但是,更好的方法是创建EditorTemplate。这将更容易,因为它可以节省您必须更改生成视图模型的现有方法。所以你需要按照以下步骤操作:

  1. 在视图的当前文件夹中创建EditorTemplates文件夹(例如,如果您的视图为Home\Index.cshtml,请创建文件夹Home\EditorTemplates)。
  2. 在该目录中创建一个强类型视图,其名称与您的模型匹配(例如,在这种情况下,该视图将被称为MedicalProductViewModel)。
  3. 将原始视图的大部分内容移动到该新模板中。
  4. 您最终会得到以下信息:

    @model MedicalProductViewModel
    
    @Html.HiddenFor(item => Model.ID)
    
    <div class="editor-label">
        @Html.LabelFor(item => Model.Name)
    </div>
    <div class="editor-field">
        @Html.EditorFor(item => Model.Name)
        @Html.ValidationMessageFor(item => Model.Name)
    </div>
    
    <div class="editor-label">
        @Html.LabelFor(item => Model.Price)
    </div>
    <div class="editor-field">
        @Html.EditorFor(item => Model.Price)
        @Html.ValidationMessageFor(item => Model.Price)
    </div>
    
    <div class="editor-label">
        @Html.LabelFor(item => Model.BrandName)
    </div>
    <div class="editor-field">
        @Html.DropDownListFor(item => Model.BrandName, Model.BrandSelectListItem)
        @Html.ValidationMessageFor(item => Model.BrandName)
    </div>
    

    请注意我们不再使用任何索引表示法来访问模型属性。

    现在,在Edit.cshtml视图中,您将完成此任务:

    @model IEnumerable<MvcMedicalStore.Models.MedicalProductViewModel>
    
    @{
        ViewBag.Title = "Edit";
    }
    
    <h2>Edit</h2>
    
    @using (Html.BeginForm()) {
        @Html.AntiForgeryToken()
        @Html.ValidationSummary(true)
    
        <fieldset>
            <legend>MedicalProduct</legend>
            @Html.EditorFor(m => m)
            <p>
                <input type="submit" value="Save" />
            </p>
        </fieldset>
    }
    
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>
    
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }
    

    虽然我在开始时给出了一个简短的解释,但我应该解释一下这实际上是做什么的。您的原始HTML将产生如下输出:

    <input name="ID" type="text" value="1" />
    <input name="Name" type="text" value="Name 1" />
    <input name="ID" type="text" value="2" />
    <input name="Name" type="text" value="Name 2" />
    

    如您所见,多个输入字段共享相同的名称。这就是模型绑定器绊倒的原因,因为您的操作告诉它绑定到集合,并且绑定器需要能够区分集合中的每个元素。 EditorTemplates非常聪明,可以确定您何时使用集合并自动将索引应用于输入字段。上面的代码将生成这样的输出:

    <input name="[0].ID" type="text" value="1" />
    <input name="[0].Name" type="text" value="Name 1" />
    <input name="[1].ID" type="text" value="2" />
    <input name="[1].Name" type="text" value="Name 2" />
    

    如您所见,这些字段现在有一个与之关联的索引。这为模型绑定器提供了将所有项目添加到集合所需的所有信息。现在,我们可以回过头来修复您的产品保存代码。

    Gert所说的对于你试图保存productList的方式仍然是正确的。您需要在该集合中的每个项目上设置EntityState.Modified标志:

    if (ModelState.IsValid)
    {
        foreach (var product in productList)
            _db.Entry(product).State = EntityState.Modified;
    
        _db.SaveChanges();
    
        return RedirectToAction("Index");
    }
    

    看看是否有效。