entity-framework - SaveChanges 上的 EF Core 不断向第二个表添加记录
问题描述
我正在创建一个轮胎存储调度程序,并将所有存储信息存储在数据库中。在完成预订过程时,它不仅将记录添加到tblSchedule
表中,还添加了客户数据的副本并将其存储到tblStorageData
表中,只是使用设置为标识列的不同 ID。
这是我的计划模型:
[Table("tblSchedule")]
public class Schedule
{
[Required]
public int ID { get; set; }
[Required]
public DateTime ScheduleDateTime { get; set; }
public bool Confirmed { get; set; } = false;
public StorageData StorageDataInfo { get; set; }
}
这是我的StorageData
模型:
[Table("tblStorageData")]
public class StorageData
{
[Required]
public int ID { get; set; }
[Required(ErrorMessage = "License plate is required to perform search.")]
[StringLength(20)]
public string LicensePlate { get; set; }
[StringLength(100)]
public string Name { get; set; }
[StringLength(10)]
public string BIN { get; set; }
}
这是我AddSchedule
获取信息并将其添加到数据库的方法:
public int AddSchedule(Schedule model)
{
if (CheckSchedule(model.ScheduleDateTime) >= 4)
{
return 0;
}
else
{
Schedule sch = new Schedule()
{
ScheduleDateTime = model.ScheduleDateTime,
StorageDataInfo = model.StorageDataInfo
};
_context.Schedule.Add(sch);
_context.SaveChanges();
return 1;
}
}
这是我的日程控制器:
[HttpGet]
public IActionResult Book(string id)
{
string session = HttpContext.Session.GetString("CustomerVerify");
string customerBooking = HttpContext.Session.GetString("CustomerBooking");
if (session == null)
{
return RedirectToAction("Index", "Customer");
}
if (string.IsNullOrEmpty(id))
{
return RedirectToAction("Month", "Schedule");
}
DateTime dt = DateTime.ParseExact(id, "yyyyMMddHHmmtt", CultureInfo.InvariantCulture);
FullBookingViewModel fbvm = JsonConvert.DeserializeObject<FullBookingViewModel>(customerBooking);
Schedule sch = new Schedule();
sch.ScheduleDateTime = dt;
sch.StorageDataInfo = fbvm.StorageDataInfo;
return View(sch);
}
[HttpPost]
public IActionResult Book(Schedule model)
{
Schedule sch = new Schedule()
{
StorageDataInfo = model.StorageDataInfo,
ScheduleDateTime = model.ScheduleDateTime,
};
int result = _scheduleData.AddSchedule(model);
if (result == 0)
{
return RedirectToAction("Day", "Schedule");
}
else
{
return RedirectToAction("Month", "Schedule");
}
}
这是 cshtml 视图:
@model Schedule
<h1>Schedule.Book</h1>
<p>Please review the following appointment before confirming:</p>
@*@Model.ScheduleDateTime.ToString("yyyy/MM/dd hh:mm tt")
<br />
@Model.StorageDataInfo.Name @Model.StorageDataInfo.LicensePlate @Model.StorageDataInfo.BIN
<br />*@
<form method="post">
<label asp-for="@Model.ScheduleDateTime">@Model.ScheduleDateTime</label><br />
@Html.HiddenFor(m => m.ScheduleDateTime)
<label asp-for="@Model.StorageDataInfo.Name">@Model.StorageDataInfo.Name</label>
<label asp-for="@Model.StorageDataInfo.LicensePlate">@Model.StorageDataInfo.LicensePlate</label>
<label asp-for="@Model.StorageDataInfo.BIN">@Model.StorageDataInfo.BIN</label><br />
@Html.HiddenFor(m => m.StorageDataInfo.Name)
@Html.HiddenFor(m => m.StorageDataInfo.LicensePlate)
@Html.HiddenFor(m => m.StorageDataInfo.BIN)
<button type="submit">Confirm</button>
<button>Cancel</button>
<button>Select Different Month</button>
<button>Select Different Day</button>
<button>Select Different Time</button>
</form>
这是 tblSchedule 表的屏幕截图,其中包含正确添加的时间,但 StorageDataInfoID 应该始终相同。
这是 tblStorageData 表的屏幕截图,其中还添加了重复信息。
我不知道为什么它应该只添加到一个引用 tblStorageData 表的 ID 而不是复制客户信息的表中时添加到两个表中。
解决方案
这样做是因为当 EF 使用导航属性时,引用就是一切。如果 DbContext 没有跟踪实体的实例,它将将该实体视为新实例,无论该实体是否具有 ID。
最初的解决方案是告诉 DbContext 跟踪提供的 StorageData 实例,并假设记录存在:
public int AddSchedule(Schedule model)
{
if (CheckSchedule(model.ScheduleDateTime) >= 4)
return 0;
_context.Attach(model.StorageDataInfo);
_context.Schedule.Add(model);
_context.SaveChanges();
return 1;
}
当服务器方法接受“实体”作为参数时,您实际上并没有返回一个实体,而是一个由客户端提供的反序列化数据填充的实体类。您不需要每次都创建新实例,但是在执行更新之类的操作以验证从客户端传入的任何数据时,您还需要小心,因为它可能已被篡改。在 DbContext 已经在跟踪相同 ID 的另一个实例的情况下,附加实体仍然会导致操作错误。上述方法的更安全版本是:
public int AddSchedule(Schedule model)
{
if (CheckSchedule(model.ScheduleDateTime) >= 4)
return 0;
var existingStorageData = _context.StorageData.Local
.SingleOrDefault(x => x.ID == model.StorageDataInfo.ID);
if(storageData == null)
_context.Attach(model.StorageDataInfo);
else
model.StorageDataInfo = existingStorageData
_context.Schedule.Add(model);
_context.SaveChanges();
return 1;
}
这样做是检查 DbContext 本地缓存(不访问数据库)以返回任何具有该 ID 的跟踪实例。如果没有返回实例,我们可以附加传入的副本,否则我们将引用替换为跟踪的实例。
推荐阅读
- google-cloud-platform - 项目的结算帐号未打开 GCP
- apache-spark - 为什么缓存小型 Spark RDD 需要在 Yarn 中分配大量内存?
- aws-lambda - 如何从另一个 AWS 账户中的步进函数调用一个 AWS 账户中的步进函数?
- javascript - 如何在 p5 Canvas 上居中图像/CreateCapture
- python - Pandas & seaborn:如何根据预定义的标准对调查答案进行分类
- javascript - 如何以角度实现从url栏直接搜索?
- python - 迭代地将 numpy 或 torch 数组绘制为图像并打开按键
- mysql - 列计数与第 1 行的值计数不匹配 - MySQL
- javascript - 使用useState Hook时如何重构具有先前状态的if else if?
- python - 我需要在某个日期创建的所有子文件夹中找到所有文本文件,打开它们,然后将内容复制到一个文本文件中