【问题标题】:Save multiple rows simultaneously from the same form - dotnet core从同一个表单同时保存多行 - dotnet core
【发布时间】:2018-01-08 20:23:18
【问题描述】:

我有一个表格,其中有一个空列,用户可以在其中输入评论:

Table
-----

TbMapId |        UniqueAdp         |    Dealership    |    Line
--------------------------------------------------------------------
1       |    [Insert comment here] |    Derby         |    abc123
2       |    [Insert comment here] |    Keighley      |    cda345
3       |    [Insert comment here] |    Manchester    |    876ghj

要评论的数据很多,我不能指望用户打开“编辑”页面并一一输入评论。相反,我需要用户能够输入一堆 cmets(一次 20 个,对 20 行),并通过单击提交按钮将它们全部保存。


如果您想直接跳到工作解决方案,请转到 EDIT #2 并查看 Rudi 接受的答案


查看

        <form asp-action="TbMapViewEdit">
            <div class="col-lg-6">
                <div class="row">
                    <input type="submit" value="Save" class="btn btn-primary" />
                    <div class="col-md-12">
                        <table class="table table-condensed table-bordered table-hover">
                            <thead>
                                <tr>
                                    <td><b>TEMP ID</b></td>
                                    <td><b>Map To</b></td>
                                    <td><b>Accounts Code</b></td>
                                    <td><b>Line</b></td>
                                    <td><b>Map Result</b></td>
                                </tr>
                            </thead>
                            <tbody>

                                @for (int i = 0; i < Model.TBMapBalancesList.Count; i++)
                                { 
                                    <tr>
                                        <td>
                                            @Html.DisplayFor(Model => Model.TBMapBalancesList[i].TbMapId)
                                            @Html.HiddenFor(Model => Model.TBMapBalancesList[i].TbMapId)
                                        </td>
                                        <td>@Html.EditorFor(Model => Model.TBMapBalancesList[i].UniqueAdp, new { @class = "control-label_DI" })</td>
                                        <td>@Html.DisplayFor(Model => Model.TBMapBalancesList[i].AccountsCode)</td>
                                        <td>@Html.DisplayFor(Model => Model.TBMapBalancesList[i].Line)</td>
                                        <td>@Html.DisplayFor(Model => Model.TBMapBalancesList[i].MapResult)</td>
                                    </tr>
                                }

                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </form>

型号

我今天了解到,我需要使用 List 才能通过使用 @for 循环(如上所示)来遍历表中的行。在我尝试使用 IEnumerable 之前。所以我在模型中添加了public List&lt;TBMapBalances&gt; TBMapBalancesList { get; set; }的定义

 public class TbMapViewModel
    {
        public IEnumerable<ASPNET_Core_1_0.Models.TBMapBalances> TBMapBalances { get; set; }
        public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
        public List<TBMapBalances> TBMapBalancesList { get; set; }

       [...]

    }

控制器:

现在这是我需要帮助的地方,我的代码根本不会抛出任何错误。当我按下提交时,没有任何反应:

        [Authorize]
        [HttpPost]
        public async Task<IActionResult> TbMapViewEdit(TbMapViewModel tbMapViewModel)
        {

            if (ModelState.IsValid)
            {
                foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
                {
                    var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();

                    if (getCode != null)
                    {
                        getCode.TbMapId = TbListId.TbMapId;
                    }

                }

                try
                {
                    _context.Update(tbMapViewModel.TBMapBalances);
                    await _context.SaveChangesAsync();
                }

                catch (DbUpdateConcurrencyException)
                {
                    throw;
                }
            }

            return RedirectToAction("TbMapView");
        }

编辑#1

视图更改

        <form asp-action="TbMapViewEdit">
            <div class="col-lg-6">
                <div class="row">
                    <input type="submit" value="Save" class="btn btn-primary" />
                    <div class="col-md-12">
                        <table class="table table-condensed table-bordered table-hover">
                            <thead>
                                <tr>
                                    <td><b>TEMP ID</b></td>
                                    <td><b>Map To</b></td>
                                    <td><b>Accounts Code</b></td>
                                    <td><b>Line</b></td>
                                    <td><b>Map Result</b></td>
                                </tr>
                            </thead>
                            <tbody>
                                @for (int i = 0; i < Model.TBMapBalances.Count; i++)
                                { 
                                    <tr>
                                        <td>
                                            @Html.DisplayFor(Model => Model.TBMapBalances[i].TbMapId)
                                            @Html.HiddenFor(Model => Model.TBMapBalances[i].TbMapId)
                                        </td>
                                        <td>@Html.EditorFor(Model => Model.TBMapBalances[i].UniqueAdp, new { @class = "control-label_DI" })</td>
                                        <td>@Html.DisplayFor(Model => Model.TBMapBalances[i].AccountsCode)</td>
                                        <td>@Html.DisplayFor(Model => Model.TBMapBalances[i].Line)</td>
                                        <td>@Html.DisplayFor(Model => Model.TBMapBalances[i].MapResult)</td>
                                    </tr>
                                }

                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </form>

