在向数据库添加新实体时,上下文未填充外键

问题描述

我遇到一个问题,我从一个包含一对多子关系的现有MysqL数据库中搭建了上下文和模型。当我尝试创建新的父母和孩子时,EF-core返回一个错误,指出该外键关系已被违反。

我看了一下EF是如何将其架起来的,但看不到那里的任何问题。由于某种原因,针对数据库执行的代码将忽略数据模型期望填充外键的事实。 我错过了什么会导致这种关系被忽略?

以下是相关位:

  1. 表定义
  2. 自动折叠上下文
  3. 执行保存的代码
  4. EF引发错误sql代码

这是两个表。注意扫描上的外键,它是的子项。

项目

CREATE TABLE `item` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,`name` VARCHAR(250) NOT NULL COLLATE 'utf8_general_ci',`UPC` VARCHAR(20) NOT NULL COLLATE 'utf8_general_ci',`friendly_name` VARCHAR(250) NULL DEFAULT NULL COLLATE 'utf8_general_ci',`date_created` DATETIME NOT NULL DEFAULT current_timestamp(),`date_deleted` DATETIME NULL DEFAULT NULL,`is_homemade` tinyint(1) NOT NULL DEFAULT '0',PRIMARY KEY (`id`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=287
;

扫描

CREATE TABLE `scan` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,`item_id` INT(11) NOT NULL DEFAULT -1,`expiration_date` DATETIME NULL DEFAULT NULL,`use_date` DATETIME NULL DEFAULT NULL,`create_date` DATETIME NOT NULL DEFAULT current_timestamp(),`delete_date` DATETIME NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE,INDEX `fk_item_instance_item` (`item_id`) USING BTREE,CONSTRAINT `fk_item_instance_item` FOREIGN KEY (`item_id`) REFERENCES `inventory`.`item` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=537
;

上下文中用于映射外键的脚手架代码。这将显示在modelBuilder的扫描端。

entity.HasOne(d => d.Item)
  .WithMany(p => p.Scan)
  .HasForeignKey(d => d.ItemId)
  .OnDelete(DeleteBehavior.ClientSetNull)
  .HasConstraintName("fk_item_instance_item");

当我执行以下代码来创建具有关联的新扫描的新项目时,我从数据库中收到错误消息。

var newItem = new Item()
{
    Name = scan.Name,Upc = scan.UPC,DateCreated = DateTime.Now,};

var newScan = new Scan()
{
    Item = newItem,CreateDate = DateTime.Now,ExpirationDate = scan.ExpirationDate,};

newItem.Scan = new List<Scan>() { newScan };

if (ModelState.IsValid)
{
    _context.Item.Add(newItem);
    _context.Entry(newScan).State = EntityState.Added;

    await _context.SaveChangesAsync();
    ...

数据库抛出一个错误,抱怨如何违反外键。查看创建的sql,我肯定可以看到它没有将项目映射到扫描。

INSERT INTO `item` (`date_created`,`date_deleted`,`friendly_name`,`is_homemade`,`name`,`UPC`)
VALUES (timestamp('2020-09-08 10:09:38.033399'),NULL,false,'test item','testing');
SELECT `id`
FROM `item`
WHERE ROW_COUNT() = 1 AND `id` = LAST_INSERT_ID()

INSERT INTO `scan` (`create_date`,`delete_date`,`expiration_date`,`use_date`)
VALUES (timestamp('2020-09-08 10:09:38.037147'),NULL);
SELECT `id`,`item_id`
FROM `scan`
WHERE ROW_COUNT() = 1 AND `id` = LAST_INSERT_ID()

解决方法

我认为问题在于,在保存newItem之前,您将newScan作为newItem中的引用。因此,newScan不会获得要填充的实际数据库实体ID,仅需要首先保存的内存中副本即可。

以这种方式尝试-(PS:我还没有测试过,只是写出我认为可以解决的内容)

if (ModelState.IsValid)
{
    var newItem = new Item()
    {
        Name = scan.Name,Upc = scan.UPC,DateCreated = DateTime.Now,};

    _context.Item.Add(newItem);
    await _context.SaveChangesAsync();

    var newScan = new Scan()
    {
        Item = newItem,// after EF has saved the Item entity,it should retrieve the DB generated Item automatically
        CreateDate = DateTime.Now,ExpirationDate = scan.ExpirationDate,};

    newItem.Scan = new List<Scan>() { newScan };


    _context.Entry(newItem).State = EntityState.Modified;
    _context.Entry(newScan).State = EntityState.Added; // Maybe you should also save this before updating the newItem a second time. It may throw an error if not!
    await _context.SaveChangesAsync();    
    
}