在剃刀页面上绑定多个对象的问题
Issues with binding multiple objects on a razor page
我正在尝试构建一个 razor 页面,允许用户在单个页面上编辑多个不同的对象 - 特定测试 运行 在实验室中进行单个测试 运行 .我一直 运行 兜圈子,现在到了这样的地步:
1.回传页面貌似清空了页面上parent(testRun)对象的一些数据,但是重新加载时数据依然完好无损
2.对子对象(testRowFatContent)的修改保留在页面中,但不保存在数据库中,当页面重新加载时消失。
我的目标是让用户能够编辑页面上的任何对象,单击“保存”,然后将值返回到屏幕并保存到数据库中。
我已经尝试强制修改 entity framework 状态,但这似乎并不是最好的解决方案(因为我需要在 cshtml 页面上创建隐藏的输入字段以捕获完整的对象) ,而且我不确定如何设置要标记为已修改的对象列表。我怀疑我遗漏了原始数据绑定的一些基本内容,但在浏览了 Microsoft 页面和 Whosebug 之后无法弄清楚。在阅读了一些表明可能是问题所在的文章后,我确实删除了 @foreach 循环并将其替换为带有 cshtml 中索引的 @for 循环。
namespace KookaburraLab.Pages.TestRun
{
public class RunTests : PageModel
{
private readonly KookaburraLab.Models.KookaburraLabContext _context;
public RunTests(KookaburraLab.Models.KookaburraLabContext context)
{
_context = context;
}
[BindProperty]
public KookaburraLab.Models.TestRun TestRun { get; set; }
[BindProperty]
public List<TestRowFatContent> testRowFatContents { get; set; }
[BindProperty]
public List<TestRowSaltExtract> testRowSaltExtracts { get; set; }
[BindProperty]
public List<TestRowWaterAbsorption> testRowWaterAbsorptions { get; set; }
[BindProperty]
public List<TestRowTensileStrength> testRowTensileStrength { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
TestRun = await _context.TestRun.SingleOrDefaultAsync(m => m.TestRunID == id);
if (TestRun == null)
{
return NotFound();
}
testRowFatContents = await _context.TestRowFatContent.Where(t => t.TestRunID == id).ToListAsync();
testRowWaterAbsorptions = await _context.TestRowWaterAbsorption.Where(t => t.TestRunID == id).ToListAsync();
testRowSaltExtracts = await _context.TestRowSaltExtract.Where(t => t.TestRunID == id).ToListAsync();
testRowTensileStrength = await _context.TestRowTensileStrength.Where(t => t.TestRunID == id).ToListAsync();
return Page();
}
public async Task<IActionResult> OnPostSaveChangesAsync()
{
_context.SaveChanges();
//ModelState.Clear();
testRowFatContents = await _context.TestRowFatContent.Where(t => t.TestRunID == TestRun.TestRunID).ToListAsync();
testRowWaterAbsorptions = await _context.TestRowWaterAbsorption.Where(t => t.TestRunID == TestRun.TestRunID).ToListAsync();
testRowSaltExtracts = await _context.TestRowSaltExtract.Where(t => t.TestRunID == TestRun.TestRunID).ToListAsync();
testRowTensileStrength = await _context.TestRowTensileStrength.Where(t => t.TestRunID == TestRun.TestRunID).ToListAsync();
return Page();
}
public async Task<IActionResult> OnPostCloseTestAsync()
{
return Page();
}
}
}
<form method="post" enctype="multipart/form-data">
<button type="submit" class="btn btn-success btn-sm" asp-page-handler="SaveChanges">Save Changes</button>
<button type="submit" class="btn btn-danger btn-sm" asp-page-handler="CloseTest">Abandon Test Run</button>
<input type="hidden" asp-for="TestRun.TestRunID" />
<input type="hidden" asp-for="TestRun.TestItemCreatedUser" />
@*<div>
<partial name="_TestRowFatContent"/>
</div>*@
<div>
@if (Model.testRowFatContents.Count > 0)
{
<h4>Fat Content Test</h4>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].TestRowStatus)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].TestRowSampleDescription)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w1EmptyFlask)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w2ThimbleWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w3ThimbleLeatherWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w4LeatherWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w5FlaskExtractWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w6ExtractWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].FatContent)
</th>
@*<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].TestItemID)
</th>*@
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].TestRowSampleID)
</th>
<th></th>
</tr>
</thead>
<tbody>
@for (int i=0; i < Model.testRowFatContents.Count(); i++)
{
<tr>
<td>
@Html.DisplayFor(modelItem => Model.testRowFatContents[i].TestRowStatus)
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].TestRowSampleDescription" class="form-control" />
<input type="hidden" asp-for="@Model.testRowFatContents[i].TestRowID" />
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].w1EmptyFlask" class="form-control" />
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].w2ThimbleWeight" class="form-control" />
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].w3ThimbleLeatherWeight" class="form-control" />
</td>
<td>
@Html.DisplayFor(modelItem => @Model.testRowFatContents[i].w4LeatherWeight)
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].w5FlaskExtractWeight" class="form-control" />
</td>
<td>
@Html.DisplayFor(modelItem => @Model.testRowFatContents[i].w6ExtractWeight)
</td>
<td>
@Html.DisplayFor(modelItem => @Model.testRowFatContents[i].FatContent)
</td>
@*<td>
@Html.DisplayFor(modelItem => item.TestItemID)
</td>*@
<td>
@Html.DisplayFor(modelItem => @Model.testRowFatContents[i].TestRowSampleID)
</td>
@*<td>
<a asp-page="./Edit" asp-route-id="@item.TestRowID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.TestRowID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.TestRowID">Delete</a>
</td>*@
</tr>
}
任何帮助将不胜感激 - 如果我错过了一些明显的事情,请提前致歉。
在调试时我已经确认正在调用 OnPost 处理程序,并且模型状态正确地反映了新的(更改的)值 - 它似乎丢失了 dbcontext,我认为它应该在 GET 和POST 在 .NET 核心中调用。我真的想避免必须为每个特定类型手动指定对 dbcontext 的更新,因为我可能会有大量项目使用此页面。我还尝试使用似乎不起作用的 AttachRange(testFatContents),无论我是否将其包含在 GET 或 POST 处理程序(或两者!)
中
我有一个类似结构的页面,可以将新项目添加到运行良好的 dbcontext,
提交表单时,表单数据会正确绑定到 PageModel 属性,但这些更改不会应用于上下文。您在断开连接的情况下工作 - 页面内的状态 - 包括上下文 - 不会在每次执行页面时重建 requests.It 。当页面因 POST 请求而执行时,状态由模型绑定器构建。因此,您必须将重构(更改)的实体附加到上下文,并告诉上下文它们处于(修改)的状态。然后上下文将知道要应用哪些更改。
EF Core 提供了一个 UpdateRange
方法,旨在让上下文知道集合已被修改,例如
_context.UpdateRange(testRowFatContents);
await _context.SaveChangesAsync();
在此处查看有关在断开连接的情况下工作的更多信息:https://www.learnentityframeworkcore.com/dbcontext/modifying-data#disconnected-scenario
您正在调用 _context.SaveChanges()
,这是在您的数据库中保存更改的正确方法,但您没有告诉数据库您想要更改的内容。
您希望使用模型绑定来收集您的用户发送到服务器的表单数据。
public async Task<IActionResult> OnPostSaveChangesAsync(List<TestRowFatContent> testRowFatContents, List<TestRowSaltExtract> testRowSaltExtracts, List<TestRowWaterAbsorption> testRowWaterAbsorptions, List<TestRowTensileStrength> testRowTensileStrength)
{
//Check if your list objects are filled with the user input, the name attribute of the input fields need to match the variable names above
//tell the context what you want to update
_context.UpdateRange(testRowFatContents);
_context.UpdateRange(testRowSaltExtracts);
_context.UpdateRange(testRowWaterAbsorptions);
_context.UpdateRange(testRowTensileStrength);
_context.SaveChanges();
return Page();
}
在 Entity Framework 中更新有时有点棘手,但重要的部分是您在 SaveChanges()
方法中拥有数据,然后告诉您的数据库如何处理它。
如果您有未包含在表单中的字段,它们将被覆盖,因为 EF 只是准确保存您从模型绑定中获得的对象。为避免未使用的字段被覆盖,您可以包括隐藏的输入字段或使用 EF 的 IsModified
属性。您必须在不想更改的属性上设置它。
示例:
_context.Entry(testRowFatContents).Property("FatContent").IsModified = false;
我正在尝试构建一个 razor 页面,允许用户在单个页面上编辑多个不同的对象 - 特定测试 运行 在实验室中进行单个测试 运行 .我一直 运行 兜圈子,现在到了这样的地步: 1.回传页面貌似清空了页面上parent(testRun)对象的一些数据,但是重新加载时数据依然完好无损 2.对子对象(testRowFatContent)的修改保留在页面中,但不保存在数据库中,当页面重新加载时消失。
我的目标是让用户能够编辑页面上的任何对象,单击“保存”,然后将值返回到屏幕并保存到数据库中。
我已经尝试强制修改 entity framework 状态,但这似乎并不是最好的解决方案(因为我需要在 cshtml 页面上创建隐藏的输入字段以捕获完整的对象) ,而且我不确定如何设置要标记为已修改的对象列表。我怀疑我遗漏了原始数据绑定的一些基本内容,但在浏览了 Microsoft 页面和 Whosebug 之后无法弄清楚。在阅读了一些表明可能是问题所在的文章后,我确实删除了 @foreach 循环并将其替换为带有 cshtml 中索引的 @for 循环。
namespace KookaburraLab.Pages.TestRun
{
public class RunTests : PageModel
{
private readonly KookaburraLab.Models.KookaburraLabContext _context;
public RunTests(KookaburraLab.Models.KookaburraLabContext context)
{
_context = context;
}
[BindProperty]
public KookaburraLab.Models.TestRun TestRun { get; set; }
[BindProperty]
public List<TestRowFatContent> testRowFatContents { get; set; }
[BindProperty]
public List<TestRowSaltExtract> testRowSaltExtracts { get; set; }
[BindProperty]
public List<TestRowWaterAbsorption> testRowWaterAbsorptions { get; set; }
[BindProperty]
public List<TestRowTensileStrength> testRowTensileStrength { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
TestRun = await _context.TestRun.SingleOrDefaultAsync(m => m.TestRunID == id);
if (TestRun == null)
{
return NotFound();
}
testRowFatContents = await _context.TestRowFatContent.Where(t => t.TestRunID == id).ToListAsync();
testRowWaterAbsorptions = await _context.TestRowWaterAbsorption.Where(t => t.TestRunID == id).ToListAsync();
testRowSaltExtracts = await _context.TestRowSaltExtract.Where(t => t.TestRunID == id).ToListAsync();
testRowTensileStrength = await _context.TestRowTensileStrength.Where(t => t.TestRunID == id).ToListAsync();
return Page();
}
public async Task<IActionResult> OnPostSaveChangesAsync()
{
_context.SaveChanges();
//ModelState.Clear();
testRowFatContents = await _context.TestRowFatContent.Where(t => t.TestRunID == TestRun.TestRunID).ToListAsync();
testRowWaterAbsorptions = await _context.TestRowWaterAbsorption.Where(t => t.TestRunID == TestRun.TestRunID).ToListAsync();
testRowSaltExtracts = await _context.TestRowSaltExtract.Where(t => t.TestRunID == TestRun.TestRunID).ToListAsync();
testRowTensileStrength = await _context.TestRowTensileStrength.Where(t => t.TestRunID == TestRun.TestRunID).ToListAsync();
return Page();
}
public async Task<IActionResult> OnPostCloseTestAsync()
{
return Page();
}
}
}
<form method="post" enctype="multipart/form-data">
<button type="submit" class="btn btn-success btn-sm" asp-page-handler="SaveChanges">Save Changes</button>
<button type="submit" class="btn btn-danger btn-sm" asp-page-handler="CloseTest">Abandon Test Run</button>
<input type="hidden" asp-for="TestRun.TestRunID" />
<input type="hidden" asp-for="TestRun.TestItemCreatedUser" />
@*<div>
<partial name="_TestRowFatContent"/>
</div>*@
<div>
@if (Model.testRowFatContents.Count > 0)
{
<h4>Fat Content Test</h4>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].TestRowStatus)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].TestRowSampleDescription)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w1EmptyFlask)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w2ThimbleWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w3ThimbleLeatherWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w4LeatherWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w5FlaskExtractWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].w6ExtractWeight)
</th>
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].FatContent)
</th>
@*<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].TestItemID)
</th>*@
<th>
@Html.DisplayNameFor(model => model.testRowFatContents[0].TestRowSampleID)
</th>
<th></th>
</tr>
</thead>
<tbody>
@for (int i=0; i < Model.testRowFatContents.Count(); i++)
{
<tr>
<td>
@Html.DisplayFor(modelItem => Model.testRowFatContents[i].TestRowStatus)
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].TestRowSampleDescription" class="form-control" />
<input type="hidden" asp-for="@Model.testRowFatContents[i].TestRowID" />
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].w1EmptyFlask" class="form-control" />
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].w2ThimbleWeight" class="form-control" />
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].w3ThimbleLeatherWeight" class="form-control" />
</td>
<td>
@Html.DisplayFor(modelItem => @Model.testRowFatContents[i].w4LeatherWeight)
</td>
<td>
<input asp-for="@Model.testRowFatContents[i].w5FlaskExtractWeight" class="form-control" />
</td>
<td>
@Html.DisplayFor(modelItem => @Model.testRowFatContents[i].w6ExtractWeight)
</td>
<td>
@Html.DisplayFor(modelItem => @Model.testRowFatContents[i].FatContent)
</td>
@*<td>
@Html.DisplayFor(modelItem => item.TestItemID)
</td>*@
<td>
@Html.DisplayFor(modelItem => @Model.testRowFatContents[i].TestRowSampleID)
</td>
@*<td>
<a asp-page="./Edit" asp-route-id="@item.TestRowID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.TestRowID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.TestRowID">Delete</a>
</td>*@
</tr>
}
任何帮助将不胜感激 - 如果我错过了一些明显的事情,请提前致歉。
在调试时我已经确认正在调用 OnPost 处理程序,并且模型状态正确地反映了新的(更改的)值 - 它似乎丢失了 dbcontext,我认为它应该在 GET 和POST 在 .NET 核心中调用。我真的想避免必须为每个特定类型手动指定对 dbcontext 的更新,因为我可能会有大量项目使用此页面。我还尝试使用似乎不起作用的 AttachRange(testFatContents),无论我是否将其包含在 GET 或 POST 处理程序(或两者!)
中我有一个类似结构的页面,可以将新项目添加到运行良好的 dbcontext,
提交表单时,表单数据会正确绑定到 PageModel 属性,但这些更改不会应用于上下文。您在断开连接的情况下工作 - 页面内的状态 - 包括上下文 - 不会在每次执行页面时重建 requests.It 。当页面因 POST 请求而执行时,状态由模型绑定器构建。因此,您必须将重构(更改)的实体附加到上下文,并告诉上下文它们处于(修改)的状态。然后上下文将知道要应用哪些更改。
EF Core 提供了一个 UpdateRange
方法,旨在让上下文知道集合已被修改,例如
_context.UpdateRange(testRowFatContents);
await _context.SaveChangesAsync();
在此处查看有关在断开连接的情况下工作的更多信息:https://www.learnentityframeworkcore.com/dbcontext/modifying-data#disconnected-scenario
您正在调用 _context.SaveChanges()
,这是在您的数据库中保存更改的正确方法,但您没有告诉数据库您想要更改的内容。
您希望使用模型绑定来收集您的用户发送到服务器的表单数据。
public async Task<IActionResult> OnPostSaveChangesAsync(List<TestRowFatContent> testRowFatContents, List<TestRowSaltExtract> testRowSaltExtracts, List<TestRowWaterAbsorption> testRowWaterAbsorptions, List<TestRowTensileStrength> testRowTensileStrength)
{
//Check if your list objects are filled with the user input, the name attribute of the input fields need to match the variable names above
//tell the context what you want to update
_context.UpdateRange(testRowFatContents);
_context.UpdateRange(testRowSaltExtracts);
_context.UpdateRange(testRowWaterAbsorptions);
_context.UpdateRange(testRowTensileStrength);
_context.SaveChanges();
return Page();
}
在 Entity Framework 中更新有时有点棘手,但重要的部分是您在 SaveChanges()
方法中拥有数据,然后告诉您的数据库如何处理它。
如果您有未包含在表单中的字段,它们将被覆盖,因为 EF 只是准确保存您从模型绑定中获得的对象。为避免未使用的字段被覆盖,您可以包括隐藏的输入字段或使用 EF 的 IsModified
属性。您必须在不想更改的属性上设置它。
示例:
_context.Entry(testRowFatContents).Property("FatContent").IsModified = false;