【问题标题】:Why does DbContext.Entry(IEnumerable<MedicalProduct>).State produce an ArguementNullException in my code?为什么 DbContext.Entry(IEnumerable<MedicalProduct>).State 在我的代码中产生 ArguementNullException?
【发布时间】:2013-10-01 18:01:21
【问题描述】:

在我的MedicalProductController 中,我试图让我的Edit 操作能够在一页上编辑多个对象。为此,我计划让我的HTTPPOST 编辑操作方法接收IEnumerable&lt;MedicalProduct&gt;,而不是脚手架为我设置的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);
    }

}

编辑.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")
}

【问题讨论】:

  • 我本来希望InvalidOperationException 声明productList 不是模型的一部分。因为这似乎是错误的:您应该设置个人 Products 的状态。

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


【解决方案1】:

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

编辑

我已经弄清楚为什么这个答案的最新版本不起作用。首先,IEnumerable&lt;T&gt; 没有直接索引器。相反,您将使用 Model.ElementAt(i).ID 来访问 ID 属性。但是,这实际上并不能解决模型绑定问题,因为出于某种原因,这不会在生成的&lt;input&gt; 字段的name 属性上生成正确的索引。 (更多内容见下文。)

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

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

你会得到以下结果:

@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" />

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

格特所说的关于您尝试保存productList 的方式仍然是正确的。您需要在该集合中的每个单独项目上设置 EntityState.Modified 标志:

if (ModelState.IsValid)
{
    foreach (var product in productList)
        _db.Entry(product).State = EntityState.Modified;

    _db.SaveChanges();

    return RedirectToAction("Index");
}

看看这是否有效。

【讨论】:

  • 当我在我的 cshtml 文件中使用该代码时,我得到一个编译错误: > 无法使用 [] 将索引应用到类型为 'System.Collections.Generic.IEnumerable'
  • @ArmorCode 更新了我的答案。希望现在这对您有用。
  • 我不确定我是否完全理解。编译器如何知道将编辑器模板文件中的内容放入Html.Editorfor(m=&gt; m) 中?我的理解是,强类型的@model IEnumerable&lt;MvcMedicalStore.Models.MedicalProductViewModel&gt; 告诉视图它应该期待一个可枚举的内容进入 EditorFor 的“m”。
  • @ArmorCode 顺便提一下,DisplayTemplates 也存在,由Html.Display* 助手使用。它们与EditorTemplates 完全相同,只是您将它们存储在名为DisplayTemplates 的文件夹中,而不是EditorTemplates。我建议阅读 Brad Wilson 关于该主题的系列文章(可在此处找到:bradwilson.typepad.com/blog/2009/10/…)。尽管它使用的是 MVC 2,但其中大部分仍然适用。唯一不同的是模板中的语法及其扩展名(.ascx 而不是 .cshtml)。
  • @ArmorCode 那回答你的问题了吗?
猜你喜欢
  • 1970-01-01
  • 2015-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多