You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core下EF5多对多关联添加学生到课程时原有成员丢失问题

问题根因

  • 多次重复提交更改冲突:AddStudent 方法内部每次执行完关联操作就单独调用 SaveChangesAsync 提交,同时控制器末尾又调用了一次全局的 _context.SaveChanges()。如果控制器开头查询的 Course 实例未加载最新的 StudentsList 数据,最终的全局提交会用旧数据覆盖之前已经提交的关联关系。
  • 重复查询导致跟踪实例不一致:如果仓储的查询方法使用了 AsNoTracking,或者首次查询 Course 时没有 Include 关联的 StudentsList,会导致内存中存在多个不同的 Course 实例,EF 提交更改时会以最后跟踪到的实例状态为准。
  • 学生存在性判断逻辑不可靠:使用 Contains 做引用类型比较,只要两个学生实例不是同一个跟踪对象,哪怕ID相同也会判定为不存在,判断逻辑失效。

修复方案

1. 调整提交逻辑,统一提交所有更改

删除 AddStudent 方法内部的 SaveChangesAsync 调用,所有实体修改完成后统一在控制器末尾提交,避免多次提交冲突。

2. 优化存在性判断逻辑

用学生ID比较替代引用比较,保证判断准确性。

3. 可选优化:减少重复查询

如果上下文是同一请求内共享的作用域实例,无需在 AddStudent 中重复查询 Course,直接复用控制器中已查询到的实例即可,降低数据库开销。

修改后的代码示例

仓储层 AddStudent 方法

public async Task<bool> AddStudent(long id, string studentId)
{
    Course course = await _context.Courses.Include(c => c.StudentsList).FirstOrDefaultAsync(c => c.CourseId == id);
    Student student = await _context.Students.FirstOrDefaultAsync(s => s.StudentId == studentId);
    if (course == null || student == null)
        return false;
    // 改用ID判断是否已存在
    if (!course.StudentsList.Any(s => s.StudentId == studentId))
    {
        course.StudentsList.Add(student);
    }
    return true;
}

控制器调整

[HttpPatch("{id}")]
public async Task<IActionResult> RegisterStudentToCourse(long id, [FromBody] List<string> studentsIds)
{
    // 查询Course时必须Include StudentsList,保证拿到最新的关联数据
    Course course = await _courseRepo.GetCourse(id);
    if (course == null || !studentsIds.Any())
        return BadRequest();
    IEnumerable<Lesson> list = await _lessonRepo.GetLessons(id);
    List<Lesson> lessonsList = list.ToList();

    foreach (string studentId in studentsIds)
    {
        Student student = _studetRepo.GetStudent(studentId);
        if (student == null)
            return BadRequest();
        // 改用ID判断是否已存在
        if (course.StudentsList.Any(s => s.StudentId == studentId))
            continue;
        
        await _courseRepo.AddStudent(id, studentId);
        foreach (Lesson l in lessonsList)
        {
            Attendance attendance = await _attendanceRepo.CreateAttandance(studentId);
            await _lessonRepo.AddAttendance(l.LessonId, attendance);
        }
    }
    // 所有修改完成后统一提交一次即可
    await _context.SaveChangesAsync();
    return Ok();
}

可选:模型属性初始化(避免空引用异常)

public class Course
{
    public long CourseId { get; set; }
    public string CourseName { get; set; }
    public IEnumerable<Lesson> LessonsList { get; set; }
    public DateTime CourseStartDate { get; set; }
    public DateTime CourseEndDate { get; set; }
    // 初始化关联集合,避免空引用
    public List<Student> StudentsList { get; set; } = new List<Student>();
}

public class Student
{
    [Key]
    public string StudentId { get; set; }
    public List<Course> Courses { get; set; } = new List<Course>();
}

内容的提问来源于stack exchange,提问作者Ziv har-lev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 08:36:04