将新实体添加到数据库时上下文未填充外键

Context not populating foreign key on adding new entities to database

我有一个问题,我从现有的 MySQL 数据库搭建了一个上下文和模型,该数据库包含一个父对多个子关系。当我尝试创建新的父项和子项时,EF-core returns 出现违反外键关系的错误。

我看了一下 EF 是如何构建它的,但没有发现任何问题。出于某种原因,针对数据库执行的代码忽略了数据模型期望填充外键这一事实。 我错过了什么会导致这种关系被忽略?

以下是相关位:

  1. Table 定义
  2. 自动脚手架上下文
  3. 执行保存的代码
  4. SQL EF 生成的引发错误的代码

这是两个表格。注意 scan 上的外键,它是 item.

的子项

项目

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
;

上下文中用于映射外键的脚手架代码。这出现在模型构建器的扫描端。

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, 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, NULL, NULL);
SELECT `id`, `item_id`
FROM `scan`
WHERE ROW_COUNT() = 1 AND `id` = LAST_INSERT_ID()

我认为问题在于您在保存 newItem 之前在 newScan 中使用了您的 newItem 作为参考。因此 newScan 没有获得实际的数据库实体 ID 来填充,只有需要先保存的 in-memory 副本。

这样试试-(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();    
    
}