模型变化

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ASPNET_Core_1_0.Models.TbMapViewModels
{
    public class TbMapViewModel
    {

        public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
        public List<TBMapBalances> TBMapBalances { get; set; }

        [...]
    }
}

对控制器的更改:

现在这是我需要帮助的地方,我的代码在当前状态下根本不会抛出任何错误 - 当我按下提交时,没有任何反应,也没有数据保存到数据库中。

但是,当您取消注释行 _context.Update(tbMapViewModel.TBMapBalances);我收到一个错误,指出 List 不是任何模型的一部分并且找不到。

另外,下面的代码是我写的,试图遵循这个 SO 帖子:update-multiple-records-at-once-in-asp-net-mvc - 最初我试图让它异步,但我得到了更多错误,无法继续。我想我会尽可能密切关注它,希望它能给我一个工作的起点。

        [Authorize]
        [HttpPost]
        public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
        {

            if (ModelState.IsValid)
            {
                foreach (var TbListId in tbMapViewModel.TBMapBalances)
                {
                    var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();

                    if (getCode != null)
                    {
                        getCode.TbMapId = TbListId.TbMapId;
                    }

                }
               // _context.Update(tbMapViewModel.TBMapBalances);
                _context.SaveChanges();

            }

            return RedirectToAction("TbMapView");
        }

编辑 #2 - 救援英雄 - 非常感谢 @RudiVisser 的帮助

首先,如果你们中的任何人 - 像我一样 - 卡在使用 .net core 1.0.0 确保首先升级到最新版本(1.1.7 lts)。我的部分悲痛是我是 USER 1.0 并且没有升级我的安装,因为修复和添加不断出现。不要像我一样成为那个人......

以下均感谢 Rudi 的帮助:

查看

@using (Html.BeginForm("TbMapViewEdit", "TbMap"))
{
            <div class="col-lg-6">
                <div class="row">
                    <input type="submit" value="Save" class="btn btn-primary" />
                    <div class="col-md-12">
                        <table class="table table-condensed table-bordered table-hover">
                            <thead>
                                <tr>
                                    <td><b>TEMP ID</b></td>
                                    <td><b>Map To</b></td>
                                    <td><b>Accounts Code</b></td>
                                    <td><b>Line</b></td>
                                    <td><b>Map Result</b></td>
                                </tr>
                            </thead>
                            <tbody>
                                 @Html.EditorFor(m => m.TBMapBalances);
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
}

将你的“方法”、“控制器”放在(Html.BeginForm("TbMapViewEdit", "TbMap")) 中,否则表单 POST 操作将设置为当前位置。

型号

为简洁起见截断。我有一个带有列表的视图模型,我将把数据保存到另一个表中,只是为了显示一些信息。

 public class TbMapViewModel
    {
        public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
        public List<TBMapBalances> TBMapBalances { get; set; } = new List<TBMapBalances>();

[...]
}

控制器

