进行多对多实体关系插入的正确方法是什么?

时间:2021-03-02 23:11:10

标签: c# .net entity-framework entity-framework-core

我使用的是 .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.

多对多实体关系插入的正确方法是什么?

1 个答案:

答案 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 },
    }));
相关问题