@Html.EditorFor(m => m.TBMapBalances);</tbody>}
将您的方法"、控制器"放入(Html.BeginForm("TbMapViewEdit", "TbMap"))
否则表单POST 操作将设置为当前位置.
模型
为简洁起见被截断.我有一个带有 List 的视图模型,我会将数据保存到另一个表中,只是为了显示一些信息.
公共类 TbMapViewModel{公共 IEnumerableTBMapUniqueADP { 获取;放;}公共列表<TBMapBalances>TBMapBalances { 获取;放;} = new List();[...]}
控制器
[授权][HttpPost]公共 IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel){如果(模型状态.IsValid){foreach(tbMapViewModel.TBMapBalances 中的 var TbListId){var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();如果(获取代码!= 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 core 中不起作用,留在那里只是为了提醒自己我需要以某种方式这样做.
研究:
在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
如何在 MVC Core 中绑定数组
https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core/
ASP.NET Core 1.0 POST IEnumerable;到控制器
解决方案
这个问题有两部分,第一是需要一种编辑数据集合的方法.这可以通过 EditorTemplates 解决,它涉及创建单个编辑器模型,然后在您希望编辑的项目集合上调用 @Html.EditorFor(..)
.
在 Github 上提供了一个最小示例(Full Fx,而不是 Core).
第二个问题是更新实体的方式,更改的属性是错误的,因此没有保存(PK 正在更新到 PK),并且实体在已经被跟踪时被附加.
foreach(tbMapViewModel.TBMapBalancesList 中的 var TbListId){var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();如果(获取代码!= null){getCode.TbMapId = TbListId.TbMapId;}}尝试{_context.Update(tbMapViewModel.TBMapBalances);等待 _context.SaveChangesAsync();}捕获 (DbUpdateConcurrencyException){扔;}
当您从数据库中检索模型时,请务必记住 Entity Framework 为您做什么.它由上下文自动跟踪,因此它已经附加并准备好更新,您更改的任何内容都将自动跟踪并随后保存.
对 _context.Update(..)
的调用尝试将非跟踪模型(来自您发布的数据)附加到基于 ID 的上下文,但因为您已经提取了该 IDout(在你的 .Where(..).FirstOrDefault(..)
)它已经被跟踪了,所以爆炸了.
另外鉴于这是 EFC 1.0 并且您没有 .Find(..)
方法,使用 .SingleOrDefault(..)
可能是更好的使用方法在主键字段上.
你重写的代码可能是这样的:
foreach(tbMapViewModel.TBMapBalancesList 中的 varpostedModel){var dbModel = _context.TBMapBalances.SingleOrDefault(p => p.TbMapId ==postedModel.TbMapId);如果(dbModel != null){dbModel.UniqueAdp = PostedModel.UniqueAdp;}}等待 _context.SaveChangesAsync();
为了后代,虽然出于安全原因我不会推荐它,如果您想将整个发布的模型附加到上下文(基于 ID)并更新它,您可以使用与原始模型类似的代码来执行此操作,删除foreach
循环:
_context.UpdateRange(tbMapViewModel.TBMapBalances);等待 _context.SaveChangesAsync();
(我不推荐它,因为发布的所有内容都将在数据库中设置,根据经验,建议只设置您希望按照第一个代码集更新的字段.但是,它应该,比 foreach 循环更快,因为您没有从数据库加载并保存回,只有后者)
I have a table that has one empty column into which user can enter a comment:
Table
-----
TbMapId | UniqueAdp | Dealership | Line
--------------------------------------------------------------------
1 | [Insert comment here] | Derby | abc123
2 | [Insert comment here] | Keighley | cda345
3 | [Insert comment here] | Manchester | 876ghj
There is a lot of data to comment on, I can't expect a user to open an 'Edit' page and type in a comment one by one. Instead I need user to be able to input a bunch of comments (say 20 at a time against 20 rows) and save them all at one click of submit button.
If you want to jump straight to working solution go to EDIT #2 & look at Rudi's accepted answer
View
<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>
Model
I've learned today that I need to use List to be able to iterate through the lines in table by the use of @for
loop (as shown above). before I was trying to use IEnumerable. So I added a definition to the model for public List<TBMapBalances> 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; }
[...]
}
Controller:
Now this is where I need the help with, my code doesn't throw any errors at all. When I press Submit nothing happens:
[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");
}
EDIT #1
Changes to View
<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>
Changes to model
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; }
[...]
}
}
Changes to Controller:
Now this is where I need the help with, my code doesn't throw any errors at all when at the current state - when I press Submit nothing happens and no data gets saved to the database.
however, when you uncomment line _context.Update(tbMapViewModel.TBMapBalances); I get an error that List is not part of any Model and is not found.
Also, below code is something I wrote trying to follow this SO post: update-multiple-records-at-once-in-asp-net-mvc - Initially I was trying to make it Async but I was getting even more errors and couldn't continue. I thought I am going to follow it as closely as possible in hope that it will get me a working starting point.
[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");
}
EDIT #2 - A hero to the rescue - big thanks to @RudiVisser for help
First of all if any of you guys are - like me - stuck using .net core 1.0.0
make sure you upgrade to the latest version first (1.1.7 lts). Part of my grief was that I was an USER 1.0 and did not upgrade my installation as fixes and additions kept coming out. Don't be that guy, like I was...
All below is thanks to Rudi's help:
View
@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>
}
Put your "Method", "Controller" in (Html.BeginForm("TbMapViewEdit", "TbMap"))
otherwise the form POST action will be set to the current location.
Model
Truncated for brevity. I have view model with List that I will be saving the data to and one other table just for displaying some info.
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; } = new List<TBMapBalances>();
[...]
}
Controller
[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");
}
Error that I was making here is that I was trying to replace the key with essentially the copy of itself (Find ID of 1 and set it to ID of 1) instead of picking up on the actual one field that I needed to edit which in my case was UniqueAdp.
Then came the new thing to me, which was Editor Template:
Editor Template
Create a folder called EditorTemplates
in 'Shared' Folder under your 'Views' folder with the exact name of the model that you intend to edit. In my case the model was called TBMapBalances
so I created a TBMapBalances.cshtml
file inside the newly created folder, then pasted this (this was originally in my main view file):
@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>
By the way the new { @class = "control-label_DI" }
is there to supposedly add class to each created input field. This doesn't seem to work in .net core and is left there just as a reminder to myself that I need to do this somehow.
Research:
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
解决方案
This problem has 2 parts to it, the first is that there needed to be a way to edit collections of data. This can be solved with EditorTemplates, which involves creating a single editor model and then calling @Html.EditorFor(..)
on the collection of items you wish to edit.
A minimal sample (Full Fx, not Core) is available on Github.
The second problem was with the way the entities were being updated, the property being changed was wrong and hence not saving (the PK was being updated to the PK) and the entity was being attached when it's already tracked.
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;
}
It's important to remember what Entity Framework does for you when you retrieve a model from the database. It is automatically tracked by the context, and so it's already attached and ready to update, anything you change will be automatically tracked and subsequently saved.
The call to _context.Update(..)
tries to attach the non-tracked models (from your POSTed data) to the context based on ID, but because you've already pulled that ID out (in your .Where(..).FirstOrDefault(..)
) it's already tracked, and so blows up.
Also given that this is EFC 1.0 and you have no .Find(..)
method, using .SingleOrDefault(..)
is probably a better method to use on a primary key field.
Your rewritten code could be as so:
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();
For posterity though I wouldn't recommend it for security reasons, if you wanted to attach your whole posted model to the context (based on ID) and update it, you can do so with code similar to your original, removing the foreach
loop:
_context.UpdateRange(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
(I don't recommend it because everything that was posted will then be set in the database, from experience it's advisable to only set the fields you're expecting to update as per the first code set. It should, however, be quicker than the foreach loop given that you're not loading from the database and saving back in, only the latter)
这篇关于从同一表单同时保存多行 - dotnet core的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文