ASP.NET EF Core 使用外键插入多个表

ASP.NET EF Core inserting into multiple tables with foreign key

我真的想不通。我经常遇到这个错误,我不确定如何修改代码以支持一对多。到目前为止我读过的例子都很难理解。有人建议修改 fluent API 或模型甚至控制器。

错误:

SqlException: Cannot insert explicit value for identity column in table 'CompetitionCategory' when IDENTITY_INSERT is set to OFF.
System.Data.SqlClient.SqlCommand+<>c.b__122_0(Task result)

DbUpdateException: An error occurred while updating the entries. See the inner exception for details.

Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)

Competition 型号 class:

public class Competition
{
        [Key]
        public int ID { get; set; }
        [Required]
        [Display(Name = "Competition Name")]
        public string CompetitionName { get; set; }
        [Required]
        public string Status { get; set; }

        public ICollection<CompetitionCategory> CompetitionCategories { get; set; }
}   

CompetitionCategory 型号 class:

public class CompetitionCategory
{
        [Key]
        public int ID { get; set; }
        [Required]
        [Display(Name = "Category Name")]
        public string CategoryName { get; set; }

        [ForeignKey("CompetitionID")]
        public int CompetitionID { get; set; }
}

经过一些修改,我意识到要将列表传递给控制器​​,我应该使用视图模型,如下所示:

public class CategoriesViewModelIEnumerable
{
        public Competition competition { get; set; }
        public CompetitionCategory competitionCategory { get; set; }

        // From Microsoft
        public IEnumerable<string> SelectedCategories { get; set; }

        public List<SelectListItem> CategoriesList { get; } = new List<SelectListItem>
        {
            new SelectListItem { Value = "xxx", Text = "xxx" },
            new SelectListItem { Value = "yyy", Text = "yyy" },
            new SelectListItem { Value = "zzz", Text = "zzz" },
         };
}

我可以成功地将数据传递到我的控制器并 read/print 它在控制台上。然而,我遇到了最后一个错误,即将第二类保存到数据库中,可能是由于一些主要的 key/foreign 键限制。

我目前只能将列表中的第一项保存到数据库中。

public async Task<IActionResult> Create(CategoriesViewModelIEnumerable model)
{
    if (ModelState.IsValid)
    {
        CompetitionCategory competitionCategory = new CompetitionCategory();
        _context.Add(model.competition);
        await _context.SaveChangesAsync();

        foreach (var CategoryName in model.SelectedCategories)
        {
            competitionCategory.CategoryName = CategoryName;
            competitionCategory.CompetitionID = model.competition.ID;
            _context.Add(competitionCategory);
            await _context.SaveChangesAsync();
        }

        await _context.SaveChangesAsync();
    }
}

非常感谢您的帮助! :)

嗯,我可能看到 2 个问题。

  1. 您正在异步执行此操作,因此它可能会在第一个数据库更改完成之前尝试保存第二个数据库更改。
  2. 你应该创建完整的模型然后添加它,第二次添加在这种情况下应该是更新,因为你已经添加了第一次并保存了更改,所以会更好使用所有数据完全创建模型并添加它,然后保存更改。

       CompetitionCategory competitionCategory = new CompetitionCategory();
       foreach (var CategoryName in model.SelectedCategories)
       {
           competitionCategory.CategoryName = CategoryName;
           competitionCategory.CompetitionID = model.competition.ID;
    
        }
       _context.Add(model.competition);
        await _context.SaveChangesAsync();
    

编辑:这个答案在不清楚的情况下有效

非常感谢 Kelso Sharp 为我指明了正确的方向。通过编辑控制器修复它。

供其他用户日后参考:

基本上您只需要添加 "main model",在本例中为 Competition

因为 Competition 已经有一个 CompetitionCategory 的集合,初始化它并将每个 CompetitionCategory 添加到集合中,如 for 循环中所示。

最后,将模型 Competition 添加到数据库中。我假设 EF Core 会在完成外键映射后自动为您将集合数据添加到 CompetitionCategory table。 (如有错误请编辑)

工作控制器:

public async Task<IActionResult> Create(CategoriesViewModelIEnumerable model)
{
    if (ModelState.IsValid)
    {
    model.competition.CompetitionCategories = new Collection<CompetitionCategory>();
        foreach (var CategoryName in model.SelectedCategories)
        {
             model.competition.CompetitionCategories.Add(new CompetitionCategory { CompetitionID=model.competition.ID, CategoryName=CategoryName});
        }
        _context.Add(model.competition);
        await _context.SaveChangesAsync();
    }
}

我认为问题(尚未实际测试过)是您实例化了一次 CompetitionCategory 实体,然后尝试将单个实例添加到 foreach 循环中每次迭代的模型中。

您应该为每次迭代创建一个新的 CompetitionCategory 实例,然后将每个新实体添加到模型中:

foreach (var CategoryName in model.SelectedCategories) 
{ 
  CompetitionCategory competitionCategory = new CompetitionCategory();
  competitionCategory.CategoryName = CategoryName;
  competitionCategory.CompetitionID = model.competition.ID;
  _context.Add(competitionCategory);
}

await _context.SaveChangesAsync();

要修复此错误,您可能会考虑将显式 ID 存储到外键中,但 EF Core 在 table 上设置主键并将身份插入设置为开。要显式关闭它,在您的 DbContext 中,您可以覆盖方法 OnModelCreating(如果您尚未这样做)并放入此行:

protected override OnModelCreating(DbModelBuilder modelBuilder){
//some more code if necessary - define via Fluent api below
modelBuilder.Entity<CompetitionCategory>().Property(t => t.CompetitionID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
//define also other ids to not have databasegeneration option set to identity to allow explicit idsif required
}

您还可以考虑先将 Competition 和 CompetitionCategory 保存在事务中,这样无论是否遇到错误,您都可以使用 EF 中的 TransactionScope 回滚或提交事务。无论如何,您得到的错误是由于您设置了一个显式 ID,如果没有明确说明,EF 默认将设置一个带有标识插入的 ID 列,否则。如果这样更方便,您可以使用 databasegeneratedoption 属性。参见 DatabaseGeneratedOption attribute