【问题标题】:Why is a new item being added?为什么要添加新项目?
【发布时间】:2021-05-15 12:32:38
【问题描述】:

一个老师可以有很多科目,反之亦然,一个科目可以有很多老师。也就是说,这里是 mtm((ef > 5.0)。这正是我所做的。但是当添加新教师时,会创建新科目。如何解决这个问题?

添加教师之前

  • 化学
  • PE
  • 生物学

然后我要添加一位新老师

    // for example subjects = ["Biology", "Chemistry"] 

    await _db.Teachers.AddAsync(new Teacher
    {
          ...
          Subjects = subjects,
    });

添加老师后

  • 化学
  • PE
  • 生物学
  • 化学
  • 生物学

教师模式

public class Teacher : User
{
    public string Name { get; set; }
    public string Surname { get; set; }
    public IList<Subject> Subjects { get; set; }
}

用户模型

public class User
{
    public int Id { get; set; }
    public string Login { get; set; }
}

主题模型

public class Subject
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<Teacher> Teachers { get; set; }
}

【问题讨论】:

  • @Larnu 好的,我会修复它
  • 主题来自哪里?您必须先从数据库中提取现有科目,然后将它们添加到教师的现有科目列表中(一种方法)。
  • @olegshmel 在您保存的同一个 DbContext 实例中,您检索要添加的主题(不禁用跟踪)。您不会像您所做的那样为主题设置新列表。您从 DbContext 获取当前列表,然后添加您之前检索到的新列表。这不会尝试保存“新”主题。

标签: c# sql sql-server database entity-framework


【解决方案1】:

当您传递实体时,这是 Web 应用程序和 EF 的常见缺陷,或者您创建对相关实体的新引用并假设 EF 将关联它们的情况。

如果您将一组主题传递到您的方法中,例如:

public void CreateTeacher(string name, IEnumerable<Subject> subjects)
{
    var teacher = new Teacher
    {
        Name = name,
        Subjects = subjects
    };
    context.Teachers.Add(teacher);
    context.SaveChanges();
}

科目列表中有化学和生物学。它看起来很无辜,但在 Web 请求调用之间,那些进入的主题只是反序列化的对象。 DbContext 不知道它们,因此当它们与新教师相关联时,它们被视为新主题。默认情况下,EF 约定会将名为 Id 的列视为身份,因此这将导致两个名为 Chemistry & Biology 的新主题添加到主题表中。

为了避免这种行为,我们需要做以下两件事之一:将主题与 DbContext 关联(在检查以确保它们尚未关联之后)或确保我们仅使用 DbContext 已获取的主题。

示例:从 DbContext 中获取主题

public void CreateTeacher(string name, IEnumerable<Subject> subjects)
{
    var subjectIds = subjects.Select(x => x.Id).ToList();
    var dbSubjects = context.Subjects.Where(x => subjectIds.Contains(x.Id)).ToList();
    var teacher = new Teacher
    {
        Name = name,
        Subjects = dbSubjects
    };
    context.Teachers.Add(teacher);
    context.SaveChanges();
}

通常我们会重构该方法以仅传入主题 ID 并使用 DbContext 加载它们。无需通过网络发送所有主题字段。

示例:与 DbContext 关联

public void CreateTeacher(string name, IEnumerable<Subject> subjects)
{
    var subjectIds = subjects.Select(x => x.Id).ToList();
    foreach(var subject in subjects)
    {
        if(!context.Subjects.Local.Any(x => x.Id == subject.Id)
            context.Subjects.Attach(subject);
    }
    var dbSubjects = context.Subjects.Local
        .Where(x => subjectIds.Contains(x => x.Id))
        .ToList();

    var teacher = new Teacher
    {
        Name = name,
        Subjects = dbSubjects
    };
    context.Teachers.Add(teacher);
    context.SaveChanges();
}

这假设所有进入的主题都是有效的并且引用数据库中的现有行。如果您可以混合新主题,那么这将变得更加复杂。 (检查主题 ID 是否为 0 等)通过转到context.Subjects.Local,我们不会访问数据库,我们只需在附加主题之前检查 DbContext 缓存中是否有任何加载的主题。附加后,它将成为.Local 集合的一部分,因此我们可以在不接触数据库的情况下找到我们的主题。

通常从数据库中获取数据更安全、更简单。通过 ID 获取实体非常快,有助于确保数据状态支持传入的引用。

【讨论】:

    【解决方案2】:

    我认为 Sergey 提议的内容接近于所需内容,因为我认为您需要将添加关系移到添加教师记录之外。只是倒数第二行与他的提议有所不同。

    var teacher=new Teacher
        {
              ...
                       // don' t use this Subjects = subjects,
        });
    
     _db.Teachers.Add(teacher);
     teacher.Subjects = tvm.Subjects;
     await _db.Teacher.SaveChangesAsync();
    

    【讨论】:

    • 是的,你理解我的正确。但是我收到了一个新错误:““无法跟踪实体类型'Subject'的实例,因为已经在跟踪具有相同键值的另一个实例 {'Id'}”。从理论上讲,它不再与问题相关。因此,如果我解决了它,我会将您的答案标记为正确
    猜你喜欢
    • 1970-01-01
    • 2021-11-30
    • 1970-01-01
    • 1970-01-01
    • 2016-12-02
    • 2020-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多