【问题标题】:ASP.NET EF Core inserting into multiple tables with foreign keyASP.NET EF Core 使用外键插入多个表
【发布时间】:2018-12-29 18:26:51
【问题描述】:

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

错误:

SqlException:当 IDENTITY_INSERT 设置为 OFF 时,无法在表“CompetitionCategory”中插入标识列的显式值。
System.Data.SqlClient.SqlCommand+c.b__122_0(任务结果)

DbUpdateException:更新条目时出错。有关详细信息,请参阅内部异常。

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

Competition模型类:

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模型类:

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" },
         };
}

我可以成功地将数据传递给我的控制器并在控制台上读取/打印它。但是我遇到了最后一个错误,即将第二个类别向前保存到数据库中,可能是由于一些主键/外键限制。

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

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

非常感谢您的帮助! :)

【问题讨论】:

    标签: c# asp.net-core asp.net-core-mvc ef-core-2.0 ef-core-2.1


    【解决方案1】:

    嗯,我可能会看到 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();
      

    【讨论】:

    • 您好。让我试着回去。当我添加 1 个比赛和 1 个类别时,异步工作。所以不太确定这是否是问题
    • 我想我会先尝试第 2 点。将在这里再次更新结果。谢谢你的帮助! :)
    • 使用 2 也可以修复 1。
    • 嗨。它没有用。错误消失了,但它没有添加到 CompetitionCategory 表中。它只插入到竞争表中。谢谢
    • 好吧,它不是为了逐字使用,只是为了给你指明正确的方向:)
    【解决方案2】:

    我认为问题(尚未实际测试过)是您将 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();
    

    【讨论】:

      【解决方案3】:

      要修复该错误,您可能会考虑将显式 ID 存储到外键中,但 EF Core 会在标识插入设置为 on 的表上设置主键。要显式关闭它,您可以在 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

      【讨论】:

        【解决方案4】:

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

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

        供其他用户日后参考:

        基本上你只需要添加你的“主模型”,在这种情况下Competition

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

        最后,将模型Competition 添加到数据库中。我假设 EF Core 会在完成外键映射后自动将 Collection 数据添加到 CompetitionCategory 表中。 (如有错误请大家修改)

        工作控制器:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-08-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-14
          相关资源
          最近更新 更多