我正在尝试创建一个名为
Facture
的对象,它有一个外键 Client
:
public class Facture
{
public int Id { get; set; }
[ValidateNever]
[DataType(DataType.Date)]
public DateTime? DateCreation { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name = "Date de facturation")]
public DateTime DateFacturation { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Date d'échéance")]
public DateTime? DateEcheance { get; set; }
[ValidateNever]
public required Client Client { get; set; }
}
public class Client
{
public int Id { get; set; }
[Required]
public required string Name { get; set; }
ICollection<Facture>? Factures { get; set; }
}
事实是我不知道如何获取从视图发送的
Client
的值。我已经在我的 FacturesController
: 中填充了一个下拉列表
public IActionResult Create()
{
ViewBag.Client = new SelectList(_context.Client, "Id", "Name");
return View();
}
在
Facture
视图中:
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<input asp-for="DateCreation" class="form-control" />
<span asp-validation-for="DateCreation" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DateFacturation" class="control-label"></label>
<input asp-for="DateFacturation" class="form-control" />
<span asp-validation-for="DateFacturation" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DateEcheance" class="control-label"></label>
<input asp-for="DateEcheance" class="form-control" />
<span asp-validation-for="DateEcheance" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Client" class="control-label"></label>
<select asp-for="Client" class="form-control" asp-items="ViewBag.Client"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
而且我不知道如何在第二个
Create
方法中获取client的值:
public async Task<IActionResult> Create([Bind("Id,DateFacturation,DateEcheance,Client")] Facture facture)
{
if (ModelState.IsValid)
{
facture.DateCreation = DateTime.Now;
_context.Add(facture);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(facture);
}
简短的回答是“不要将实体发送到视图,项目到视图模型”。数据不存在是因为您可能一开始就没有提供数据。意思是,在将 Facture 发送到视图时忘记了
.Include(f => f.Client)
,和/或没有视图 JS 的输入来重新构建要发回的客户端数据。
详细答案:控制器提供给 MVC 视图的对象不会发送到浏览器。它们被发送到视图引擎。视图引擎构建 html + JS 视图发送到浏览器。 “视图”没有模型是什么/曾经是什么的概念。它所拥有的只是视图引擎组成的
<form>
元素中的输入控件。如果您使用浏览器中的调试工具并检查您的页面,您将发现没有“模型”,只有 HTML 元素。当您的页面回发或获取要在 Ajax 调用等中使用的 FormData 时,输入元素的 Id 和值将组成一个简单的 JSON 对象并发送到控制器。然后,MVC 将其与控制器操作中的参数结合起来。
因此,为了让您的 MVC 控制器操作接收“Facture”,它将构造一个新的 Facture 实例,并根据传入的 JSON 中具有相同名称的值填充属性。任何不匹配的内容都会被忽略。如果您想获取相关数据,那么您需要在视图上拥有已映射的输入控件,并根据相关数据命名。这些将被命名为遵循 MVC 将遵循的约定来重新饱和相关实体的实例。通常,您没有/不需要这些字段的可见输入,因此实践是使用隐藏输入。 IE。
@Html.HiddenFor(model => model.ClientId)
如果您希望在返回时填充整个客户端实体,则需要为每个客户端属性提供输入。可能,但过度且不必要。最需要注意的是,您没有获得发送到视图引擎的相同对象实例,甚至没有获得控制器操作中 DbContext 跟踪的对象实例。
ViewModel 更好用,因为它们打破了这个错误的假设。您知道您将获得一个简单的“视图”模型,并且需要将其转换为新的模型,或者获取现有的“实体”以将模型转回其中。
当涉及到为新 Facture 选择“客户端”并填充客户端/w ClientId 和名称下拉列表的关联时,该 ClientId 将映射到模型中的属性。例如,如果这是一个新的 Facture 实体,并且您希望设置 Facture 的客户端,则从 DbContext 中获取它:
public async Task<IActionResult> Create([Bind("Id,DateFacturation,DateEcheance,Client")] Facture facture)
{
if (ModelState.IsValid)
{
var client = _context.Clients.Single(c => c.ClientId == facture.ClientId);
facture.DateCreation = DateTime.Now;
facture.Client = client;
_context.Add(facture);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(facture);
}
即使您为 Facture.Client 中的每个属性添加了隐藏输入,并确保在选择 Client 时填充它们,并在将 Facture“添加”到 DbContext 时在回调到控制器时传回填充facture.Client ,客户端引用不是跟踪引用,因此 EF 也会将其视为插入,从而导致重复数据异常,或插入具有新 PK 的新重复行。
我建议养成这样的习惯:将视图模型投影并传递给视图(在 GET 操作上使用
Select()
),然后在 POST/PUT/PATCH 上组合和查找实体。它可以避免与引用混淆,并避免可能会暴露出比您预期更多的域详细信息的问题,或者更糟糕的是,可能会出现不应更新的数据被覆盖的问题。 (如果您正在进行_context.Update(facture);
之类的编辑操作)