[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{

    if (ModelState.IsValid)
    {
        foreach (var TbListId in tbMapViewModel.TBMapBalances)
        {
            var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();

            if (getCode != null)
            {
                getCode.UniqueAdp = TbListId.UniqueAdp;
            }

        }
        _context.SaveChanges();
    }

    return RedirectToAction("TbMapView");
}

我在这里犯的错误是我试图用它自己的副本替换密钥(查找 ID 为 1 并将其设置为 ID 1),而不是选择我需要的实际字段编辑在我的例子中是 UniqueAdp。

然后给我带来了新的东西,那就是编辑器模板:

编辑器模板

在“视图”文件夹下的“共享”文件夹中创建一个名为 EditorTemplates 的文件夹,并使用您要编辑的模型的确切名称。在我的例子中,模型被称为TBMapBalances,所以我在新创建的文件夹中创建了一个TBMapBalances.cshtml 文件,然后粘贴了这个(这最初是在我的主视图文件中):

@model ASPNET_Core_1_0.Models.TBMapBalances

<tr>
    <td>
        @Html.DisplayFor(Model => Model.TbMapId)
        @Html.HiddenFor(Model => Model.TbMapId)
    </td>
    <td>@Html.EditorFor(Model => Model.UniqueAdp, new { @class = "control-label_DI" })</td>
    <td>@Html.DisplayFor(Model => Model.AccountsCode)</td>
    <td>@Html.DisplayFor(Model => Model.Line)</td>
    <td>@Html.DisplayFor(Model => Model.MapResult)</td>
</tr>

顺便说一句,new { @class = "control-label_DI" } 是为了给每个创建的输入字段添加类。这似乎在 .net 核心中不起作用,并留在那里只是为了提醒自己我需要以某种方式执行此操作。

研究:

Update multiple records at once in asp.net mvc

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms

http://www.binaryintellect.net/articles/b1e0b153-47f4-4b29-8583-958aa22d9284.aspx

How to bind an Array in MVC Core

https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core/

ASP.NET Core 1.0 POST IEnumerable<T> to controller

【问题讨论】:

    标签: c# asp.net-core .net-core asp.net-core-mvc


    【解决方案1】:

    这个问题有两部分,第一是需要有一种方法来编辑数据集合。这可以通过 EditorTemplates 解决,这涉及创建单个编辑器模型,然后在您希望编辑的项目集合上调用 @Html.EditorFor(..)

    提供最小样本(完整 Fx,非核心)on Github

    第二个问题是更新实体的方式,被更改的属性是错误的,因此没有保存(PK 正在更新到 PK),并且实体在它已经被跟踪时被附加。

    foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
    {
        var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
        if (getCode != null)
        {
            getCode.TbMapId = TbListId.TbMapId;
        }
    }
    try
    {
        _context.Update(tbMapViewModel.TBMapBalances);
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        throw;
    }
    

    当您从数据库中检索模型时,记住 Entity Framework 为您做了什么很重要。它由上下文自动跟踪,因此它已经附加并准备更新,您所做的任何更改都将被自动跟踪并随后保存。

    _context.Update(..) 的调用尝试根据 ID 将未跟踪的模型(来自您的 POST 数据)附加到上下文中,但是因为您已经提取了该 ID(在您的 .Where(..).FirstOrDefault(..) 中)它已经被跟踪,然后就炸了。

    另外考虑到这是 EFC 1.0 并且您没有 .Find(..) 方法,使用 .SingleOrDefault(..) 可能是在主键字段上使用的更好方法。

    你重写的代码可能是这样的:

    foreach (var postedModel in tbMapViewModel.TBMapBalancesList)
    {
        var dbModel = _context.TBMapBalances.SingleOrDefault(p => p.TbMapId == postedModel.TbMapId);
        if (dbModel != null)
        {
            dbModel.UniqueAdp = postedModel.UniqueAdp;
        }
    }
    await _context.SaveChangesAsync();
    

    虽然出于安全原因我不推荐它,但为了后代,如果您想将整个发布的模型附加到上下文(基于 ID)并更新它,您可以使用与原始代码类似的代码来执行此操作,删除foreach循环:

    _context.UpdateRange(tbMapViewModel.TBMapBalances);
    await _context.SaveChangesAsync();
    

    (我不推荐它,因为发布的所有内容都将设置在数据库中,根据经验,建议仅根据第一个代码集设置您希望更新的字段。但是,它应该,比 foreach 循环更快,因为您没有从数据库加载并重新保存,只有后者)

    【讨论】:

    • 再次感谢。在我的编辑#2 中,我提到需要将核心框架和 ef 升级到最新版本。 1.1.x 具有“查找”功能。
    【解决方案2】:

    您是否已经在剃须刀页面中内置了 cmets 的输入?我没看见他们。您想要做的是为循环内的循环中的每个项目创建一个具有您想要的输入类型的表单。然后,每个表单将引用迭代器作为隐藏值,以便在发布时传递。如果循环是foreach(var item in Model.items){},您将拥有一个包含该块中输入的表单,其中隐藏输入看起来像&lt;input type="hidden" name="index" value="@item.index"/&gt; 这将允许您使用任何标识符发布您需要将该特定表单数据附加到正确模型的任何标识符。

    请参阅此答案以了解循环内表单的结构Multiple forms on one MVC form, created with a loop, only the first submits data

    【讨论】:

    • &lt;td&gt;@Html.EditorFor(Model =&gt; Model.TBMapBalancesList[i].UniqueAdp, new { @class = "control-label_DI" })&lt;/td&gt; 这一行。 HTML 帮助器为 db 中的每个项目创建输入。您提到的逻辑在某种程度上已经存在,但是如果您阅读我的研究中的帖子,您会发现到目前为止我得到的似乎是方式,而我缺少的是控制器中的 POST 操作方法-或者更确切地说是如何使其将数据保存到表中
    猜你喜欢
    • 2018-06-02
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    • 2021-06-05
    • 2016-01-06
    • 1970-01-01
    • 2020-04-01
    • 2011-06-29
    相关资源
    最近更新 更多