为什么DbContext.Entry(IEnumerable的< MedicalProduct&GT)。国家产生我的code的ArguementNullException? [英] Why does DbContext.Entry(IEnumerable<MedicalProduct>).State produce an ArguementNullException in my code?

查看:201
本文介绍了为什么DbContext.Entry(IEnumerable的< MedicalProduct&GT)。国家产生我的code的ArguementNullException?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的 MedicalProductController ,我试图让我的修改的行动能够在一个页面上编辑多个对象。要做到这一点,我计划在我的 HTTPPOST 编辑操作方法接收的IEnumerable< MedicalProduct> 而不是 MedicalProduct 的脚手架设置为我说话。

当我点击保存提交了一些变化,我得到一个 ArguementNullException 未处理就行了: _db.Entry(productList的).STATE = EntityState .Modified; ,我不明白为什么它是空

MedicalProductController

 公共类MedicalProductController:控制器
{
    私人MvcMedicalStoreDb _db =新Mv​​cMedicalStoreDb();    //一些code不再赘述    公众的ActionResult编辑(INT ID = 0)
    {
        MedicalProduct产物= _db.Products.Find(ID);
        如果(产品== NULL)
        {
            返回HttpNotFound();
        }
        VAR productList的=新名单< MedicalProduct> {}产品;
        VAR视图模型= GetMedicalProductViewModelList(productList的);
        返回视图(视图模型);
    }    [HttpPost]
    [ValidateAntiForgeryToken]
    公众的ActionResult编辑(IEnumerable的< MedicalProduct> productList的)
    {
        如果(ModelState.IsValid)
        {
            _db.Entry(productList的).STATE = EntityState.Modified;
            _db.SaveChanges();
            返回RedirectToAction(「指数」);
        }        // VAR productList的=新名单< MedicalProduct> {}产品;
        VAR视图模型= GetMedicalProductViewModelList(productList的);
        返回视图(视图模型);
    }}

Edit.cshtml

  @model IEnumerable的< MvcMedicalStore.Models.MedicalProductViewModel>@ {
    ViewBag.Title =编辑;
}< H2>编辑< / H>@using(Html.BeginForm()){
    @ Html.AntiForgeryToken()
    @ Html.ValidationSummary(真)    <&字段集GT;
        <传奇> MedicalProduct< /传说>        @foreach(以型号VAR modelItem)
        {
            @ Html.HiddenFor(项目=> modelItem.ID)            < D​​IV CLASS =编辑标记>
                @ Html.LabelFor(项目=> modelItem.Name)
            < / DIV>
            < D​​IV CLASS =主编场>
                @ Html.EditorFor(项目=> modelItem.Name)
                @ Html.ValidationMessageFor(项目=> modelItem.Name)
            < / DIV>            < D​​IV CLASS =编辑标记>
                @ Html.LabelFor(项目=> modelItem.Price)
            < / DIV>
            < D​​IV CLASS =主编场>
                @ Html.EditorFor(项目=> modelItem.Price)
                @ Html.ValidationMessageFor(项目=> modelItem.Price)
            < / DIV>
            < D​​IV CLASS =编辑标记>
                @ Html.LabelFor(项目=> modelItem.BrandName)
            < / DIV>
            < D​​IV CLASS =主编场>
                @ Html.DropDownListFor(项目=> modelItem.BrandName,modelItem.BrandSelectListItem)
                @ Html.ValidationMessageFor(项目=> modelItem.BrandName)
            < / DIV>
        }
        &所述p为H.;
            <输入类型=提交值=保存/>
        &所述; / P>
    < /字段集>
}< D​​IV>
    @ Html.ActionLink(返回目录,索引)
< / DIV>@section脚本{
    @ Scripts.Render(〜/包/ jqueryval)
}


解决方案

在我看来像模型绑定不能绑定到你的收藏,这将导致它是。它这样做的原因是因为你没有指定每个元素的索引。这意味着MVC有没有办法确定如何将它们正确结合

修改

我已经想通了,为什么这个答案的最后一个版本没有工作。首先,的IEnumerable< T> 没有直接的索引。相反,你会使用 Model.ElementAt(I).ID 访问 ID 属性。然而,这实际上不会与模型结合问题解决的问题,因为,由于某种原因,这并不产生对名称的正确索引为生成的属性<输入> 字段。 (更多内容如下)。

有两种方法可以解决这个问题。第一种方式是通过一个列表来的观点,而不是的IEnumerable ,然后访问该领域我展示早。然而,更好的方法是创建一个 EditorTemplate 来代替。因为它可以节省您不必更改现有方法这是生成您的视图模型这将是更容易。所以,你需要遵循以下步骤:


  1. 创建视图的当前文件夹内的 EditorTemplates 文件夹(例如,如果你的观点是首页\\ Index.cshtml ,创建该文件夹首页\\ EditorTemplates )。

  2. 创建与名称目录中的一个强类型的视图,你的模型相匹配(例如在这种情况下,视图将被称为 MedicalProductViewModel )。

  3. 将你的大部分原始视图的成新的模板。

您将与下列结束:

  @model MedicalProductViewModel@ Html.HiddenFor(项目=> Model.ID)< D​​IV CLASS =编辑标记>
    @ Html.LabelFor(项目=> Model.Name)
< / DIV>
< D​​IV CLASS =主编场>
    @ Html.EditorFor(项目=> Model.Name)
    @ Html.ValidationMessageFor(项目=> Model.Name)
< / DIV>< D​​IV CLASS =编辑标记>
    @ Html.LabelFor(项目=> Model.Price)
< / DIV>
< D​​IV CLASS =主编场>
    @ Html.EditorFor(项目=> Model.Price)
    @ Html.ValidationMessageFor(项目=> Model.Price)
< / DIV>< D​​IV CLASS =编辑标记>
    @ Html.LabelFor(项目=> Model.BrandName)
< / DIV>
< D​​IV CLASS =主编场>
    @ Html.DropDownListFor(项目=> Model.BrandName,Model.BrandSelectListItem)
    @ Html.ValidationMessageFor(项目=> Model.BrandName)
< / DIV>

请注意我们如何不再使用任何索引符号来访问模型的属性。

现在在你的 Edit.cshtml 视图,你会留下这样的:

  @model IEnumerable的< MvcMedicalStore.Models.MedicalProductViewModel>@ {
    ViewBag.Title =编辑;
}< H2>编辑< / H>@using(Html.BeginForm()){
    @ Html.AntiForgeryToken()
    @ Html.ValidationSummary(真)    <&字段集GT;
        <传奇> MedicalProduct< /传说>
        @ Html.EditorFor(M =>米)
        &所述p为H.;
            <输入类型=提交值=保存/>
        &所述; / P>
    < /字段集>
}< D​​IV>
    @ Html.ActionLink(返回目录,索引)
< / DIV>@section脚本{
    @ Scripts.Render(〜/包/ jqueryval)
}

虽然我给在开始一个简短的解释,我真的应该解释一下这究竟是干什么。你原来的HTML会产生类似以下的输出:

 <输入名称=ID类型=文本值=1/>
<输入名称=名称TYPE =文本VALUE =名称1/>
<输入名称=ID类型=文本VALUE =2/>
<输入名称=名称TYPE =文本VALUE =2名/>

正如你所看到的,多个输入字段共享相同的名称。这就是为什么该模型粘合剂绊倒了,因为你的行动告诉它绑定到一个集合和粘合剂需要能够集合中的每个元素进行区分。 EditorTemplates 有足够的智慧找出当你与一个收集工作,并将自动索引你的输入字段。什么上面的code会做的就是产生这样的输出这个代替:

 <输入名称=[0] .ID类型=文本值=1/>
<输入名称=[0] .Name点类型=文本VALUE =名称1/>
<输入名称=[1] .ID类型=文本VALUE =2/>
<输入名称=[1] .Name点类型=文本VALUE =2名/>

正如你可以看到,字段现在有与之关联的索引。这使粘合剂的所有信息,则需要能够对所有的项目添加到集合的模型。现在,这出的方式,我们可以回去修理你的产品节省了code。

什么格特说仍然是正确的你正试图保存产品的方式。您需要进行设置 EntityState.Modified 标志上集合中的每个单独的项目:

 如果(ModelState.IsValid)
{
    的foreach(在productList的变种产品)
        _db.Entry(产品).STATE = EntityState.Modified;    _db.SaveChanges();    返回RedirectToAction(「指数」);
}

其是否正常工作。

In my MedicalProductController, I am trying to make my Edit action able to edit multiple objects on one page. To do that, I plan on my HTTPPOST edit action method receiving an IEnumerable<MedicalProduct> instead of the MedicalProduct that the scaffolding set up for me.

When I click save to submit some changes, I get an ArguementNullException unhandled on the line: _db.Entry(productList).State = EntityState.Modified; and I don't understand why it is null.

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")
}

解决方案

It looks to me like the model binder isn't able to bind to your collection, which would cause it to be null. The reason it's doing that is because you're not specifying an index for each of the elements. That means MVC has no way to determine how to bind them correctly.

Edit

I've figured out why the last revision of this answer didn't work. Firstly, IEnumerable<T> doesn't have a direct indexer. Instead you would use Model.ElementAt(i).ID to access the ID property. However, this actually wouldn't solve the problem with the model binding issue as, for some reason, this doesn't generate the proper indices on the name attributes for the generated <input> fields. (More on this below.)

There are two ways to fix that. The first way would be to pass a List to the view, instead of IEnumerable, then accessing the fields as I showed earlier. However, the better way would be to create an EditorTemplate instead. This will be easier because it saves you having to change your existing methods which are generating your view model. So you'll need to follow these steps:

  1. Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates).
  2. Create a strongly-typed view in that directory with the name that matches your model (e.g in this case the view would be called MedicalProductViewModel).
  3. Move the bulk of your original view into that new template.

You'll end up with the following:

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

Notice how we're no longer using any indexing notation to access the model properties.

Now in your Edit.cshtml view, you'd be left with this:

@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")
}

Although I gave a brief explanation at the start, I should really explain what this is actually doing. Your original HTML would have produced output like the following:

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

As you can see, multiple input fields share the same name. That's why the model binder is tripping up, because your action is telling it to bind to a collection and the binder needs to be able to distinguish between each element in the collection. EditorTemplates are smart enough to figure out when you're working with a collection and will apply indices to your input fields automatically. What the code above will do is generate output like this instead:

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

As you can see, the fields now have an index associated with them. That gives the model binder all the information it needs to be able to add all of the items to the collection. Now that's out of the way, we can get back to fixing your product saving code.

What Gert said is still right about the way you're trying to save productList. You need to be setting the EntityState.Modified flag on each individual item in that collection:

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

    _db.SaveChanges();

    return RedirectToAction("Index");
}

See if that works.

这篇关于为什么DbContext.Entry(IEnumerable的&LT; MedicalProduct&GT)。国家产生我的code的ArguementNullException?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