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

时间:2018-12-29 18:26:52

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

我真的无法弄清楚。我经常遇到此错误,并且不确定如何修改代码以支持1对多。到目前为止,我已阅读的示例很难理解。有些人建议修改流畅的API或模型,甚至修改控制器。

错误:

  

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

     

DbUpdateException:更新条目时发生错误。有关详细信息,请参见内部异常。

     

Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection连接,CancellationToken cancelledToken)

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

非常感谢您的帮助! :)

4 个答案:

答案 0 :(得分:2)

好吧,我可能会看到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 :(得分:0)

编辑:如果不清楚,可以使用此答案

非常感谢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();
    }
}

答案 2 :(得分:0)

我认为问题(尚未进行实际测试)是,您实例化了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 :(得分:0)

要解决该错误,您可以考虑将显式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