我使用的是 .net5 和 EntityFrameworkCore 5。
我在问题和类别之间存在多对多关系。
我使用的是第一代代码。
public class Question
{
public int Id { get; set; }
public string Title { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Question> Questions { get; set; }
}
我想知道如何添加带有类别的问题。
我试过了:
[HttpPost]
public async Task<ActionResult<Question>> PostQuestion(Question question)
{
question.Categories.Add(new Category() { Id = 1 });
_context.Questions.Add(question);
await _context.SaveChangesAsync();
return CreatedAtAction("GetQuestion", new { id = question.Id }, question);
}
我的数据库中有一个 Id 为 1 的类别。
但是我得到这个例外
SqlException: Cannot insert explicit value for identity column in table 'Categories' when IDENTITY_INSERT is set to OFF.
多对多实体关系插入的正确方法是什么?
答案 0 :(得分:1)
真正正确和预期的方法是在将相关实体添加到“链接”集合之前将它们加载到上下文中。
假设您有一个现有相关实体键的列表:
var categoryIds = new[] { 1, 3, 4 };
然后您可以使用Find
方法将相应的实体加载到上下文中并获取它们的实例:
question.Categories = categoryIds
.Select(id => _context.Categories.Find(id))
.ToList();
缺点是它需要 N
次数据库往返来加载您可能并不真正需要的数据。
通过发出基于 Contains
的查询,只需一次额外的数据库往返即可:
question.Categories = await _context.Categories
.Where(e => categoryIds.Contains(e.Id))
.ToListAsync();
如果你真的不想要相关实体,下面是一些其他的方法。
如果上下文生命周期仅限于该调用,那么您可以像尝试一样使用假(存根)实体,但您必须将它们Attach
放到上下文中,以便 EF Core 将它们视为存在而不是如果你不这样做,就比新的:
question.Categories = categoryIds
.Select(id => _context.Attach(new Category { Id = id }))
.ToList();
另一种方式是在shadow join字典类型实体集中直接插入条目。但它需要知道连接实体类型的常规名称及其影子 FK,因此这是类型不安全的。
此外,您还需要先 Add
实体以使其临时密钥可用:
var entry = _context.Questions.Add(question);
然后对于您所显示的模型
var joinEntityName = "CategoryQuestion";
var fromPK = nameof(Question.Id);
var fromFK = "QuestionsId";
var toFK = "CategoriesId";
实际上这些可以从 EF Core 元数据中获取,这样会更安全:
var navInfo = entry.Collection(e => e.Courses).Metadata as Microsoft.EntityFrameworkCore.Metadata.ISkipNavigation;
var joinEntityName = navInfo.JoinEntityType.Name;
var fromPK = navInfo.ForeignKey.PrincipalKey.Properties.Single().Name;
var fromFK = navInfo.ForeignKey.Properties.Single().Name;
var toFK = navInfo.Inverse.ForeignKey.Properties.Single().Name;
那么插入代码为:
var fromId = entry.CurrentValues[fromPK]; // the temp PK
db.Set<Dictionary<string, object>>(joinEntityName).AddRange(
categoryIds.Select(toId => new Dictionary<string, object>
{
{ fromFK, fromId },
{ toFK, toId },
}));