如何解决EF Core:更新现有子项而不是添加新子项
我正在使用 ASP.NET Core (MVC) 创建一个 Web 应用程序。数据库是 SQL Server。代码优先设计。我是 EF 的新手。
我已经定义了 Visitor
模型、一个 Checkin
和一个 Checkout
模型,其中访客/签入比例为 1:1,访客/签出比例为 1:1。该应用的想法是,当创建新访客时,会创建新的签到和签出记录以及访客记录的子项。
我的模型如下(我使用ForeignKey
数据注解来定义访问者和签到之间以及访问者和结帐之间的一对一关系):
public class Visitor
{
[Key]
public int Id { get; set; }
[DisplayName("Visitor First Name")]
[Required]
public string FirstName { get; set; }
[DisplayName("Visitor Last Name")]
[Required]
public string LastName { get; set; }
public CheckIn CheckIn { get; set; }
public CheckOut CheckOut { get; set; }
}
public class CheckIn
{
[ForeignKey("Visitor")]
public int Id { get; set; }
[DisplayName("Time In")]
[DisplayFormat(ApplyFormatInEditMode = true,DataFormatString = "{0:g}")]
public DateTime Time { get; set; }
public Visitor Visitor { get; set; }
}
public class CheckOut
{
[ForeignKey("Visitor")]
public int Id { get; set; }
[DisplayName("Time Out")]
[DisplayFormat(ApplyFormatInEditMode = true,DataFormatString = "{0:g}")]
public DateTime? Time { get; set; }
public Visitor Visitor { get; set; }
}
我的控制器中有一个 POST 方法,它应该在数据库中添加一个新访问者以及一个新的签入和签出记录作为访问者记录的子项。
但是,我遇到了未将签到记录添加到数据库的问题:而是更新了数据库中的现有签到记录,因此其主键是新访问者记录的键 (ID)。新访客记录创建成功。此外,新的结帐记录也已正确创建并作为新访客记录的子项。这是我的控制器中 POST 方法的代码(删除了一些与此问题无关的代码)(也使用视图模型将数据从视图传递到控制器):
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ConfirmSignin(SignVM obj)
{
if (ModelState.IsValid)
{
var visitor = new Visitor();
var checkin = new CheckIn();
var checkout = new CheckOut();
checkin = obj.CheckIn;
checkout = obj.CheckOut;
visitor = obj.Visitor;
visitor.CheckIn = checkin;
visitor.CheckOut = checkout;
_context.Add(visitor);
_context.Add(checkin);
_context.Add(checkout);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index),"Home",new { LocationId = obj.Visitor.LocationId });
}
return View(obj);
}
在数据库中成功创建了访问者:
在数据库中也成功创建了结帐:
您会注意到结帐记录的 ID 与父记录访问者的 ID 匹配 - 所以这部分似乎工作正常。
但是在数据库中添加新访问者时不会添加签到记录:
当我添加访问者 ID=6 时,应该在数据库中添加新的签到记录 - 而 EF 更新了现有的签到记录并将其主键从 5(前一个访问者)更新为 6(新访问者)。
我假设我在控制器代码中做错了什么。有什么建议吗?
更新 - 2021 年 4 月 28 日: 下面是对应的GET方法的代码:
[HttpGet]
public IActionResult ConfirmSignin()
{
SignVM signVM = new();
if (TempData["NewSignIn"] is string s)
{
signVM = JsonConvert.DeserializeObject<SignVM>(s);
}
ViewData["LocationName"] = new SelectList(_context.Locations,"Id","Name",signVM.Visitor.LocationId);
return View(signVM);
}
以下是更新后的 POST 方法的完整代码:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ConfirmSignin(SignVM obj)
{
if (ModelState.IsValid)
{
var escort = new Escort();
escort = await _context.Escorts
.Include(c => c.CheckIn)
.FirstOrDefaultAsync(m => m.EmployeeId == obj.Escort.EmployeeId);
if (escort == null)
{
escort = obj.Escort;
_context.Add(escort);
await _context.SaveChangesAsync();
}
var visitor = obj.Visitor;
var checkin = obj.CheckIn;
var checkout = obj.CheckOut;
visitor.CheckIn = checkin;
visitor.CheckOut = checkout;
escort.CheckIn = new List<CheckIn>();
escort.CheckOut = new List<CheckOut>();
escort.CheckIn.Add(checkin);
escort.CheckOut.Add(checkout);
_context.Add(visitor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index),new { LocationId = obj.Visitor.LocationId });
}
return View(obj);
}
这是对应视图的代码:
@model VisitorLog.Models.ViewModels.SignVM
@{
ViewData["Title"] = "Sign In";
}
<div class="row justify-content-center mt-3">
<div class="col-md-4">
<div class="h4 text-center">Confirm Sign In</div>
<hr />
<form asp-action="ConfirmSignin">
<div class="form-group">
<label asp-for="Visitor.LocationId" class="control-label"></label>
<select asp-for="Visitor.LocationId" class="form-control" disabled asp-items="ViewBag.LocationName"></select>
</div>
<div class="form-group">
<label asp-for="Visitor.FullName" class="control-label"></label>
<input asp-for="Visitor.FullName" class="form-control" readonly value="@Model.Visitor.FullName" />
</div>
<div class="form-group">
<label asp-for="Visitor.Company" class="control-label"></label>
<input asp-for="Visitor.Company" class="form-control" readonly value="@Model.Visitor.Company" />
</div>
<div class="form-group">
<label asp-for="Escort.EmployeeId" class="control-label"></label>
<input asp-for="Escort.EmployeeId" class="form-control" readonly value="@Model.Escort.EmployeeId" />
</div>
<div class="form-group">
<label asp-for="Escort.FullName" class="control-label"></label>
<input asp-for="Escort.FullName" class="form-control" readonly value="@Model.Escort.FullName" />
</div>
<div class="form-group">
<label asp-for="CheckIn.Time" class="control-label"></label>
<input asp-for="CheckIn.Time" class="form-control" readonly type="text" value="@Model.CheckIn.Time.ToString("g")" />
</div>
<div class="form-group">
<label asp-for="CheckOut.ExpectedTime" class="control-label"></label>
<input asp-for="CheckOut.ExpectedTime" class="form-control" readonly type="text" value="@Model.CheckOut.ExpectedTime.ToString("g")" />
</div>
<div class="form-group">
<input asp-for="Visitor.LocationId" class="form-control" hidden value="@Model.Visitor.LocationId" />
</div>
<div class="form-group">
<input asp-for="Visitor.FirstName" class="form-control" hidden value="@Model.Visitor.FirstName" />
</div>
<div class="form-group">
<input asp-for="Visitor.LastName" class="form-control" hidden value="@Model.Visitor.LastName" />
</div>
<div class="form-group">
<input asp-for="Visitor.SignedIn" class="form-control" hidden value="@Model.Visitor.SignedIn" />
</div>
<div class="form-group">
<input asp-for="Escort.FirstName" class="form-control" hidden value="@Model.Escort.FirstName" />
</div>
<div class="form-group">
<input asp-for="Escort.LastName" class="form-control" hidden value="@Model.Escort.LastName" />
</div>
<div class="form-group">
<input asp-for="Escort.EmailAddress" class="form-control" hidden value="@Model.Escort.EmailAddress" />
</div>
<div class="form-group">
<input asp-for="Escort.Phone" class="form-control" hidden value="@Model.Escort.Phone" />
</div>
<div class="form-group text-center mt-4">
<input type="submit" class="btn btn-primary col-5" value="Confirm" />
<a class="btn btn-danger col-5 offset-1" asp-controller="Visitors" asp-action="SignIn" asp-route-LocationId=@Context.Request.Query["LocationId"]>Back</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
解决方法
你必须修复你的课程:
public class CheckIn
{
[Key]
public int Id { get; set; }
[DisplayName("Time In")]
[DisplayFormat(ApplyFormatInEditMode = true,DataFormatString = "{0:g}")]
public DateTime Time { get; set; }
public int VisitorId { get; set; }
[ForeignKey("VisitorId")]
public virtual Visitor Visitor { get; set; }
}
public class CheckOut
{
[Key]
public int Id { get; set; }
[DisplayName("Time Out")]
[DisplayFormat(ApplyFormatInEditMode = true,DataFormatString = "{0:g}")]
public DateTime? Time { get; set; }
public int VisitorId { get; set; }
[ForeignKey("VisitorId")]
public virtual Visitor Visitor { get; set; }
}
并修复您的代码
.....
var visitor = obj.Visitor;
var checkin = obj.CheckIn;
var checkout = obj.CheckOut;
visitor.CheckIn = checkin;
visitor.CheckOut = checkout;
_context.Add(visitor);
await _context.SaveChangesAsync();
.....
但是恕我直言,您不需要 2 张桌子 - 签入和签出。一个 CheckInOut 和 IsCheckOut bool 标志就足够了
public class CheckInOut
{
[Key]
public int Id { get; set; }
public bool IsCheckOut { get; set; }
[DisplayName("Time")]
[DisplayFormat(ApplyFormatInEditMode = true,DataFormatString = "{0:g}")]
public DateTime Time { get; set; }
public int VisitorId { get; set; }
[ForeignKey("VisitorId")]
public virtual Visitor Visitor { get; set; }
}
,
我通过如下更新 POST 方法解决了这个问题:
改变:
escort = await _context.Escorts
.Include(c => c.CheckIn)
.FirstOrDefaultAsync(m => m.EmployeeId == obj.Escort.EmployeeId);
致:
escort = await _context.Escorts
.FirstOrDefaultAsync(m => m.EmployeeId == obj.Escort.EmployeeId);
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。