繁体   English   中英

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

[英]What is the correct way to do many to many entity relation insert?

我正在使用 .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.

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

真正正确和预期的方法是在将相关实体添加到“链接”集合之前将它们加载到上下文中。

假设您有一个现有相关实体键的列表:

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

另一种方法是直接在影子连接字典类型实体集中插入条目。 但它需要知道连接实体类型及其影子 FK 的常规名称,因此这是类型不安全的。

您还需要首先Add实体才能使其临时密钥可用:

var entry = _context.Questions.Add(question);

然后对于所示的 model 你有

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM