为啥我的 ASP.NET Core 3.1 控制器会自动为从视图返回的 EF Core 模型分配 ID?
Posted
技术标签:
【中文标题】为啥我的 ASP.NET Core 3.1 控制器会自动为从视图返回的 EF Core 模型分配 ID?【英文标题】:Why is my ASP.NET Core 3.1 controller automatically assigning an ID to an EF Core model that was returned from a view?为什么我的 ASP.NET Core 3.1 控制器会自动为从视图返回的 EF Core 模型分配 ID? 【发布时间】:2021-10-02 17:06:37 【问题描述】:这有点奇怪,我 90% 确定这是一个错误,但你永远不知道。
我正在为学生提供辅导课程,并试图让他们了解如何使用 EF Core 和 ASP.NET Core 3.1。由于他们是初学者,因此我不想为每个视图编写模型,以去除视图不需要的所有 EF Core 实体数据。相反,我只是让他们将实体模型类直接传递给视图,并在他们从视图中POST
时将其直接传递回控制器。
我试图向他们展示如何将 Comment
添加到 Review
实体,但我遇到了一个奇怪的问题。
鉴于以下GET
操作:
[HttpGet]
public IActionResult Write([FromRoute] int id)
return View(new Comment
ReviewId = id
);
还有以下视图:
@model Comment
@
ViewBag.Title = "Add a comment";
<nav>
<a asp-controller="Home" asp-action="Index">Back</a>
</nav>
<h1>ID: @Model.Id</h1>
<form method="post" asp-controller="Comments" asp-action="Write">
@html.HiddenFor(c => c.ReviewId)
@Html.LabelFor(c => c.Content)
@Html.TextAreaFor(c => c.Content)
<button type="submit">Submit</button>
</form>
Comment
的 ID 会自动设置为我数据库中的最后一个 ID,即使它没有被 View 修改或由 GET
方法设置,只要它到达我的控制器的 POST
行动。
[HttpPost]
public IActionResult Write(Comment model)
// Using a debugger, "model" has an ID equal to that of the
// last Comment in my database as soon as we enter this method...
var review = _context.Reviews
.Include(r => r.Comments)
.FirstOrDefault(r => r.Id == model.ReviewId);
if (review != null)
review.Comments.Add(model);
_context.Update(review);
_context.SaveChanges(); // <-- Causes a primary key conflict error because of "model.Id"'s value
我对这种行为感到有点困惑,但我并没有永远将 EF Core 模型传递给控制器(我们通常编写视图特定的模型来去除我工作的不需要的数据),所以也许这个是正常行为吗?但它感觉对我来说就像一个错误。
我已经找到了一个“修复”,只需明确地绑定我想要的属性:
[HttpPost]
public IActionResult Write([Bind("Content, ReviewId")]Comment model)
但我想知道为什么model
的Id
属性一碰到我的控制器就会自动设置为最后一个Comment
实体的Id
。
【问题讨论】:
@GertArnold,当你第一次回复我的帖子时,我正在写我的答案。我发现了这个问题。这是因为我的签名中没有[FromForm]
,并且框架从 URL 中推断出模型的 ID(我猜是因为默认端点路由映射中的 id?
位与 model.Id
属性名称匹配?),它具有父评论的 ID(不会因为我们添加了更多评论而改变)。这意味着向同一个Review
对象添加更多Comment
s 最终会与Review.Id
的值发生冲突。
我怀疑会发生这样的事情,但不知道如何发生。所以就是这样。
我从来没有在网络核心中使用过 [FromForm]。但它通常仅在需要指向源的特殊情况下使用(在同一操作中,一些参数来自路由,一些来自查询字符串,一些来自表单)。
【参考方案1】:
在进一步摆弄之后,我找到了问题的根源。
TL;DR:
这解决了我的问题。
[HttpPost]
public IActionResult Write([FromForm]Comment model)
解释:
我完全忘记将[FromForm]
添加到我的控制器的POST
操作中,因此框架没有将正确的数据放在正确的字段中。我猜它正在使用默认控制器路由端点映射的id?
部分并将此值分配给model.Id
,这会在将多个Comment
s 添加到Review
时导致问题,因为URL 的路由@987654329 @ 值是父 Review
的 Id
,而不是 Comment
,当您将多个 Comment
s 添加到单个 Review
时,此值保持不变。
这意味着向第一个Review
对象添加第二个Comment
会导致主键冲突,因为Review
的Id
是“1”,第一个 Comment
的Id
也为“1”,URL为/write/1
,将第二个Comment
的Id
赋值为“1”,导致key冲突。
这也是为什么model.Id
的值一接触到我的控制器就似乎发生了变化。因为它是。控制器只是使用它认为最好的方式从请求中获取数据,但它做错了。
当我添加一个隐藏的 <input>
标记以确保将 Id
强制设置为 0 不会解决问题时,我意识到绑定有问题。
我之前在视图中添加了以下行,以确保我的 Id
在视图中设置为“0”:
<h1>ID: @Model.Id</h1>
但隐藏的<input>
标记的值与Review
的Id
匹配,而不是“0”。所以我再次查看数据绑定,并在 MSDN 上找到了这个页面,其中涵盖了 Binding source parameter inference 的主题,其中指出框架将尝试使用它认为最好的方法,而不是给出明确的绑定源属性。
【讨论】:
以上是关于为啥我的 ASP.NET Core 3.1 控制器会自动为从视图返回的 EF Core 模型分配 ID?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的 ASP.Net Core Web API 控制器不返回 XML?
ASP.Net Core 3.1 脚手架的身份页面并未全部使用(即登录页面正常,注册页面不正常)......为啥?